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
+38 -39
View File
@@ -55,7 +55,7 @@ body {
-webkit-font-smoothing: antialiased;
}
/* ─── NAV ─────────────────────────────────────────────────── */
/* NAV */
.nav {
position: sticky;
top: 0;
@@ -112,7 +112,7 @@ body {
.nav-search input {
width: 100%;
padding: 8px 14px 8px 36px;
border-radius: 20px;
border-radius: var(--radius-xl);
border: 1px solid var(--border);
background: var(--bg-off);
color: var(--text);
@@ -154,7 +154,7 @@ body {
font-weight: 600;
color: var(--text-secondary);
text-decoration: none;
border-radius: 5px;
border-radius: var(--radius-sm);
transition: background .12s, color .12s;
line-height: 1.4;
}
@@ -162,7 +162,7 @@ body {
.nav-az a.active { background: var(--blue); color: #fff; }
.nav-az a.empty { opacity: .25; pointer-events: none; }
/* ─── MAIN ────────────────────────────────────────────────── */
/* MAIN */
main {
max-width: 860px;
margin: 0 auto;
@@ -199,7 +199,7 @@ main {
}
.search-empty.visible { display: block; }
/* ─── LETTER SECTION ──────────────────────────────────────── */
/* LETTER SECTION */
.letter-section {
margin-bottom: 48px;
}
@@ -228,7 +228,7 @@ main {
color: var(--text-secondary);
}
/* ─── TERM CARD ───────────────────────────────────────────── */
/* TERM CARD */
.term-card {
background: var(--bg-card);
border: 1px solid var(--border);
@@ -263,7 +263,7 @@ main {
.term-category {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
border-radius: var(--radius-xl);
font-size: .75rem;
font-weight: 600;
letter-spacing: .02em;
@@ -331,7 +331,7 @@ main {
.term-related a {
display: inline-block;
padding: 3px 10px;
border-radius: 20px;
border-radius: var(--radius-xl);
font-size: .8125rem;
font-weight: 500;
color: var(--blue);
@@ -345,7 +345,7 @@ main {
color: #fff;
}
/* ─── RESPONSIVE ──────────────────────────────────────────── */
/* RESPONSIVE */
@media (max-width: 600px) {
.nav { gap: 10px; padding: 0 16px; }
.nav-az { display: none; }
@@ -358,13 +358,13 @@ main {
.nav-az { max-width: 200px; }
}
/* ─── HIGHLIGHT when anchor-targeted ─────────────────────── */
/* HIGHLIGHT when anchor-targeted */
.term-card:target {
border-color: var(--blue);
box-shadow: 0 0 0 3px rgba(0,102,255,.15);
}
/* ─── HIDDEN during search ────────────────────────────────── */
/* HIDDEN during search */
.term-card.hidden { display: none; }
.letter-section.hidden { display: none; }
</style>
@@ -423,7 +423,7 @@ main {
<div class="search-empty" id="search-empty">No terms match "<span id="search-query"></span>"</div>
<!-- ═══════════════════════════════ A ═══════════════════════════════ -->
<!-- A -->
<section class="letter-section" id="a">
<div class="letter-heading"><h2>A</h2><span class="letter-count"></span></div>
@@ -627,7 +627,7 @@ main {
</section>
<!-- ═══════════════════════════════ C ═══════════════════════════════ -->
<!-- C -->
<section class="letter-section" id="c">
<div class="letter-heading"><h2>C</h2><span class="letter-count"></span></div>
@@ -752,7 +752,7 @@ main {
</section>
<!-- ═══════════════════════════════ D ═══════════════════════════════ -->
<!-- D -->
<section class="letter-section" id="d">
<div class="letter-heading"><h2>D</h2><span class="letter-count"></span></div>
@@ -876,7 +876,7 @@ main {
</section>
<!-- ═══════════════════════════════ E ═══════════════════════════════ -->
<!-- E -->
<section class="letter-section" id="e">
<div class="letter-heading"><h2>E</h2><span class="letter-count"></span></div>
@@ -902,7 +902,7 @@ main {
</section>
<!-- ═══════════════════════════════ F ═══════════════════════════════ -->
<!-- F -->
<section class="letter-section" id="f">
<div class="letter-heading"><h2>F</h2><span class="letter-count"></span></div>
@@ -928,7 +928,7 @@ main {
</section>
<!-- ═══════════════════════════════ G ═══════════════════════════════ -->
<!-- G -->
<section class="letter-section" id="g">
<div class="letter-heading"><h2>G</h2><span class="letter-count"></span></div>
@@ -1053,7 +1053,7 @@ main {
</section>
<!-- ═══════════════════════════════ H ═══════════════════════════════ -->
<!-- H -->
<section class="letter-section" id="h">
<div class="letter-heading"><h2>H</h2><span class="letter-count"></span></div>
@@ -1078,7 +1078,7 @@ main {
</section>
<!-- ═══════════════════════════════ I ═══════════════════════════════ -->
<!-- I -->
<section class="letter-section" id="i">
<div class="letter-heading"><h2>I</h2><span class="letter-count"></span></div>
@@ -1144,7 +1144,7 @@ main {
</section>
<!-- ═══════════════════════════════ J ═══════════════════════════════ -->
<!-- J -->
<section class="letter-section" id="j">
<div class="letter-heading"><h2>J</h2><span class="letter-count"></span></div>
@@ -1170,7 +1170,7 @@ main {
</section>
<!-- ═══════════════════════════════ K ═══════════════════════════════ -->
<!-- K -->
<section class="letter-section" id="k">
<div class="letter-heading"><h2>K</h2><span class="letter-count"></span></div>
@@ -1196,7 +1196,7 @@ main {
</section>
<!-- ═══════════════════════════════ L ═══════════════════════════════ -->
<!-- L -->
<section class="letter-section" id="l">
<div class="letter-heading"><h2>L</h2><span class="letter-count"></span></div>
@@ -1221,7 +1221,7 @@ main {
</section>
<!-- ═══════════════════════════════ M ═══════════════════════════════ -->
<!-- M -->
<section class="letter-section" id="m">
<div class="letter-heading"><h2>M</h2><span class="letter-count"></span></div>
@@ -1306,7 +1306,7 @@ main {
</section>
<!-- ═══════════════════════════════ O ═══════════════════════════════ -->
<!-- O -->
<section class="letter-section" id="o">
<div class="letter-heading"><h2>O</h2><span class="letter-count"></span></div>
@@ -1411,7 +1411,7 @@ main {
</section>
<!-- ═══════════════════════════════ P ═══════════════════════════════ -->
<!-- P -->
<section class="letter-section" id="p">
<div class="letter-heading"><h2>P</h2><span class="letter-count"></span></div>
@@ -1517,7 +1517,7 @@ main {
</section>
<!-- ═══════════════════════════════ Q ═══════════════════════════════ -->
<!-- Q -->
<section class="letter-section" id="q">
<div class="letter-heading"><h2>Q</h2><span class="letter-count"></span></div>
@@ -1543,7 +1543,7 @@ main {
</section>
<!-- ═══════════════════════════════ R ═══════════════════════════════ -->
<!-- R -->
<section class="letter-section" id="r">
<div class="letter-heading"><h2>R</h2><span class="letter-count"></span></div>
@@ -1646,7 +1646,7 @@ main {
</section>
<!-- ═══════════════════════════════ S ═══════════════════════════════ -->
<!-- S -->
<section class="letter-section" id="s">
<div class="letter-heading"><h2>S</h2><span class="letter-count"></span></div>
@@ -1850,7 +1850,7 @@ main {
</section>
<!-- ═══════════════════════════════ T ═══════════════════════════════ -->
<!-- T -->
<section class="letter-section" id="t">
<div class="letter-heading"><h2>T</h2><span class="letter-count"></span></div>
@@ -1916,7 +1916,7 @@ main {
</section>
<!-- ═══════════════════════════════ V ═══════════════════════════════ -->
<!-- V -->
<section class="letter-section" id="v">
<div class="letter-heading"><h2>V</h2><span class="letter-count"></span></div>
@@ -1956,13 +1956,13 @@ main {
<a href="#payout">Payout</a>
<a href="#stripe-connect">Stripe Connect</a>
<a href="#w9-w8ben">W-9 / W-8BEN</a>
<a href="#1099-k">1099-K</a>
<a href="#1099AAk">1099-K</a>
</div>
</article>
</section>
<!-- ═══════════════════════════════ W ═══════════════════════════════ -->
<!-- W -->
<section class="letter-section" id="w">
<div class="letter-heading"><h2>W</h2><span class="letter-count"></span></div>
@@ -2018,7 +2018,7 @@ main {
</div>
<div class="term-related">
<span>Related:</span>
<a href="#1099-k">1099-K</a>
<a href="#1099AAk">1099-K</a>
<a href="#stripe-connect">Stripe Connect</a>
<a href="#vat">VAT</a>
<a href="#kyc">KYC</a>
@@ -2027,7 +2027,7 @@ main {
</section>
<!-- ═══════════════════════════════ Z ═══════════════════════════════ -->
<!-- Z -->
<section class="letter-section" id="z">
<div class="letter-heading"><h2>Z</h2><span class="letter-count"></span></div>
@@ -2073,19 +2073,19 @@ main {
</section>
<!-- ═══════════ ACTIVE TIER (alphabetical under A but listed after for content flow) ══════════ -->
<!-- ACTIVE TIER (alphabetical under A but listed after for content flow) -->
</main>
<script>
// ─── Add letter counts ────────────────────────────────────────────────
// Add letter counts
document.querySelectorAll('.letter-section').forEach(section => {
const cards = section.querySelectorAll('.term-card');
const countEl = section.querySelector('.letter-count');
if (countEl) countEl.textContent = cards.length + ' term' + (cards.length !== 1 ? 's' : '');
});
// ─── Live search ──────────────────────────────────────────────────────
// Live search
const searchInput = document.getElementById('glossary-search');
const emptyMsg = document.getElementById('search-empty');
const querySpan = document.getElementById('search-query');
@@ -2118,7 +2118,7 @@ searchInput.addEventListener('input', () => {
emptyMsg.classList.toggle('visible', !anyVisible);
});
// ─── AZ active letter tracking ──────────────────────────────────────
// AZ active letter tracking
const azLinks = document.querySelectorAll('.nav-az a');
const sections = Array.from(document.querySelectorAll('.letter-section[id]'));
@@ -2223,7 +2223,6 @@ sections.forEach(s => observer.observe(s));
<nav style="display:flex;gap:20px;flex-wrap:wrap">
<a href="/" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">For Zoomers</a>
<a href="/for-organisations/" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">For Organisations</a>
<a href="/developer" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Developer</a>
<a href="/pricing" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Pricing</a>
<a href="/about" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">About</a>
<a href="/status" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Status</a>