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
+13 -13
View File
@@ -15,14 +15,14 @@ html,body{min-height:100%;font-family:-apple-system,BlinkMacSystemFont,'SF Pro T
#app{max-width:960px;margin:0 auto;padding:16px}
h1{font-size:22px;font-weight:800;letter-spacing:-.5px;margin-bottom:4px}
.sub{font-size:12px;color:var(--t2);margin-bottom:24px}
#drop-zone{border:2px dashed rgba(0,0,0,0.15);border-radius:12px;padding:40px;text-align:center;cursor:pointer;transition:border-color 0.2s;margin-bottom:20px}
#drop-zone{border:2px dashed rgba(0,0,0,0.15);border-radius: var(--radius-md);padding:40px;text-align:center;cursor:pointer;transition:border-color 0.2s;margin-bottom:20px}
#drop-zone:hover,#drop-zone.drag{border-color:var(--accent3)}
#drop-zone p{color:var(--t2);font-size:13px}
#preview-wrap{display:none;position:relative;margin-bottom:16px}
#preview{max-width:100%;border-radius:10px;display:block}
#preview{max-width:100%;border-radius: var(--radius-md);display:block}
#canvas{position:absolute;top:0;left:0;cursor:crosshair}
#labels-panel{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;margin-bottom:16px}
.label-btn{padding:8px 12px;border-radius:8px;border:1px solid var(--border);background:var(--s2);cursor:pointer;font-size:12px;font-weight:600;color:var(--t2);transition:all 0.12s;display:flex;align-items:center;gap:7px}
.label-btn{padding:8px 12px;border-radius: var(--radius-md);border:1px solid var(--border);background:var(--s2);cursor:pointer;font-size:12px;font-weight:600;color:var(--t2);transition:all 0.12s;display:flex;align-items:center;gap:7px}
.label-btn:hover{background:var(--s3);color:var(--text)}
.label-btn.active{border-color:currentColor}
.label-btn[data-cat="electrical_hazard"].active{color:#f87171;background:rgba(248,113,113,0.1)}
@@ -37,22 +37,22 @@ h1{font-size:22px;font-weight:800;letter-spacing:-.5px;margin-bottom:4px}
.label-btn[data-cat="infrastructure_ok"].active{color:#4ade80;background:rgba(74,222,128,0.1)}
#scores{display:grid;grid-template-columns:1fr;gap:8px;margin-bottom:16px}
@media(min-width:560px){#scores{grid-template-columns:1fr 1fr 1fr}}
.score-group{background:var(--s2);border-radius:8px;padding:10px 12px}
.score-group{background:var(--s2);border-radius: var(--radius-md);padding:10px 12px}
.score-group label{font-size:9px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--t2);display:block;margin-bottom:6px}
.score-group input[type=range]{width:100%;accent-color:var(--accent3)}
.score-val{font-size:14px;font-weight:800;font-family:monospace;color:var(--accent3)}
#boxes-list{margin-bottom:16px;font-size:11px;color:var(--t2)}
.box-item{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--s2);border-radius:5px;margin-bottom:4px}
.box-item{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--s2);border-radius: var(--radius-sm);margin-bottom:4px}
.box-del{color:#f87171;cursor:pointer;font-size:14px;background:none;border:none;padding:0 4px}
.btn{padding:9px 20px;border-radius:8px;border:none;cursor:pointer;font-size:13px;font-weight:700}
.btn{padding:9px 20px;border-radius: var(--radius-md);border:none;cursor:pointer;font-size:13px;font-weight:700}
.btn-primary{background:var(--accent);color: #1d1d1f}
.btn-secondary{background:var(--s2);color:var(--text);border:1px solid var(--border)}
#actions{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}
#output{background:var(--s2);border-radius:8px;padding:14px;font-size:11px;font-family:monospace;white-space:pre-wrap;color:#4ade80;display:none;max-height:300px;overflow-y:auto}
#output{background:var(--s2);border-radius: var(--radius-md);padding:14px;font-size:11px;font-family:monospace;white-space:pre-wrap;color:#4ade80;display:none;max-height:300px;overflow-y:auto}
#file-input{display:none}
.contradiction-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:16px;background:var(--s2);border-radius:8px;padding:10px 12px}
.contradiction-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:16px;background:var(--s2);border-radius: var(--radius-md);padding:10px 12px}
.contradiction-row label{font-size:11px;font-weight:600;color:var(--t2);flex:1}
.contradiction-row select{background:var(--s3);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px}
.contradiction-row select{background:var(--s3);color:var(--text);border:1px solid var(--border);border-radius: var(--radius-sm);padding:4px 8px;font-size:11px}
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
@@ -222,10 +222,10 @@ h1{font-size:22px;font-weight:800;letter-spacing:-.5px;margin-bottom:4px}
<div id="actions">
<button class="btn btn-primary" onclick="exportLabel()">Export JSON</button>
<button class="btn btn-secondary" onclick="clearAll()">Clear</button>
<input type="text" id="lat-input" placeholder="lat" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px">
<input type="text" id="lng-input" placeholder="lng" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px">
<input type="text" id="city-input" placeholder="city" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px">
<input type="text" id="district-input" placeholder="district" style="flex:1;min-width:80px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px">
<input type="text" id="lat-input" placeholder="lat" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius: var(--radius-sm);color:var(--text);font-size:12px">
<input type="text" id="lng-input" placeholder="lng" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius: var(--radius-sm);color:var(--text);font-size:12px">
<input type="text" id="city-input" placeholder="city" style="flex:1;min-width:70px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius: var(--radius-sm);color:var(--text);font-size:12px">
<input type="text" id="district-input" placeholder="district" style="flex:1;min-width:80px;padding:8px;background:var(--s2);border:1px solid var(--border);border-radius: var(--radius-sm);color:var(--text);font-size:12px">
</div>
<pre id="output"></pre>