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
+37 -37
View File
@@ -59,7 +59,7 @@
a { color: inherit; text-decoration: none; }
/* ── NAV ── */
/* NAV */
nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
display: flex; align-items: center; justify-content: space-between;
@@ -110,7 +110,7 @@
.btn-outline:hover { background: rgba(0,0,0,0.04); transform: translateY(-1px); }
.btn-lg { padding: 14px 30px; font-size: 16px; border-radius: var(--radius-md); }
/* ── HERO ── */
/* HERO */
.hero {
background: var(--bg-dark);
padding: 140px 24px 96px;
@@ -160,7 +160,7 @@
margin: 0 auto 48px;
}
/* ── PIPELINE OVERVIEW ── */
/* PIPELINE OVERVIEW */
.pipeline {
background: var(--bg-dark);
padding: 0 24px 80px;
@@ -197,7 +197,7 @@
.pipeline-num {
width: 56px; height: 56px;
border-radius: 50%;
border-radius: var(--radius-full);
background: rgba(0,102,255,0.1);
border: 2px solid var(--blue);
display: flex; align-items: center; justify-content: center;
@@ -215,7 +215,7 @@
line-height: 1.3;
}
/* ── SECTION COMMON ── */
/* SECTION COMMON */
.container { max-width: 1140px; margin: 0 auto; }
.section-label {
@@ -240,7 +240,7 @@
line-height: 1.7;
}
/* ── LAYER SECTIONS ── */
/* LAYER SECTIONS */
.layer {
padding: 80px 24px;
border-bottom: 1px solid #1a1a1a;
@@ -276,7 +276,7 @@
.layer-num-badge {
width: 36px; height: 36px;
border-radius: 50%;
border-radius: var(--radius-full);
background: var(--blue);
color: #1d1d1f;
font-size: 14px;
@@ -308,7 +308,7 @@
line-height: 1.7;
}
/* ── FEATURE LIST ── */
/* FEATURE LIST */
.feature-list {
display: flex;
flex-direction: column;
@@ -358,7 +358,7 @@
line-height: 1.55;
}
/* ── OUTPUT SECTION ── */
/* OUTPUT SECTION */
.output-section {
background: var(--bg-dark);
padding: 96px 24px;
@@ -426,7 +426,7 @@
font-style: italic;
}
/* ── DIMENSIONS BAND ── */
/* DIMENSIONS BAND */
.dimensions-band {
background: #e8f0fe;
padding: 64px 24px;
@@ -469,7 +469,7 @@
.dim-name { font-size: 13px; font-weight: 700; color: #1d1d1f; line-height: 1.3; }
.dim-scale { font-size: 11px; color: rgba(255,255,255,0.75); margin-top: 4px; }
/* ── CTA BAND ── */
/* CTA BAND */
.cta-band {
background: var(--blue);
padding: 72px 24px;
@@ -503,7 +503,7 @@
transform: translateY(-1px);
}
/* ── FOOTER ── */
/* FOOTER */
footer {
background: var(--bg-dark);
border-top: 1px solid var(--border);
@@ -526,14 +526,14 @@
.footer-social {
width: 32px; height: 32px;
border: 1px solid var(--border);
border-radius: 8px;
border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
transition: border-color 0.2s, background 0.2s;
}
.footer-social:hover { border-color: rgba(0,0,0,0.25); background: rgba(0,0,0,0.04); }
.footer-tagline { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
/* ── PDF DOWNLOAD BUTTON ── */
/* PDF DOWNLOAD BUTTON */
.pdf-download-btn {
display: inline-flex; align-items: center; gap: 10px;
background: rgba(0,0,0,0.08);
@@ -553,7 +553,7 @@
}
.pdf-download-btn svg { flex-shrink: 0; }
/* ── DATA QUALITY ── */
/* DATA QUALITY */
.dq-section { background: #f8f9fc; padding: 96px 24px; }
.dq-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 48px; }
.dq-card {
@@ -579,11 +579,11 @@
}
.dq-standards li::before {
content: '';
width: 6px; height: 6px; border-radius: 50%;
width: 6px; height: 6px; border-radius: var(--radius-full);
background: var(--blue); flex-shrink: 0; margin-top: 6px;
}
/* ── CONTRADICTION FLOW DIAGRAM ── */
/* CONTRADICTION FLOW DIAGRAM */
.flow-section { background: #f5f5f7; padding: 96px 24px; }
.flow-diagram {
max-width: 820px; margin: 56px auto 0;
@@ -600,7 +600,7 @@
.flow-node.flow-rank { border-color: rgba(239,68,68,0.35); background: rgba(239,68,68,0.06); }
.flow-node.flow-redteam { border-color: rgba(139,92,246,0.4); background: rgba(139,92,246,0.07); }
.flow-node.flow-output { border-color: rgba(34,197,94,0.4); background: rgba(34,197,94,0.07); }
.flow-node-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.flow-node-icon { width: 40px; height: 40px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.flow-node-text h4 { font-size: 14px; font-weight: 700; color: #1d1d1f; margin-bottom: 3px; }
.flow-node-text p { font-size: 12px; color: rgba(255,255,255,0.75); line-height: 1.5; }
.flow-arrow { display: flex; align-items: center; justify-content: center; height: 32px; }
@@ -619,7 +619,7 @@
.flow-split-arrow::before { right: calc(50% + 8px); }
.flow-split-arrow::after { left: calc(50% + 8px); }
/* ── RESPONSIVE ── */
/* RESPONSIVE */
@media (max-width: 900px) {
nav { padding: 0 20px; }
.nav-links { display: none; }
@@ -699,7 +699,7 @@
<section class="hero">
<div class="hero-eyebrow">Methodology</div>
<h1>How Landvex works.</h1>
<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:12px 18px;margin:16px 0;font-size:.875rem;color:#92400e;"><strong>Pre-launch:</strong> All methodology documentation describes the system as designed for August 2026 launch. No live intelligence is being generated or sold at this time.</div>
<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius: var(--radius-md);padding:12px 18px;margin:16px 0;font-size:.875rem;color:#92400e;"><strong>Pre-launch:</strong> All methodology documentation describes the system as designed for August 2026 launch. No live intelligence is being generated or sold at this time.</div>
<p class="hero-sub">Five interlocked layers — from global field observation to verified intelligence. Every output is sourced, explained, and challenged before delivery.</p>
<div style="display:flex;gap:14px;flex-wrap:wrap;justify-content:center;">
<a class="btn btn-lg" href="https://www.landvex.com/#contact">Request a briefing &rarr;</a>
@@ -734,7 +734,7 @@
<h3>Red Team<br>Analysis</h3>
</div>
<div class="pipeline-step">
<div class="pipeline-num"></div>
<div class="pipeline-num"><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></div>
<h3>Verified<br>Output</h3>
</div>
</div>
@@ -744,12 +744,12 @@
<!-- PDF DOWNLOAD BLOCK -->
<section style="padding:0 24px;background:#fff;">
<div style="max-width:1140px;margin:0 auto;">
<div style="background:var(--blue);border-radius:16px;padding:32px 40px;display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;margin:48px 0">
<div style="background:var(--blue);border-radius: var(--radius-lg);padding:32px 40px;display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;margin:48px 0">
<div>
<h3 style="color: #1d1d1f;font-size:1.25rem;margin-bottom:6px">Landvex Intelligence Methodology</h3>
<p style="color:rgba(255,255,255,.75);font-size:.9375rem">8-page technical overview. How field observations become decision intelligence.</p>
</div>
<a href="/methodology/landvex-methodology-2026.pdf" style="background:#fff;color:var(--blue);font-weight:700;padding:14px 28px;border-radius:10px;text-decoration:none;white-space:nowrap;flex-shrink:0">
<a href="/methodology/landvex-methodology-2026.pdf" style="background:#fff;color:var(--blue);font-weight:700;padding:14px 28px;border-radius: var(--radius-md);text-decoration:none;white-space:nowrap;flex-shrink:0">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;vertical-align:middle"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Download PDF
</a>
@@ -901,14 +901,14 @@
</div>
<div class="feature-list">
<div class="feature-item">
<div class="feature-icon">🏙️</div>
<div class="feature-icon"></div>
<div>
<h4>Observations aggregated into district / city / region scores</h4>
<p>Multiple observations from multiple contributors covering the same area are weighted, de-duplicated, and aggregated into a single composite score per dimension per area.</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">🔄</div>
<div class="feature-icon"><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>
<h4>Scores updated continuously as new observations arrive</h4>
<p>There is no batch cadence. Every validated observation triggers a score recalculation for the relevant area. Intelligence is always as fresh as the most recent field data.</p>
@@ -938,21 +938,21 @@
</div>
<div class="feature-list">
<div class="feature-item" style="background:rgba(0,0,0,0.04); border-color:rgba(0,0,0,0.08);">
<div class="feature-icon">🏛️</div>
<div class="feature-icon"></div>
<div>
<h4 style="color: #ffffff">Official data ingested continuously</h4>
<p style="color:rgba(255,255,255,0.75)">National statistics, municipal open data, land registries, and transport authorities are continuously ingested and indexed against covered geographies.</p>
</div>
</div>
<div class="feature-item" style="background:rgba(0,0,0,0.04); border-color:rgba(0,0,0,0.08);">
<div class="feature-icon">⚖️</div>
<div class="feature-icon"></div>
<div>
<h4 style="color: #ffffff">Every official claim compared against observed reality</h4>
<p style="color:rgba(255,255,255,0.75)">For every official data point covering an area with observation coverage, the Contradiction Engine runs an automated comparison. Supporting evidence and contradicting evidence are catalogued separately.</p>
</div>
</div>
<div class="feature-item" style="background:rgba(0,0,0,0.04); border-color:rgba(0,0,0,0.08);">
<div class="feature-icon">🚩</div>
<div class="feature-icon"></div>
<div>
<h4 style="color: #ffffff">Contradictions flagged, ranked by severity and economic impact</h4>
<p style="color:rgba(255,255,255,0.75)">Not all contradictions are equal. The engine ranks flagged conflicts by how severe the discrepancy is, and by the estimated economic significance of the affected area or asset class.</p>
@@ -982,7 +982,7 @@
</div>
<div class="feature-list">
<div class="feature-item">
<div class="feature-icon">⚔️</div>
<div class="feature-icon"></div>
<div>
<h4>Every major conclusion challenged by a second model</h4>
<p>The primary analysis model produces a conclusion. A separate red team model, given the same evidence, is tasked with finding holes, alternative explanations, and disconfirming observations. The conclusion is only finalized after this adversarial review.</p>
@@ -996,14 +996,14 @@
</div>
</div>
<div class="feature-item">
<div class="feature-icon"></div>
<div class="feature-icon"></div>
<div>
<h4>Contradicting evidence listed</h4>
<p>Contradicting evidence is not suppressed. Every conclusion is delivered alongside the evidence that pushed against it. Where the red team found weaknesses, those are explicitly documented.</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">📏</div>
<div class="feature-icon"></div>
<div>
<h4>Uncertainty quantified</h4>
<p>Confidence levels are expressed numerically, not as qualitative hedges. Clients know not just what Landvex concludes, but how certain that conclusion is and why.</p>
@@ -1028,7 +1028,7 @@
<div class="cite-note">Every score cites contributing observations and source data.</div>
</div>
<div class="output-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
<div class="output-icon">🚩</div>
<div class="output-icon"></div>
<h3>Contradiction Reports</h3>
<p>Where observed reality conflicts with official data or investment thesis. Ranked by severity and economic significance. Evidence on both sides included.</p>
<div class="cite-note">Includes confidence score and source breakdown.</div>
@@ -1046,13 +1046,13 @@
<div class="cite-note">Uncertainty ranges included with every forecast.</div>
</div>
<div class="output-card" style="cursor:pointer;" onclick="location.href='#contact'" role="button" tabindex="0">
<div class="output-icon">📝</div>
<div class="output-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>
<h3>Executive Summaries</h3>
<p>Decision-ready narratives structured around the client's specific question — not generic reports. Concise, direct, and directly linked to the decision being made.</p>
<div class="cite-note">Supporting data attached; key sources highlighted.</div>
</div>
<div class="output-card" style="cursor:pointer;border-color: rgba(0,102,255,0.25); background: rgba(0,102,255,0.06);" onclick="location.href='#contact'" role="button" tabindex="0">
<div class="output-icon" style="background: rgba(0,102,255,0.2); border-color: rgba(0,102,255,0.4);">📌</div>
<div class="output-icon" style="background: rgba(0,102,255,0.2); border-color: rgba(0,102,255,0.4);"></div>
<h3>All outputs cite sources<br>and explain reasoning</h3>
<p>There is no black box. Every Landvex output tells you what it concluded, what it was based on, what pushed against it, and how confident the conclusion is.</p>
<div class="cite-note">Because intelligence you can't verify is just opinion.</div>
@@ -1247,7 +1247,7 @@
it analyses, scores and flags discrepancies in what has been observed.
</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:32px">
<div style="background:rgba(0,0,0,.05);border-radius:10px;padding:18px 20px">
<div style="background:rgba(0,0,0,.05);border-radius: var(--radius-md);padding:18px 20px">
<div style="font-size:.8rem;font-weight:700;color:rgba(255,255,255,.75);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">What AMOS does</div>
<ul style="font-size:.875rem;color:rgba(255,255,255,.75);line-height:1.7;padding-left:16px;margin:0">
<li>Cross-references field observations with official statistics</li>
@@ -1256,7 +1256,7 @@
<li>Generates trend signals from change patterns in field data</li>
</ul>
</div>
<div style="background:rgba(0,0,0,.05);border-radius:10px;padding:18px 20px">
<div style="background:rgba(0,0,0,.05);border-radius: var(--radius-md);padding:18px 20px">
<div style="font-size:.8rem;font-weight:700;color:rgba(255,255,255,.75);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">What AMOS does not do</div>
<ul style="font-size:.875rem;color:rgba(255,255,255,.75);line-height:1.7;padding-left:16px;margin:0">
<li>Generate or invent observations not in field data</li>
@@ -1266,7 +1266,7 @@
</ul>
</div>
</div>
<div style="background:rgba(0,0,0,.06);border-radius:10px;padding:20px 24px;border-left:3px solid #f59e0b">
<div style="background:rgba(0,0,0,.06);border-radius: var(--radius-md);padding:20px 24px;border-left:3px solid #f59e0b">
<div style="font-size:.8rem;font-weight:700;color:rgba(255,255,255,.75);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">AI Red Team</div>
<p style="font-size:.875rem;color:rgba(255,255,255,.7);line-height:1.65;margin:0">
Every AMOS contradiction flag is independently challenged by a second model before delivery.