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
@@ -40,13 +40,13 @@
.nav-links{display:flex;align-items:center;gap:32px}
.nav-links a{color:var(--text-muted);font-size:var(--text-base);font-weight:500;transition:color .15s}
.nav-links a:hover{color:var(--text-dark)}
.btn-nav{background:var(--blue);color:#fff;border:none;border-radius:var(--radius-lg);padding:10px 24px;font-size:var(--text-base);font-weight:700;transition:background .15s;white-space:nowrap}
.btn-nav{background:var(--blue);color:#fff;border:none;border-radius: var(--radius-lg);padding:10px 24px;font-size:var(--text-base);font-weight:700;transition:background .15s;white-space:nowrap}
.btn-nav:hover{background:var(--blue-dark)}
.mobile-menu{display:none}
@media(max-width:768px){
.nav-links{display:none}
.hamburger{display:flex;flex-direction:column;gap:5px;cursor:pointer;padding:8px;background:none;border:none}
.hamburger span{display:block;width:22px;height:2px;background:var(--text-dark);border-radius:2px}
.hamburger span{display:block;width:22px;height:2px;background:var(--text-dark);border-radius: var(--radius-sm)}
.mobile-menu{display:none;position:fixed;inset:0;top:68px;background:var(--bg-white);z-index:299;padding:24px;flex-direction:column;gap:0;border-top:1px solid rgba(0,0,0,.08)}
.mobile-menu.open{display:flex}
.mobile-menu a{color:var(--text-body);font-size:1.1rem;font-weight:500;padding:18px 0;border-bottom:1px solid rgba(0,0,0,.08);display:block}
@@ -57,16 +57,16 @@
.hero-org-inner{max-width:720px}
.hero-org h1{color:var(--text-dark);margin-bottom:24px}
.hero-org p{font-size:clamp(1.0625rem,2vw,1.25rem);color:var(--text-body);line-height:1.6;margin-bottom:32px}
.hero-badge{display:inline-flex;align-items:center;gap:8px;background:var(--blue-pale);border:1px solid rgba(0,102,255,.15);border-radius:100px;padding:8px 18px;font-size:var(--text-sm);font-weight:600;color:var(--blue);margin-bottom:28px}
.hero-badge{display:inline-flex;align-items:center;gap:8px;background:var(--blue-pale);border:1px solid rgba(0,102,255,.15);border-radius: 100px;padding:8px 18px;font-size:var(--text-sm);font-weight:600;color:var(--blue);margin-bottom:28px}
.hero-cta{display:flex;gap:12px;flex-wrap:wrap}
.btn-primary{background:var(--blue);color:#fff;border:none;border-radius:var(--radius-lg);padding:14px 32px;font-size:1rem;font-weight:700;transition:background .15s}
.btn-primary{background:var(--blue);color:#fff;border:none;border-radius: var(--radius-lg);padding:14px 32px;font-size:1rem;font-weight:700;transition:background .15s}
.btn-primary:hover{background:var(--blue-dark)}
.btn-ghost{background:transparent;border:2px solid rgba(0,0,0,.15);color:var(--text-dark);border-radius:var(--radius-lg);padding:14px 32px;font-size:1rem;font-weight:700;transition:border-color .15s,background .15s}
.btn-ghost{background:transparent;border:2px solid rgba(0,0,0,.15);color:var(--text-dark);border-radius: var(--radius-lg);padding:14px 32px;font-size:1rem;font-weight:700;transition:border-color .15s,background .15s}
.btn-ghost:hover{border-color:var(--blue);background:var(--blue-pale)}
/* USE CASES */
.usecase-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.usecase-card{background:var(--bg-off);border-radius:var(--radius-card);padding:32px 28px;transition:transform var(--transition),box-shadow var(--transition)}
.usecase-card{background:var(--bg-off);border-radius: var(--radius-card);padding:32px 28px;transition:transform var(--transition),box-shadow var(--transition)}
.usecase-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-hover)}
.usecase-icon{font-size:2rem;margin-bottom:16px}
.usecase-card h3{font-size:1.125rem;font-weight:800;color:var(--text-dark);margin-bottom:8px}
@@ -78,12 +78,12 @@
.feature-row{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;margin-bottom:64px}
.feature-row.reverse{direction:rtl}
.feature-row.reverse>*{direction:ltr}
.feature-visual{background:var(--bg-off);border-radius:var(--radius-card);padding:40px;text-align:center}
.feature-visual{background:var(--bg-off);border-radius: var(--radius-card);padding:40px;text-align:center}
.feature-visual .big-icon{font-size:4rem}
.feature-text h3{font-size:var(--text-2xl);color:var(--text-dark);margin-bottom:16px}
.feature-text p{color:var(--text-body);margin-bottom:16px}
.feature-list li{display:flex;align-items:flex-start;gap:10px;font-size:var(--text-base);padding:6px 0}
.feature-list li::before{content:'';color:var(--green);font-weight:700}
.feature-list li::before{content:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';color:var(--green);font-weight:700}
@media(max-width:768px){.feature-row{grid-template-columns:1fr;gap:32px}}
/* STATS */
@@ -97,14 +97,14 @@
/* PRICING */
.pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.pricing-card{background:var(--bg-off);border:2px solid #E8E8E8;border-radius:var(--radius-card);padding:36px 28px;text-align:center}
.pricing-card{background:var(--bg-off);border:2px solid #E8E8E8;border-radius: var(--radius-card);padding:36px 28px;text-align:center}
.pricing-card.featured{border-color:var(--blue);transform:scale(1.02)}
.pricing-card h3{font-size:1.5rem;font-weight:900;margin-bottom:4px;color:var(--text-dark)}
.pricing-price{font-size:2rem;font-weight:900;color:var(--blue);margin:16px 0}
.pricing-price span{font-size:var(--text-sm);color:var(--text-muted);font-weight:500}
.pricing-features{text-align:left;margin:24px 0}
.pricing-features li{display:flex;align-items:flex-start;gap:10px;font-size:var(--text-base);padding:6px 0}
.pricing-features li::before{content:'';color:var(--green);font-weight:700}
.pricing-features li::before{content:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';color:var(--green);font-weight:700}
@media(max-width:900px){.pricing-grid{grid-template-columns:1fr;max-width:480px;margin:0 auto}}
/* CTA */
@@ -155,7 +155,7 @@
<section class="hero-org">
<div class="container">
<div class="hero-org-inner">
<div class="hero-badge">🏢 Enterprise Field Intelligence</div>
<div class="hero-badge"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H7M6 8H7M6 11H7M9 5H10M9 8H10M9 11H10" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg> Enterprise Field Intelligence</div>
<h1>Verified reality,<br>on demand.</h1>
<p>Deploy photo missions at scale. Infrastructure inspection, property documentation, retail audits, environmental surveys — captured by verified Zoomers, reviewed by AI, delivered in hours.</p>
<div class="hero-cta">
@@ -185,32 +185,32 @@
</div>
<div class="usecase-grid">
<div class="usecase-card">
<div class="usecase-icon">🏗️</div>
<div class="usecase-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 14H14M4 14V8L8 4L12 8V14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 14V10H10V14" stroke="#666" stroke-width="1.5"/></svg></div>
<h3>Infrastructure Inspection</h3>
<p>Bridges, roads, facades, and utilities. Document condition without sending an inspector. Get geotagged, timestamped imagery within hours.</p>
</div>
<div class="usecase-card">
<div class="usecase-icon">🏠</div>
<div class="usecase-icon"></div>
<h3>Property Documentation</h3>
<p>Pre-purchase inspections, rental condition reports, insurance claims. Standardised photo sets from any address.</p>
</div>
<div class="usecase-card">
<div class="usecase-icon">🛒</div>
<div class="usecase-icon"></div>
<h3>Retail & OOH Audit</h3>
<p>Verify signage placement, shelf compliance, promotional displays. Competitive intelligence from real stores, not syndicated data.</p>
</div>
<div class="usecase-card">
<div class="usecase-icon">🌿</div>
<div class="usecase-icon"></div>
<h3>Environmental Survey</h3>
<p>Track vegetation, erosion, flood damage, coastal change. Repeatable observation points with GPS accuracy.</p>
</div>
<div class="usecase-card">
<div class="usecase-icon">🚗</div>
<div class="usecase-icon"></div>
<h3>Road Condition</h3>
<p>Potholes, surface degradation, signage visibility. Municipalities prioritise repairs with verified visual evidence.</p>
</div>
<div class="usecase-card">
<div class="usecase-icon"></div>
<div class="usecase-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M9 2L5 9H8L7 14L11 7H8L9 2Z" fill="#f59e0b"/></svg></div>
<h3>Event & Emergency</h3>
<p>Storm damage, accident documentation, construction progress. Rapid deployment when speed matters.</p>
</div>
@@ -225,7 +225,7 @@
<p>From mission definition to verified data delivery.</p>
</div>
<div class="feature-row">
<div class="feature-visual"><div class="big-icon">📝</div></div>
<div class="feature-visual"><div class="big-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 13V10L10 3L13 6L6 13H3Z" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 4L12 7" stroke="#666" stroke-width="1.5"/></svg></div></div>
<div class="feature-text">
<h3>1. Define your mission</h3>
<p>Set location, requirements, and compensation. Use templates or build custom briefs. Define exactly what angles, coverage, and metadata you need.</p>
@@ -238,7 +238,7 @@
</div>
</div>
<div class="feature-row reverse">
<div class="feature-visual"><div class="big-icon">📸</div></div>
<div class="feature-visual"><div class="big-icon"></div></div>
<div class="feature-text">
<h3>2. Zoomers capture</h3>
<p>Verified Zoomers in the area claim and complete missions. Every photo carries GPS, timestamp, and integrity hash at capture.</p>
@@ -251,7 +251,7 @@
</div>
</div>
<div class="feature-row">
<div class="feature-visual"><div class="big-icon"></div></div>
<div class="feature-visual"><div class="big-icon"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>
<div class="feature-text">
<h3>3. AI review & delivery</h3>
<p>Submissions are reviewed for completeness, quality, and specification compliance within minutes. Approved data is delivered to your dashboard or API.</p>
@@ -344,7 +344,6 @@
<ul>
<li><a href="/academy/">Zoomer Academy</a></li>
<li><a href="/glossary">Glossary</a></li>
<li><a href="/developer">API docs</a></li>
<li><a href="/status">System status</a></li>
</ul>
</div>