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
+36 -36
View File
@@ -48,7 +48,7 @@
overflow-x: hidden;
}
/* ── Nav Bar ─────────────────────────────────────────────────────────── */
/* Nav Bar */
.nav-bar {
position: sticky;
top: 0;
@@ -81,7 +81,7 @@
font-size: 13px;
font-weight: 600;
padding: 4px 10px;
border-radius: 20px;
border-radius: var(--radius-xl);
background: var(--ios-bg-tertiary);
color: var(--ios-label-secondary);
}
@@ -89,7 +89,7 @@
.nav-pill.warning { background: rgba(255,159,10,0.2); color: var(--ios-orange); }
.nav-pill.ok { background: rgba(48,209,88,0.2); color: var(--ios-green); }
/* ── Hero Countdown ──────────────────────────────────────────────────── */
/* Hero Countdown */
.hero {
display: flex;
flex-direction: column;
@@ -160,7 +160,7 @@
}
.hero-date strong { color: var(--ios-label); font-weight: 600; }
/* ── Stat Row ────────────────────────────────────────────────────────── */
/* Stat Row */
.stat-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
@@ -187,7 +187,7 @@
margin-top: 2px;
}
/* ── Content ─────────────────────────────────────────────────────────── */
/* Content */
.content { padding: 0 0 20px; }
/* Section header */
@@ -237,7 +237,7 @@
.checkbox {
width: 24px;
height: 24px;
border-radius: 50%;
border-radius: var(--radius-full);
border: 2px solid var(--ios-bg-elevated);
display: flex;
align-items: center;
@@ -282,7 +282,7 @@
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
border-radius: var(--radius-sm);
background: rgba(255,69,58,0.15);
color: var(--ios-red);
flex-shrink: 0;
@@ -291,7 +291,7 @@
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
border-radius: var(--radius-sm);
background: rgba(255,159,10,0.15);
color: var(--ios-orange);
flex-shrink: 0;
@@ -300,7 +300,7 @@
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
border-radius: var(--radius-sm);
background: rgba(48,209,88,0.15);
color: var(--ios-green);
flex-shrink: 0;
@@ -318,7 +318,7 @@
.status-icon {
width: 36px;
height: 36px;
border-radius: 10px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
@@ -344,7 +344,7 @@
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.status-dot.red { background: var(--ios-red); }
@@ -364,7 +364,7 @@
.action-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
@@ -408,12 +408,12 @@
.progress-bar-track {
height: 6px;
background: var(--ios-bg-elevated);
border-radius: 3px;
border-radius: var(--radius-sm);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
border-radius: 3px;
border-radius: var(--radius-sm);
background: var(--ios-green);
transition: width 0.8s var(--spring);
}
@@ -448,7 +448,7 @@
height: 14px;
border: 2px solid var(--ios-label-tertiary);
border-top-color: var(--ios-blue);
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
vertical-align: middle;
}
@@ -501,19 +501,19 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</head>
<body>
<!-- ── Nav Bar ─────────────────────────────────────────────────────────────── -->
<!-- Nav Bar -->
<nav class="nav-bar">
<a class="nav-back" href="aamos-hub/index.html">
<svg width="10" height="17" viewBox="0 0 10 17" fill="none"><path d="M9 1L1 8.5L9 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Hub
</a>
<span class="nav-title">quiXzoom Launch</span>
<span class="nav-pill critical" id="nav-pill">🚀 <span id="nav-days"></span>d</span>
<span class="nav-pill critical" id="nav-pill"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2L10 8L14 9L8 14L6 8L2 7L8 2Z" fill="#666"/></svg> <span id="nav-days"></span>d</span>
</nav>
<!-- ── Hero ───────────────────────────────────────────────────────────────── -->
<!-- Hero -->
<section class="hero">
<div class="hero-eyebrow">🇸🇪 Sverige-lansering</div>
<div class="hero-eyebrow"><span class="flag-se">SE</span> Sverige-lansering</div>
<h1 class="hero-title">quiXzoom Launch Tracker</h1>
<div class="countdown-ring">
@@ -533,7 +533,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<p class="hero-date">Lanseringsdatum: <strong>1 augusti 2026</strong></p>
</section>
<!-- ── Stats ──────────────────────────────────────────────────────────────── -->
<!-- Stats -->
<div class="stat-row">
<div class="stat-cell">
<div class="stat-num" id="stat-done" style="color:var(--ios-green)">0</div>
@@ -549,7 +549,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</div>
</div>
<!-- ── Content ────────────────────────────────────────────────────────────── -->
<!-- Content -->
<div class="content">
<!-- Section 1: Kritiska Milstolpar -->
@@ -665,12 +665,12 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<!-- Section 2: Revenue Readiness -->
<div class="section-header">
<h2 class="section-title">Revenue Readiness</h2>
<span class="section-badge crit" id="revenue-badge">⚠️ Ej redo</span>
<span class="section-badge crit" id="revenue-badge"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg> Ej redo</span>
</div>
<div class="card-list">
<div class="status-row">
<div class="status-icon red">💳</div>
<div class="status-icon red"></div>
<div class="status-text">
<div class="status-label">Stripe KYC</div>
<div class="status-desc">Blockar alla live-betalningar — aktivera omedelbart</div>
@@ -678,7 +678,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot red"></div>
</div>
<div class="status-row">
<div class="status-icon orange">🔄</div>
<div class="status-icon orange"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 8C2 11.3 4.7 14 8 14C10.5 14 12.7 12.5 13.7 10.3" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M14 8C14 4.7 11.3 2 8 2C5.5 2 3.3 3.5 2.3 5.7" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M13.5 7.5L14 10L11.5 9.5" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.5 8.5L2 6L4.5 6.5" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<div class="status-text">
<div class="status-label">Payment flow</div>
<div class="status-desc">Ej testbar utan Stripe KYC · Implementerat i kod</div>
@@ -686,7 +686,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot orange"></div>
</div>
<div class="status-row">
<div class="status-icon green">💰</div>
<div class="status-icon green"></div>
<div class="status-text">
<div class="status-label">Pricing konfigurerat</div>
<div class="status-desc">quiXzoom-priser satt i systemet</div>
@@ -694,7 +694,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot green"></div>
</div>
<div class="status-row">
<div class="status-icon red">🏦</div>
<div class="status-icon red"></div>
<div class="status-text">
<div class="status-label">Stripe Connect (zoomer-utbetalningar)</div>
<div class="status-desc">Väntar på KYC-godkännande</div>
@@ -712,7 +712,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="card-list">
<div class="status-row">
<div class="status-icon green">⚙️</div>
<div class="status-icon green"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="3" stroke="#666" stroke-width="1.5"/><path d="M8 2V4M8 12V14M2 8H4M12 8H14M4.3 4.3L5.7 5.7M10.3 10.3L11.7 11.7M4.3 11.7L5.7 10.3M10.3 5.7L11.7 4.3" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div>
<div class="status-text">
<div class="status-label">AAMOS backend</div>
<div class="status-desc">Live · amos.wavult.com operationell</div>
@@ -720,7 +720,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot green"></div>
</div>
<div class="status-row">
<div class="status-icon green">🌐</div>
<div class="status-icon green"></div>
<div class="status-text">
<div class="status-label">quixzoom.com</div>
<div class="status-desc">Landing page live</div>
@@ -728,7 +728,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot green"></div>
</div>
<div class="status-row">
<div class="status-icon orange">📱</div>
<div class="status-icon orange"></div>
<div class="status-text">
<div class="status-label">iOS / Android app</div>
<div class="status-desc">Under utveckling · App Store-submission ej genomförd</div>
@@ -736,7 +736,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot orange"></div>
</div>
<div class="status-row">
<div class="status-icon orange">🔑</div>
<div class="status-icon orange"></div>
<div class="status-text">
<div class="status-label">TED EU API</div>
<div class="status-desc">API-nyckel saknas · 5 min att fixa</div>
@@ -744,7 +744,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot orange"></div>
</div>
<div class="status-row">
<div class="status-icon red">🧪</div>
<div class="status-icon red"></div>
<div class="status-text">
<div class="status-label">Load-test + rollback-plan</div>
<div class="status-desc">Ej genomfört · Kritisk inför launch</div>
@@ -752,7 +752,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="status-dot red"></div>
</div>
<div class="status-row">
<div class="status-icon red">🔐</div>
<div class="status-icon red"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="7" width="10" height="7" rx="1" stroke="#666" stroke-width="1.5"/><path d="M5 7V5C5 3.3 6.3 2 8 2C9.7 2 11 3.3 11 5V7" stroke="#666" stroke-width="1.5"/><circle cx="8" cy="10.5" r="1" fill="#666"/></svg></div>
<div class="status-text">
<div class="status-label">Pentest / säkerhetsgranskning</div>
<div class="status-desc">Ej genomförd · Måste göras inför launch</div>
@@ -804,7 +804,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</div>
<div class="info-card">
<div class="info-card-icon">💡</div>
<div class="info-card-icon"></div>
<div class="info-card-text">
<strong>Nästa kritiska deadline:</strong> PR/pressrelease måste vara klar senast <strong>25 maj</strong> (3 veckor innan launch). Zoomer-rekrytering bör starta omedelbart — mål: 20+ zoomers till soft launch <strong>1 augusti</strong>.
</div>
@@ -815,7 +815,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</div><!-- /content -->
<script>
// ── Launch date ──────────────────────────────────────────────────────────
// Launch date
const LAUNCH_DATE = new Date('2026-06-15T00:00:00+02:00');
// We assume campaign started ~90 days before launch for the ring
const CAMPAIGN_START = new Date('2026-03-17T00:00:00+02:00');
@@ -851,7 +851,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
});
}
// ── Milestone state (persisted in localStorage) ───────────────────────────
// Milestone state (persisted in localStorage)
const STORAGE_KEY = 'qx_launch_milestones_v2';
function loadState() {
@@ -904,7 +904,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
applyState();
}
// ── Init ──────────────────────────────────────────────────────────────────
// Init
updateHero();
applyState();
setInterval(updateHero, 60000);