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
+27 -27
View File
@@ -44,7 +44,7 @@ body {
text-decoration: none; flex-shrink: 0;
}
.nav-logo-mark {
width: 30px; height: 30px; border-radius: 8px;
width: 30px; height: 30px; border-radius: var(--radius-md);
background: linear-gradient(135deg, #007AFF, #0055CC);
display: flex; align-items: center; justify-content: center;
font-size: 15px; font-weight: 800; color: #1d1d1f;
@@ -54,7 +54,7 @@ body {
.nav-link:hover { color: #1d1d1f; }
.nav-cta {
background: #007AFF; color: #1d1d1f; font-size: 14px; font-weight: 600;
padding: 8px 18px; border-radius: 10px; text-decoration: none;
padding: 8px 18px; border-radius: var(--radius-md); text-decoration: none;
transition: background 0.15s; white-space: nowrap;
}
.nav-cta:hover { background: #0066DD; }
@@ -77,7 +77,7 @@ body {
.hero-eyebrow {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(0,122,255,0.2); border: 0.5px solid rgba(0,122,255,0.4);
border-radius: 20px; padding: 5px 14px;
border-radius: var(--radius-xl); padding: 5px 14px;
font-size: 12px; font-weight: 600; color: #64B5F6;
letter-spacing: 0.04em; margin-bottom: 24px;
position: relative; z-index: 1;
@@ -109,14 +109,14 @@ body {
.btn-primary {
background: #007AFF; color: #1d1d1f;
font-size: 16px; font-weight: 600; padding: 14px 28px;
border-radius: 14px; text-decoration: none; transition: all 0.2s;
border-radius: var(--radius-lg); text-decoration: none; transition: all 0.2s;
box-shadow: 0 4px 20px rgba(0,122,255,0.4);
}
.btn-primary:hover { background: #0066DD; transform: translateY(-1px); box-shadow: 0 6px 24px rgba(0,122,255,0.5); }
.btn-secondary {
background: rgba(0,0,0,0.1); color: #1d1d1f;
font-size: 16px; font-weight: 600; padding: 14px 28px;
border-radius: 14px; text-decoration: none;
border-radius: var(--radius-lg); text-decoration: none;
border: 0.5px solid rgba(0,0,0,0.2);
backdrop-filter: blur(10px); transition: all 0.2s;
}
@@ -156,7 +156,7 @@ body {
}
.problem-card {
background: var(--grouped-background-secondary);
border-radius: 16px; padding: 24px;
border-radius: var(--radius-lg); padding: 24px;
border: 0.5px solid var(--separator);
}
.problem-card .icon { font-size: 28px; margin-bottom: 14px; }
@@ -172,7 +172,7 @@ body {
padding: 28px 24px; position: relative; text-align: center;
}
.step-num {
width: 44px; height: 44px; border-radius: 50%;
width: 44px; height: 44px; border-radius: var(--radius-full);
background: linear-gradient(135deg, #007AFF, #0055CC);
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 700; color: #1d1d1f;
@@ -194,7 +194,7 @@ body {
}
.for-card {
background: var(--grouped-background-secondary);
border: 0.5px solid var(--separator); border-radius: 14px;
border: 0.5px solid var(--separator); border-radius: var(--radius-lg);
padding: 20px 18px;
display: flex; align-items: flex-start; gap: 12px;
}
@@ -209,7 +209,7 @@ body {
}
.case-card {
background: var(--grouped-background-secondary);
border-radius: 16px; overflow: hidden;
border-radius: var(--radius-lg); overflow: hidden;
border: 0.5px solid var(--separator);
}
.case-card-header {
@@ -217,7 +217,7 @@ body {
display: flex; align-items: flex-start; gap: 12px;
}
.case-logo {
width: 44px; height: 44px; border-radius: 10px;
width: 44px; height: 44px; border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
font-size: 22px; flex-shrink: 0;
}
@@ -235,7 +235,7 @@ body {
}
.case-tag {
display: inline-block; font-size: 11px; font-weight: 600;
padding: 3px 10px; border-radius: 20px;
padding: 3px 10px; border-radius: var(--radius-xl);
background: rgba(0,122,255,0.1); color: var(--ios-blue);
margin-right: 6px; margin-bottom: 4px;
}
@@ -247,7 +247,7 @@ body {
}
.pricing-card {
background: var(--grouped-background-secondary);
border: 0.5px solid var(--separator); border-radius: 20px;
border: 0.5px solid var(--separator); border-radius: var(--radius-xl);
padding: 28px 24px;
}
.pricing-card.featured {
@@ -257,7 +257,7 @@ body {
}
.pricing-badge {
display: inline-block; font-size: 11px; font-weight: 700;
padding: 3px 10px; border-radius: 20px;
padding: 3px 10px; border-radius: var(--radius-xl);
background: var(--ios-blue); color: #1d1d1f;
margin-bottom: 16px; letter-spacing: 0.04em;
}
@@ -274,12 +274,12 @@ body {
display: flex; align-items: center; gap: 8px;
}
.pricing-features li:last-child { border-bottom: none; }
.pricing-features li::before { content: ''; color: var(--ios-green); font-weight: 700; flex-shrink: 0; }
.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(--ios-green); font-weight: 700; flex-shrink: 0; }
.pricing-btn {
display: block; text-align: center;
background: var(--ios-blue); color: #1d1d1f;
font-size: 15px; font-weight: 600; padding: 12px;
border-radius: 12px; text-decoration: none; transition: background 0.15s;
border-radius: var(--radius-md); text-decoration: none; transition: background 0.15s;
}
.pricing-btn:hover { background: #0066DD; }
.pricing-btn.secondary {
@@ -454,7 +454,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<!-- HERO -->
<section class="hero">
<div class="hero-eyebrow">🛸 quiXzoom + LandveX Platform</div>
<div class="hero-eyebrow"> quiXzoom + LandveX Platform</div>
<h1>Right control.<br><em>Right cost.</em><br>Right interval.</h1>
<p class="hero-tagline">Infrastructure owners are overpaying for blanket inspection contracts and making decisions from outdated data. LandveX changes that.</p>
<div class="hero-ctas">
@@ -486,12 +486,12 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="problem-grid">
<div class="problem-card">
<div class="icon">💸</div>
<div class="icon"></div>
<h3>Overpriced blanket contracts</h3>
<p>Traditional consulting firms charge for scheduled visits regardless of actual risk. You pay the same whether the asset is fine or failing.</p>
</div>
<div class="problem-card">
<div class="icon">📅</div>
<div class="icon"></div>
<h3>Annual cycle doesn't match reality</h3>
<p>Critical infrastructure changes faster than annual inspection cycles. A bridge deck can deteriorate significantly between visits — and you won't know until it's too late.</p>
</div>
@@ -501,7 +501,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<p>Maintenance budgets are allocated based on last year's report. Risk-based prioritization is impossible without current, objective assessment data.</p>
</div>
<div class="problem-card">
<div class="icon">🗺️</div>
<div class="icon"></div>
<h3>No spatial intelligence</h3>
<p>Traditional inspection reports are PDFs. There's no system connecting asset condition, geospatial context, and maintenance history into actionable intelligence.</p>
</div>
@@ -550,42 +550,42 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="for-grid">
<div class="for-card">
<div class="for-icon">🏙️</div>
<div class="for-icon"></div>
<div>
<h3>Municipalities</h3>
<p>Roads, bridges, parks, public buildings, and drainage networks. Serve residents with evidence-based maintenance — within budget.</p>
</div>
</div>
<div class="for-card">
<div class="for-icon">🚧</div>
<div class="for-icon"></div>
<div>
<h3>Trafikverket</h3>
<p>National road and rail network inspection at scale. Replace manual bridge surveys with drone-powered AI assessment across the entire network.</p>
</div>
</div>
<div class="for-card">
<div class="for-icon">🏗️</div>
<div class="for-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>
<div>
<h3>Property Owners</h3>
<p>Commercial real estate, industrial complexes, and facility managers who need objective condition data to plan capex and comply with regulatory requirements.</p>
</div>
</div>
<div class="for-card">
<div class="for-icon"></div>
<div class="for-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>
<div>
<h3>Energy Infrastructure</h3>
<p>Power lines, substations, wind farms, and solar installations. Scheduled and condition-triggered inspections without shutdowns or scaffolding.</p>
</div>
</div>
<div class="for-card">
<div class="for-icon">🌊</div>
<div class="for-icon"></div>
<div>
<h3>Water Utilities</h3>
<p>Dams, pipelines, reservoirs, and treatment facilities. AI-detected anomalies before they become emergency repairs or service failures.</p>
</div>
</div>
<div class="for-card">
<div class="for-icon">🏛️</div>
<div class="for-icon"></div>
<div>
<h3>Region & County</h3>
<p>Regional infrastructure authorities managing multi-municipality assets. Centralized oversight with per-asset granularity.</p>
@@ -604,7 +604,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="case-grid">
<div class="case-card">
<div class="case-card-header">
<div class="case-logo" style="background:rgba(0,122,255,0.1)">🏙️</div>
<div class="case-logo" style="background:rgba(0,122,255,0.1)"></div>
<div>
<div class="case-org">Municipality of Gothenburg</div>
<div class="case-name">Bridge Network Assessment 2025</div>
@@ -636,7 +636,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="case-card">
<div class="case-card-header">
<div class="case-logo" style="background:rgba(255,149,0,0.1)"></div>
<div class="case-logo" style="background:rgba(255,149,0,0.1)"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M9 2L5 9H8L7 14L11 7H8L9 2Z" fill="#f59e0b"/></svg></div>
<div>
<div class="case-org">Vattenfall Eldistribution AB</div>
<div class="case-name">High-Voltage Line Survey — Norrland</div>