feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+21 -21
View File
@@ -36,8 +36,8 @@ nav{background:rgba(0,0,0,0.92);backdrop-filter:blur(20px) saturate(180%);border
.hero-title{font-size:clamp(32px,6vw,52px);font-weight:900;letter-spacing:-1.5px;line-height:1.08;color:var(--text);margin-bottom:18px}
.hero-title span{color:var(--accent3)}
.hero-sub{font-size:17px;color:var(--t2);line-height:1.6;max-width:520px;margin:0 auto 24px}
.hero-badge{display:inline-flex;align-items:center;gap:6px;background:rgba(0,122,255,0.08);border:1px solid rgba(0,122,255,0.18);border-radius:20px;padding:6px 14px;font-size:11px;font-weight:600;color:var(--accent3)}
.hero-badge::before{content:'';width:6px;height:6px;border-radius:50%;background:#22c55e;animation:pulse 1.8s ease-in-out infinite}
.hero-badge{display:inline-flex;align-items:center;gap:6px;background:rgba(0,122,255,0.08);border:1px solid rgba(0,122,255,0.18);border-radius: var(--radius-xl);padding:6px 14px;font-size:11px;font-weight:600;color:var(--accent3)}
.hero-badge::before{content:'';width:6px;height:6px;border-radius: var(--radius-full);background:#22c55e;animation:pulse 1.8s ease-in-out infinite}
@keyframes pulse{0%{opacity:1}50%{opacity:0.4}100%{opacity:1}}
/* CITY GRID */
@@ -47,21 +47,21 @@ nav{background:rgba(0,0,0,0.92);backdrop-filter:blur(20px) saturate(180%);border
.section-count{font-size:12px;color:var(--t3)}
.cities-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
.city-card{background:var(--s1);border:1px solid var(--border);border-radius:20px;padding:24px;text-decoration:none;color:var(--text);display:block;transition:all 0.15s;position:relative;overflow:hidden}
.city-card{background:var(--s1);border:1px solid var(--border);border-radius: var(--radius-xl);padding:24px;text-decoration:none;color:var(--text);display:block;transition:all 0.15s;position:relative;overflow:hidden}
.city-card:hover{border-color:rgba(0,122,255,0.25);box-shadow:0 8px 32px rgba(0,0,0,0.08);transform:translateY(-2px)}
.city-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:20px 20px 0 0}
.city-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius: var(--radius-xl) 20px 0 0}
.city-card.sweden::before{background:linear-gradient(90deg,#006aa7,#fecc02)}
.city-card-flag{font-size:32px;margin-bottom:14px;line-height:1}
.city-card-name{font-size:22px;font-weight:900;letter-spacing:-0.6px;color:var(--text);margin-bottom:4px}
.city-card-region{font-size:11px;color:var(--t3);font-weight:500;margin-bottom:16px}
.city-card-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:20px}
.city-stat{background:var(--s2);border-radius:10px;padding:10px 12px}
.city-stat{background:var(--s2);border-radius: var(--radius-md);padding:10px 12px}
.city-stat-val{font-size:18px;font-weight:800;letter-spacing:-0.3px;line-height:1;margin-bottom:2px}
.city-stat-lbl{font-size:9px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;color:var(--t3)}
.city-card-tags{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:20px}
.tag{font-size:10px;font-weight:600;padding:3px 9px;border-radius:6px;letter-spacing:0.2px}
.tag{font-size:10px;font-weight:600;padding:3px 9px;border-radius: var(--radius-sm);letter-spacing:0.2px}
.tag-green{background:rgba(22,163,74,0.1);color:#15803d}
.tag-yellow{background:rgba(202,138,4,0.1);color:#92400e}
.tag-red{background:rgba(220,38,38,0.1);color:#991b1b}
@@ -78,9 +78,9 @@ nav{background:rgba(0,0,0,0.92);backdrop-filter:blur(20px) saturate(180%);border
/* METHODOLOGY */
.method-bar{max-width:1100px;margin:0 auto 48px;padding:0 24px}
.method-inner{background:var(--s1);border:1px solid var(--border);border-radius:16px;padding:20px 24px;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}
.method-inner{background:var(--s1);border:1px solid var(--border);border-radius: var(--radius-lg);padding:20px 24px;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}
.method-item{display:flex;align-items:flex-start;gap:10px}
.method-icon{width:32px;height:32px;border-radius:8px;background:rgba(0,122,255,0.08);display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0}
.method-icon{width:32px;height:32px;border-radius: var(--radius-md);background:rgba(0,122,255,0.08);display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0}
.method-title{font-size:12px;font-weight:700;color:var(--text);margin-bottom:2px}
.method-sub{font-size:11px;color:var(--t2);line-height:1.4}
@@ -228,7 +228,7 @@ footer a{color:var(--accent3);text-decoration:none}
<!-- STOCKHOLM -->
<a href="/stockholm-intelligence" class="city-card sweden">
<div class="city-card-flag">🇸🇪</div>
<div class="city-card-flag"><span class="flag-se">SE</span></div>
<div class="city-card-name">Stockholm</div>
<div class="city-card-region">Capital · Stockholm County</div>
<div class="city-card-stats">
@@ -251,7 +251,7 @@ footer a{color:var(--accent3);text-decoration:none}
</div>
<div class="city-card-tags">
<span class="tag tag-green">Global coverage</span>
<span class="tag tag-red">RinkebyTensta </span>
<span class="tag tag-red">RinkebyTensta </span>
<span class="tag tag-blue">Hagastaden +91 UMI</span>
<span class="tag tag-green">Östermalm premium</span>
</div>
@@ -263,7 +263,7 @@ footer a{color:var(--accent3);text-decoration:none}
<!-- GÖTEBORG -->
<a href="/goteborg-intelligence" class="city-card sweden">
<div class="city-card-flag"></div>
<div class="city-card-flag"></div>
<div class="city-card-name">Göteborg</div>
<div class="city-card-region">Västra Götaland · Sweden's 2nd City</div>
<div class="city-card-stats">
@@ -286,7 +286,7 @@ footer a{color:var(--accent3);text-decoration:none}
</div>
<div class="city-card-tags">
<span class="tag tag-blue">Mölndal pharma hub</span>
<span class="tag tag-red">Angered HIGH</span>
<span class="tag tag-red">Angered HIGH</span>
<span class="tag tag-green">Linnéstaden +54 UMI</span>
<span class="tag tag-yellow">Avenyn tourism</span>
</div>
@@ -298,7 +298,7 @@ footer a{color:var(--accent3);text-decoration:none}
<!-- MALMÖ -->
<a href="/malmo-intelligence" class="city-card sweden">
<div class="city-card-flag">🌉</div>
<div class="city-card-flag"></div>
<div class="city-card-name">Malmö</div>
<div class="city-card-region">Skåne · Öresund Region</div>
<div class="city-card-stats">
@@ -321,7 +321,7 @@ footer a{color:var(--accent3);text-decoration:none}
</div>
<div class="city-card-tags">
<span class="tag tag-blue">Hyllie → CPH 20 min</span>
<span class="tag tag-red">Rosengård HIGH</span>
<span class="tag tag-red">Rosengård HIGH</span>
<span class="tag tag-green">Västra Hamnen +56</span>
<span class="tag tag-yellow">Turning Torso icon</span>
</div>
@@ -333,7 +333,7 @@ footer a{color:var(--accent3);text-decoration:none}
<!-- UPPSALA -->
<a href="/uppsala-intelligence" class="city-card sweden">
<div class="city-card-flag">🎓</div>
<div class="city-card-flag"></div>
<div class="city-card-name">Uppsala</div>
<div class="city-card-region">Uppsala County · University City</div>
<div class="city-card-stats">
@@ -374,42 +374,42 @@ footer a{color:var(--accent3);text-decoration:none}
<div style="font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:var(--t3);margin-bottom:14px">Intelligence Sources</div>
<div class="method-inner">
<div class="method-item">
<div class="method-icon">🗺</div>
<div class="method-icon"></div>
<div>
<div class="method-title">District Scoring</div>
<div class="method-sub">6 profile dimensions per district. Business, Tourist, Safety, Investment, Nightlife, Infrastructure.</div>
</div>
</div>
<div class="method-item">
<div class="method-icon"></div>
<div class="method-icon"></div>
<div>
<div class="method-title">Contradiction Engine</div>
<div class="method-sub">Detects gaps between official data and observed reality. High/Medium/Low classification.</div>
</div>
</div>
<div class="method-item">
<div class="method-icon">🚓</div>
<div class="method-icon"></div>
<div>
<div class="method-title">Live Police Events</div>
<div class="method-sub">Polisen.se API — real-time incident data mapped to districts.</div>
</div>
</div>
<div class="method-item">
<div class="method-icon">📰</div>
<div class="method-icon"></div>
<div>
<div class="method-title">Local News</div>
<div class="method-sub">SVT local feeds for each city — Stockholm, Väst, Skåne, Uppland.</div>
</div>
</div>
<div class="method-item">
<div class="method-icon">📈</div>
<div class="method-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 12L6 8L9 11L14 5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 5H14V9" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<div>
<div class="method-title">Urban Momentum Index</div>
<div class="method-sub">UMI tracks trajectory. High positive = fast appreciation or development signal.</div>
</div>
</div>
<div class="method-item">
<div class="method-icon">🔬</div>
<div class="method-icon"></div>
<div>
<div class="method-title">Source First</div>
<div class="method-sub">Every data point is sourced and weighted. Confidence score shown per district.</div>