Files
boc/quixzoom-landing-fixed/launch-checklist.html
T
Bernt 48ea61cdcc docs: add quixzoom-auth-core product to AAMOS
- Product documentation in docs/products/
- Updated MEMORY.md with product info
- quiXzoom Auth Core as AAMOS Identity product
2026-07-14 09:58:53 +00:00

1068 lines
43 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Launch Readiness — quiXzoom 1 augusti 2026</title>
<style>
/* iOS 26 / Apple Native — Light Mode */
:root {
--bg: #f2f2f7;
--bg-card: #ffffff;
--bg-secondary: #f2f2f7;
--separator: rgba(60,60,67,0.18);
--label: #000000;
--label-secondary: rgba(60,60,67,0.6);
--label-tertiary: rgba(60,60,67,0.3);
--tint: #007AFF;
--green: #34C759;
--red: #FF3B30;
--yellow: #FF9500;
--orange: #FF9500;
--purple: #AF52DE;
--fill: rgba(120,120,128,0.12);
--fill-secondary: rgba(120,120,128,0.08);
--radius: 12px;
--radius-lg: 16px;
--radius-sm: 8px;
--font: -apple-system, "SF Pro Display", "Helvetica Neue", sans-serif;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--label);
min-height: 100dvh;
padding-top: calc(var(--safe-top) + 0px);
padding-bottom: calc(var(--safe-bottom) + 60px);
}
/* Nav Bar */
.nav-bar {
position: sticky;
top: 0;
z-index: 100;
background: rgba(242,242,247,0.88);
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
border-bottom: 0.5px solid var(--separator);
padding: 14px 20px 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-title { font-size: 17px; font-weight: 600; letter-spacing: -0.4px; }
.nav-badge {
background: var(--tint);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 4px 12px;
border-radius: var(--radius-xl);
transition: background 0.3s;
}
.nav-badge.ready { background: var(--green); }
.nav-badge.warn { background: var(--orange); }
/* Page Header */
.page-header { padding: 24px 20px 8px; }
.page-title { font-size: 34px; font-weight: 700; letter-spacing: -0.5px; line-height: 1.1; }
.page-sub { font-size: 15px; color: var(--label-secondary); margin-top: 4px; }
/* Countdown Hero */
.countdown-hero {
margin: 16px 16px 0;
background: linear-gradient(135deg, #001E50 0%, #0040A8 100%);
border-radius: var(--radius-lg);
padding: 22px 20px 20px;
box-shadow: 0 4px 20px rgba(0,30,80,0.3);
color: #fff;
}
.countdown-label {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: rgba(255,255,255,0.75);
margin-bottom: 12px;
}
.countdown-blocks {
display: flex;
gap: 10px;
align-items: flex-start;
}
.cd-block {
flex: 1;
background: rgba(255,255,255,0.12);
border-radius: var(--radius-md);
padding: 12px 6px 10px;
text-align: center;
}
.cd-num {
font-size: 32px;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -1px;
line-height: 1;
}
.cd-unit {
font-size: 11px;
font-weight: 500;
color: rgba(255,255,255,0.75);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 4px;
}
.cd-sep {
font-size: 26px;
font-weight: 700;
color: rgba(255,255,255,0.75);
margin-top: 10px;
}
.countdown-launch {
margin-top: 14px;
font-size: 13px;
color: rgba(255,255,255,0.8);
text-align: center;
}
.countdown-launch strong { color: #fff; font-weight: 700; }
/* Progress Hero */
.progress-hero {
margin: 12px 16px 0;
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: 18px 20px 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.progress-row {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 12px;
}
.progress-pct {
font-size: 42px;
font-weight: 700;
letter-spacing: -1px;
line-height: 1;
color: var(--tint);
}
.progress-label { font-size: 14px; color: var(--label-secondary); margin-top: 2px; }
.progress-right { text-align: right; }
.deadline-label { font-size: 12px; color: var(--label-secondary); text-transform: uppercase; letter-spacing: 0.5px; }
.deadline-date { font-size: 20px; font-weight: 700; letter-spacing: -0.4px; }
.days-left { font-size: 13px; color: var(--orange); font-weight: 600; margin-top: 2px; }
.days-left.green { color: var(--green); }
.progress-bar-bg {
background: var(--fill);
border-radius: var(--radius-sm);
height: 10px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
border-radius: var(--radius-sm);
background: linear-gradient(90deg, var(--tint), #5AC8FA);
transition: width 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Stat Row */
.stat-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
padding: 12px 16px 0;
}
.stat-mini {
background: var(--bg-card);
border-radius: var(--radius);
padding: 12px 10px;
text-align: center;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.stat-mini-num {
font-size: 22px;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -0.4px;
}
.stat-mini-num.green { color: var(--green); }
.stat-mini-num.red { color: var(--red); }
.stat-mini-num.yellow { color: var(--yellow); }
.stat-mini-num.blue { color: var(--tint); }
.stat-mini-lbl {
font-size: 10px;
font-weight: 500;
color: var(--label-secondary);
text-transform: uppercase;
letter-spacing: 0.3px;
margin-top: 2px;
}
/* Final Verification Banner */
.verify-banner {
margin: 22px 16px 0;
background: rgba(52,199,89,0.10);
border: 1.5px solid rgba(52,199,89,0.35);
border-radius: var(--radius-lg);
padding: 14px 16px;
}
.verify-banner-title {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--green);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 6px;
}
.verify-row {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 0;
border-bottom: 0.5px solid rgba(52,199,89,0.2);
}
.verify-row:last-child { border-bottom: none; padding-bottom: 0; }
.verify-dot {
width: 20px; height: 20px;
border-radius: var(--radius-full);
display: flex; align-items: center; justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
}
.vdot-ok { background: var(--green); color: #fff; }
.vdot-warn { background: var(--orange); color: #fff; }
.vdot-err { background: var(--red); color: #fff; }
.verify-text { font-size: 14px; font-weight: 500; flex: 1; }
.verify-endpoint {
font-size: 11px;
font-family: "SF Mono", "Menlo", monospace;
color: var(--label-secondary);
background: var(--fill);
padding: 2px 6px;
border-radius: var(--radius-sm);
}
/* Category Section */
.cat-section { margin-top: 28px; }
.cat-header {
display: flex;
align-items: center;
gap: 8px;
padding: 0 20px 10px;
}
.cat-icon {
width: 28px; height: 28px;
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.cat-name {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--label-secondary);
flex: 1;
}
.cat-progress {
font-size: 12px;
font-weight: 600;
color: var(--label-tertiary);
}
.cat-progress-bar {
height: 3px;
border-radius: var(--radius-sm);
background: var(--fill);
margin: 0 20px 8px;
overflow: hidden;
}
.cat-progress-fill {
height: 100%;
border-radius: var(--radius-sm);
transition: width 0.6s ease;
}
/* Checklist */
.checklist {
background: var(--bg-card);
border-radius: var(--radius-lg);
margin: 0 16px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.check-item {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 14px 16px;
border-bottom: 0.5px solid var(--separator);
transition: background 0.12s;
-webkit-user-select: none;
user-select: none;
}
.check-item:last-child { border-bottom: none; }
.check-item:active { background: var(--fill); }
.check-circle {
width: 24px; height: 24px;
border-radius: var(--radius-full);
flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
font-size: 13px;
font-weight: 700;
margin-top: 1px;
transition: all 0.2s;
}
.check-circle.done { background: var(--green); color: #fff; }
.check-circle.in-prog { background: rgba(0,122,255,0.15); border: 2px solid var(--tint); color: var(--tint); }
.check-circle.blocked { background: rgba(255,59,48,0.12); border: 2px solid var(--red); color: var(--red); }
.check-circle.pending { border: 2px solid var(--separator); color: var(--label-tertiary); background: transparent; }
.check-content { flex: 1; min-width: 0; }
.check-title {
font-size: 15px;
font-weight: 500;
color: var(--label);
line-height: 1.3;
}
.check-title.line-through { text-decoration: line-through; color: var(--label-secondary); }
.check-note {
font-size: 12px;
color: var(--label-tertiary);
margin-top: 3px;
font-style: italic;
}
.check-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 5px;
align-items: center;
}
.meta-chip {
font-size: 12px;
font-weight: 500;
padding: 2px 8px;
border-radius: var(--radius-sm);
background: var(--fill);
color: var(--label-secondary);
}
.meta-deadline {
font-size: 12px;
font-weight: 500;
color: var(--label-tertiary);
}
.meta-deadline.urgent { color: var(--red); font-weight: 600; }
.meta-deadline.soon { color: var(--orange); font-weight: 600; }
.check-right {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.owner-badge {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: var(--radius-sm);
background: var(--fill);
color: var(--label-secondary);
}
.status-tag {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: var(--radius-sm);
}
.tag-done { background: rgba(52,199,89,0.15); color: var(--green); }
.tag-inprog { background: rgba(0,122,255,0.12); color: var(--tint); }
.tag-blocked { background: rgba(255,59,48,0.12); color: var(--red); }
.tag-pending { background: var(--fill); color: var(--label-tertiary); }
/* Footer */
.footer {
text-align: center;
padding: 32px 20px 12px;
font-size: 12px;
color: var(--label-tertiary);
line-height: 1.6;
}
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
<style id="wcag-contrast-enforcement">
/* AAMOS GLOBAL CONTRAST ENFORCEMENT — WCAG AA (4.5:1 minimum) */
/* Regel: Ingen text under 70% opacity. Aldrig. */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
/* Fix common low-contrast patterns */
[style*="rgba(60,60,67,0.4)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(60,60,67,0.3)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(255,255,255,0.4)"] { color: rgba(255,255,255,0.72) !important; }
[style*="rgba(255,255,255,0.3)"] { color: rgba(255,255,255,0.72) !important; }
[style*="opacity: 0.4"] { opacity: 0.72 !important; }
[style*="opacity: 0.3"] { opacity: 0.72 !important; }
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
<script type="module" src="https://auth.quixzoom.com/site-auth-snippet.js"></script>
</head>
<body>
<h1 style="position:absolute;left:-9999px;">Launch Checklist for quiXzoom</h1>
<!-- Nav Bar -->
<div class="nav-bar">
<div class="nav-title"><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> Launch Readiness</div>
<div class="nav-badge" id="navBadge">Laddar…</div>
</div>
<!-- Page Header -->
<div class="page-header">
<div class="page-title">quiXzoom Sverige</div>
<div class="page-sub">Final Launch Check · <span id="totalItems"></span> items · 1 augusti 2026</div>
</div>
<!-- Countdown Hero -->
<div class="countdown-hero">
<div class="countdown-label">Tid till launch</div>
<div class="countdown-blocks">
<div class="cd-block">
<div class="cd-num" id="cdDays">--</div>
<div class="cd-unit">Dagar</div>
</div>
<div class="cd-sep">:</div>
<div class="cd-block">
<div class="cd-num" id="cdHours">--</div>
<div class="cd-unit">Timmar</div>
</div>
<div class="cd-sep">:</div>
<div class="cd-block">
<div class="cd-num" id="cdMins">--</div>
<div class="cd-unit">Minuter</div>
</div>
<div class="cd-sep">:</div>
<div class="cd-block">
<div class="cd-num" id="cdSecs">--</div>
<div class="cd-unit">Sekunder</div>
</div>
</div>
<div class="countdown-launch">
Launch: <strong>1 augusti 2026 kl. 09:00 CEST</strong>
</div>
</div>
<!-- Progress Hero -->
<div class="progress-hero">
<div class="progress-row">
<div class="progress-left">
<div class="progress-pct" id="heroPct">0%</div>
<div class="progress-label" id="heroLabel">Laddar…</div>
</div>
<div class="progress-right">
<div class="deadline-label">Go / No-Go</div>
<div class="deadline-date">1 augusti</div>
<div class="days-left" id="daysLeft"></div>
</div>
</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill" id="heroBar" style="width:0%"></div>
</div>
</div>
<!-- Stat Row -->
<div class="stat-row">
<div class="stat-mini">
<div class="stat-mini-num green" id="cntDone"></div>
<div class="stat-mini-lbl">Klara</div>
</div>
<div class="stat-mini">
<div class="stat-mini-num blue" id="cntInprog"></div>
<div class="stat-mini-lbl">Pågår</div>
</div>
<div class="stat-mini">
<div class="stat-mini-num red" id="cntBlocked"></div>
<div class="stat-mini-lbl">Blockade</div>
</div>
<div class="stat-mini">
<div class="stat-mini-num yellow" id="cntPending"></div>
<div class="stat-mini-lbl">Pending</div>
</div>
</div>
<!-- Final Verification Section -->
<div style="margin:22px 16px 0">
<div style="font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:0.6px;color:var(--label-secondary);padding:0 4px 8px;display:flex;align-items:center;gap:6px;">
<span style="font-size:16px"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span> Final Verification — W11-QUIXZOOM-LAUNCH
</div>
</div>
<div class="verify-banner">
<div class="verify-banner-title"><span><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="#666" stroke-width="2"/><path d="M11 11L14 14" stroke="#666" stroke-width="2" stroke-linecap="round"/></svg></span> 5 kritiska kontrollpunkter</div>
<div class="verify-row">
<div class="verify-dot vdot-ok"><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>
<div class="verify-text">Alla quiXzoom API-endpoints svarar</div>
<div class="verify-endpoint">14 moduler · /api/quixzoom/*</div>
</div>
<div class="verify-row">
<div class="verify-dot vdot-ok"><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>
<div class="verify-text">Zoomer-registrering end-to-end</div>
<div class="verify-endpoint">POST /zoomers/register → KYC → godkänd</div>
</div>
<div class="verify-row">
<div class="verify-dot vdot-ok"><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>
<div class="verify-text">Mission create / accept / complete-flöde</div>
<div class="verify-endpoint">draft → open → active → completed</div>
</div>
<div class="verify-row">
<div class="verify-dot vdot-ok"><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>
<div class="verify-text">Payout-automation aktiv (Stripe Connect)</div>
<div class="verify-endpoint">POST /payouts/process · 85% Zoomer / 15% plattform</div>
</div>
<div class="verify-row">
<div class="verify-dot vdot-ok"><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>
<div class="verify-text">DAC7 XML-export fungerar</div>
<div class="verify-endpoint">GET /compliance/dac7/report?format=xml · Skatteverket-schema</div>
</div>
</div>
<!-- Checklist (rendered by JS) -->
<div id="checklistContainer"></div>
<div class="footer">
quiXzoom Launch Readiness Final Check<br>
Uppdaterat <span id="footerTs"></span> · W11-QUIXZOOM-LAUNCH
</div>
<script>
const LAUNCH = new Date('2026-06-15T09:00:00+02:00');
// Countdown
function pad(n) { return String(n).padStart(2, '0'); }
function tickCountdown() {
const diff = LAUNCH - new Date();
if (diff <= 0) {
document.getElementById('cdDays').textContent = '00';
document.getElementById('cdHours').textContent = '00';
document.getElementById('cdMins').textContent = '00';
document.getElementById('cdSecs').textContent = '00';
return;
}
const totalSecs = Math.floor(diff / 1000);
const days = Math.floor(totalSecs / 86400);
const hours = Math.floor((totalSecs % 86400) / 3600);
const mins = Math.floor((totalSecs % 3600) / 60);
const secs = totalSecs % 60;
document.getElementById('cdDays').textContent = pad(days);
document.getElementById('cdHours').textContent = pad(hours);
document.getElementById('cdMins').textContent = pad(mins);
document.getElementById('cdSecs').textContent = pad(secs);
}
tickCountdown();
setInterval(tickCountdown, 1000);
const CATEGORIES = [
{
id: 'verification',
name: 'API Verification',
icon: '<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>',
color: '#007AFF',
items: [
{ title: 'missions.mjs — create/accept/complete lifecycle', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /missions + PATCH /:id/status · statusmaskin verifierad' },
{ title: 'registry.mjs — Zoomer-registrering E2E', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /zoomers/register → KYC-steg 1-6 → verified' },
{ title: 'payouts.mjs — Stripe Connect batch-utbetalning', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /payouts/batch · 15% platform fee · min 100 SEK trösk' },
{ title: 'dac7.mjs — XML-export Skatteverket-schema', owner: 'Legal', status: 'done', deadline: 'Juni 10', note: 'GET /compliance/dac7/report?format=xml · DAC7 Art. 8ac DAC' },
{ title: 'matching.mjs — Haversine-matchning live', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /missions/:id/find-zoomers · avstånd+rating+utrustning' },
{ title: 'ratings.mjs — exponential decay weighting', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: '30-dagars halveringstid · minimum 3 ratings för publik score' },
{ title: 'kyc-metrics.mjs — /kyc/metrics endpoint', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'Var blockad 2026-05-20 → fixad och verifierad' },
{ title: 'push-notifications.mjs — APNs Production Certificate', owner: 'iOS', status: 'in-prog', deadline: 'Juni 13', urgent: true, note: 'Installerat på ny Mac efter SSH-access löst' },
{ title: 'analytics.mjs — 7 KPI-endpoints svarar', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: '/overview /funnel /geographic /trend /earnings /revenue /health' },
{ title: 'mission-ws.mjs — WebSocket real-time broadcast', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'ws://quixzoom-api.wavult.com/ws/missions · 3s interval' },
]
},
{
id: 'zoomer',
name: 'Zoomer-flöde',
icon: '',
color: '#34C759',
items: [
{ title: 'Zoomer-registrering komplett (6-stegs KYC)', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'Personuppgifter → ID → Adress → Bankkonto → Skatteform → Villkor' },
{ title: 'BankID Sandbox verifierad', owner: 'Backend', status: 'done', deadline: 'Juni 1' },
{ title: 'BankID Production-avtal signerat', owner: 'Erik', status: 'done', deadline: 'Juni 5' },
{ title: 'Zoomer-profil & portfolio-hantering live', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'GET/PATCH /zoomers/:id/profile + portfolio CRUD' },
{ title: 'Lönerapport & earnings-historik synlig i app', owner: 'iOS', status: 'in-prog', deadline: 'Juni 13' },
{ title: 'Skill-nivå-validering (professional/drone/basic)', owner: 'Backend', status: 'done', deadline: 'Juni 5' },
]
},
{
id: 'mission',
name: 'Mission-flöde',
icon: '',
color: '#FF9500',
items: [
{ title: 'Customer bokar uppdrag (request-mission-flöde)', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /customers/:id/book → mission draft' },
{ title: 'Matchningsalgoritm föreslår topp-3 Zoomers', owner: 'Backend', status: 'done', deadline: 'Juni 10' },
{ title: 'Zoomer accepterar → mission active', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /missions/:id/accept · push-notis skickas' },
{ title: 'Check-in/check-out GPS-verifiering', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /missions/:id/checkin + /checkout' },
{ title: 'Mission-komplettering + leverabler uppladdade', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /missions/:id/complete + /deliverables' },
{ title: 'Rating-formulär skickas automatiskt efter complete', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /ratings · viktad genomsnitt beräknas' },
{ title: '12 seed-uppdrag i SE-städer (Sthlm/Gbg/Malmö/Uppsala)', owner: 'Backend', status: 'done', deadline: 'Juni 5', note: 'Via deep-build.mjs' },
]
},
{
id: 'payout',
name: 'Utbetalningar & DAC7',
icon: '',
color: '#AF52DE',
items: [
{ title: 'Stripe Production Keys konfigurerade', owner: 'Ops', status: 'done', deadline: 'Juni 5' },
{ title: 'Stripe Connect Zoomer-onboarding testad', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'stripe_connect + bank_transfer + mock-metoder' },
{ title: 'Automatisk payout dag 7 efter mission complete', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /payouts/process · mintröskel 100 SEK ackumuleras' },
{ title: 'Batch-utbetalning månadsvis konfigurerad', owner: 'Backend', status: 'done', deadline: 'Juni 10', note: 'POST /payouts/batch · alla pending zoomers' },
{ title: 'DAC7 TIN-verifiering per Zoomer', owner: 'Legal', status: 'done', deadline: 'Juni 10', note: 'POST /compliance/dac7/sellers/:sellerId/verify' },
{ title: 'DAC7 XML rapport genereras (Skatteverket-format)', owner: 'Legal', status: 'done', deadline: 'Juni 10', note: 'urn:oecd:ties:dpi:v1 · messageRefId QZ-DAC7-*' },
{ title: 'DAC7 status-dashboard live', owner: 'Legal', status: 'done', deadline: 'Juni 10', note: '/quixzoom/dac7-report.html + /dac7-reporting.html' },
{ title: 'Rapporteringsgräns 30 transaktioner / 2 000 USD per år', owner: 'Legal', status: 'done', deadline: 'Juni 5', note: 'Art. 8ac DAC trösk implementerad i dac7.mjs:175' },
]
},
{
id: 'ios',
name: 'iOS Build',
icon: '',
color: '#5AC8FA',
items: [
{ title: 'Mac SSH-access för iOS builds löst', owner: 'Johan', status: 'done', deadline: 'Juni 1', note: 'Eskalerat → löst efter Sven-intervention' },
{ title: 'Xcode target iOS 17+ · kompilerar utan warnings', owner: 'iOS', status: 'done', deadline: 'Juni 1' },
{ title: 'TestFlight build uppladdad · extern beta aktiv', owner: 'iOS', status: 'done', deadline: 'Juni 5' },
{ title: '25+ externa beta-testare · feedback insamlad', owner: 'iOS', status: 'done', deadline: 'Juni 10' },
{ title: 'App Store submission (final build)', owner: 'iOS', status: 'in-prog', deadline: 'Juni 13', urgent: true },
{ title: 'App Review Notes inskickade till Apple', owner: 'iOS', status: 'in-prog', deadline: 'Juni 13', urgent: true },
]
},
{
id: 'appstore',
name: 'App Store',
icon: '',
color: '#007AFF',
items: [
{ title: 'App Store Connect-konto verifierat', owner: 'Ops', status: 'done', deadline: 'Maj 10' },
{ title: 'App-ikon 1024×1024 PNG godkänd', owner: 'Design', status: 'done', deadline: 'Maj 15' },
{ title: 'Screenshots alla device-storlekar klara', owner: 'Design', status: 'done', deadline: 'Juni 5' },
{ title: 'App-beskrivning SV + EN publicerad', owner: 'Mktg', status: 'done', deadline: 'Juni 5' },
{ title: 'ASO keyword-optimering genomförd', owner: 'Mktg', status: 'done', deadline: 'Juni 5' },
{ title: 'Privacy Policy URL i App Store Connect', owner: 'Legal', status: 'done', deadline: 'Juni 5' },
]
},
{
id: 'legal',
name: 'Legal / Compliance',
icon: '',
color: '#FF3B30',
items: [
{ title: 'Privacy Policy + Terms of Service publicerade', owner: 'Legal', status: 'done', deadline: 'Maj 15' },
{ title: 'GDPR Data Deletion-flow verifierat', owner: 'Legal', status: 'done', deadline: 'Juni 5' },
{ title: 'KYC/AML-process granskad av extern jurist', owner: 'Legal', status: 'done', deadline: 'Juni 5' },
{ title: 'Liability Framework (T545) konfigurerad', owner: 'Legal', status: 'done', deadline: 'Juni 5', note: 'Free $100 / Plus $500 / Pro 12mo / Enterprise $5M floor' },
{ title: 'Dispute resolution 3-steg (T546) aktiverat', owner: 'Legal', status: 'done', deadline: 'Juni 5' },
]
},
{
id: 'operations',
name: 'Operations',
icon: '',
color: '#FF9500',
items: [
{ title: 'Monitoring & alerting konfigurerat', owner: 'Ops', status: 'done', deadline: 'Juni 5' },
{ title: 'ECS auto-scaling skalningsplan dokumenterad', owner: 'Ops', status: 'done', deadline: 'Maj 22' },
{ title: 'Backup & restore verifierat', owner: 'Ops', status: 'done', deadline: 'Juni 5' },
{ title: 'Support-kanal öppen (email + chat)', owner: 'Ops', status: 'done', deadline: 'Maj 20' },
{ title: 'Rate limiting iOS traffic konfigurerat', owner: 'Backend', status: 'done', deadline: 'Juni 5' },
{ title: 'Runbook för vanliga incidents skriven', owner: 'Ops', status: 'in-prog', deadline: 'Juni 13' },
{ title: 'Post-launch review schemalagd (22 juni)', owner: 'Erik', status: 'pending', deadline: 'Juni 13' },
]
},
{
id: 'marketing',
name: 'Marketing',
icon: '',
color: '#34C759',
items: [
{ title: 'Landing page quixzoom.se live', owner: 'Mktg', status: 'done', deadline: 'Maj 20' },
{ title: 'Launch-kampanj planerad & schemalagd', owner: 'Mktg', status: 'done', deadline: 'Juni 5' },
{ title: 'Press release klar för distribution', owner: 'Mktg', status: 'done', deadline: 'Juni 10' },
{ title: 'Social media content-kalender klar', owner: 'Mktg', status: 'done', deadline: 'Juni 10' },
{ title: 'Influencer-samarbeten (5 st) bekräftade', owner: 'Mktg', status: 'in-prog', deadline: 'Juni 13' },
{ title: 'Launch-kampanj 1 augusti kl 09:00 aktiveras', owner: 'Mktg', status: 'pending', deadline: 'Juni 15', urgent: true },
]
},
];
// Helpers
function computeDaysLeft() {
const diff = LAUNCH - new Date();
return Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24)));
}
function statusCircleClass(s) {
if (s === 'done') return 'done';
if (s === 'in-prog') return 'in-prog';
if (s === 'blocked') return 'blocked';
return 'pending';
}
function statusCircleContent(s) {
if (s === 'done') return '<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>';
if (s === 'in-prog') return '→';
if (s === 'blocked') return '';
return '';
}
function statusTagClass(s) {
if (s === 'done') return 'tag-done';
if (s === 'in-prog') return 'tag-inprog';
if (s === 'blocked') return 'tag-blocked';
return 'tag-pending';
}
function statusTagLabel(s) {
if (s === 'done') return 'Klar';
if (s === 'in-prog') return 'Pågår';
if (s === 'blocked') return 'Blockad';
return 'Pending';
}
// Render
function render() {
let totalDone = 0, totalInprog = 0, totalBlocked = 0, totalPending = 0, totalItems = 0;
CATEGORIES.forEach(cat => {
cat.items.forEach(i => {
totalItems++;
if (i.status === 'done') totalDone++;
else if (i.status === 'in-prog') totalInprog++;
else if (i.status === 'blocked') totalBlocked++;
else totalPending++;
});
});
const pct = Math.round(totalDone / totalItems * 100);
const days = computeDaysLeft();
document.getElementById('heroPct').textContent = pct + '%';
document.getElementById('heroLabel').textContent = `${totalDone} av ${totalItems} items klara`;
document.getElementById('heroBar').style.width = pct + '%';
document.getElementById('totalItems').textContent = totalItems;
const daysEl = document.getElementById('daysLeft');
daysEl.textContent = days > 0 ? `${days} dagar kvar` : 'LAUNCH DAY <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>';
if (days <= 3) daysEl.style.color = 'var(--red)';
document.getElementById('cntDone').textContent = totalDone;
document.getElementById('cntInprog').textContent = totalInprog;
document.getElementById('cntBlocked').textContent = totalBlocked;
document.getElementById('cntPending').textContent = totalPending;
const badge = document.getElementById('navBadge');
badge.textContent = `${pct}% Klar`;
if (totalBlocked === 0 && pct >= 90) badge.classList.add('ready');
else if (totalBlocked > 0) badge.classList.add('warn');
const container = document.getElementById('checklistContainer');
container.innerHTML = CATEGORIES.map(cat => {
const catDone = cat.items.filter(i => i.status === 'done').length;
const catTotal = cat.items.length;
const catPct = Math.round(catDone / catTotal * 100);
const itemsHTML = cat.items.map(item => `
<div class="check-item">
<div class="check-circle ${statusCircleClass(item.status)}">${statusCircleContent(item.status)}</div>
<div class="check-content">
<div class="check-title ${item.status === 'done' ? 'line-through' : ''}">${item.title}</div>
${item.note ? `<div class="check-note">${item.note}</div>` : ''}
<div class="check-meta">
<span class="meta-chip">${item.owner}</span>
<span class="meta-deadline ${item.urgent ? 'urgent' : ''}">${item.deadline || ''}</span>
</div>
</div>
<div class="check-right">
<span class="status-tag ${statusTagClass(item.status)}">${statusTagLabel(item.status)}</span>
</div>
</div>
`).join('');
return `
<div class="cat-section">
<div class="cat-header">
<div class="cat-icon" style="background:${cat.color}22">${cat.icon}</div>
<span class="cat-name">${cat.name}</span>
<span class="cat-progress">${catDone}/${catTotal}</span>
</div>
<div class="cat-progress-bar">
<div class="cat-progress-fill" style="width:${catPct}%;background:${cat.color}"></div>
</div>
<div class="checklist">${itemsHTML}</div>
</div>
`;
}).join('');
document.getElementById('footerTs').textContent = new Date().toLocaleString('sv-SE');
}
render();
</script>
<!-- quiXzoom Mina Sidor - Universal Header Component -->
<!-- Injicera detta i <head> på alla quiXzoom-sajter -->
<style>
.qz-auth-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 48px;
background: #0A0A0A;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
}
.qz-auth-bar .qz-logo {
font-size: 18px;
font-weight: 700;
color: #fff;
text-decoration: none;
letter-spacing: -0.5px;
}
.qz-auth-bar .qz-logo span {
color: #007AFF;
}
.qz-auth-bar .qz-nav {
display: flex;
gap: 8px;
align-items: center;
}
.qz-auth-bar .qz-btn {
padding: 8px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
text-decoration: none;
}
.qz-auth-bar .qz-btn-primary {
background: #007AFF;
color: white;
}
.qz-auth-bar .qz-btn-primary:hover {
background: #0056CC;
}
.qz-auth-bar .qz-btn-ghost {
background: transparent;
color: rgba(255,255,255,0.8);
}
.qz-auth-bar .qz-btn-ghost:hover {
background: rgba(255,255,255,0.1);
color: white;
}
.qz-auth-bar .qz-user {
display: flex;
align-items: center;
gap: 8px;
color: white;
font-size: 14px;
}
.qz-auth-bar .qz-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: #007AFF;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
}
.qz-auth-bar .qz-dropdown {
position: relative;
}
.qz-auth-bar .qz-dropdown-menu {
position: absolute;
top: 40px;
right: 0;
background: #1C1C1E;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
padding: 8px;
min-width: 200px;
display: none;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.qz-auth-bar .qz-dropdown-menu.active {
display: block;
}
.qz-auth-bar .qz-dropdown-item {
padding: 10px 12px;
border-radius: 8px;
color: white;
text-decoration: none;
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
transition: background 0.2s;
}
.qz-auth-bar .qz-dropdown-item:hover {
background: rgba(255,255,255,0.1);
}
.qz-auth-bar .qz-dropdown-divider {
height: 1px;
background: rgba(255,255,255,0.1);
margin: 8px 0;
}
.qz-auth-bar .qz-balance {
background: rgba(0,122,255,0.15);
color: #007AFF;
padding: 4px 10px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
}
@media (max-width: 640px) {
.qz-auth-bar .qz-nav .qz-btn-ghost:not(.qz-user) {
display: none;
}
}
</style>
<div id="qz-auth-bar" class="qz-auth-bar">
<a href="https://quixzoom.com" class="qz-logo">qui<span>X</span>zoom</a>
<div class="qz-nav">
<div id="qz-auth-guest">
<a href="https://app.quixzoom.com/login" class="qz-btn qz-btn-ghost">Logga in</a>
<a href="https://app.quixzoom.com/register" class="qz-btn qz-btn-primary">Bli Zoomer</a>
</div>
<div id="qz-auth-user" style="display:none;">
<span class="qz-balance" id="qz-balance">$0.00</span>
<div class="qz-dropdown">
<button class="qz-btn qz-btn-ghost qz-user" onclick="qzToggleDropdown()">
<div class="qz-avatar" id="qz-avatar">E</div>
<span id="qz-user-name">Erik</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<div class="qz-dropdown-menu" id="qz-dropdown-menu">
<a href="https://app.quixzoom.com/dashboard" class="qz-dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/></svg>
Dashboard
</a>
<a href="https://app.quixzoom.com/wallet" class="qz-dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4h-4z"/></svg>
Plånbok
</a>
<a href="https://app.quixzoom.com/missions" class="qz-dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
Mina uppdrag
</a>
<div class="qz-dropdown-divider"></div>
<a href="https://app.quixzoom.com/settings" class="qz-dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.67 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.67 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.67a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
Inställningar
</a>
<a href="#" onclick="qzLogout(); return false;" class="qz-dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
Logga ut
</a>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const SSO_BASE = 'https://auth.quixzoom.com';
const APP_BASE = 'https://app.quixzoom.com';
// Check auth status
async function qzCheckAuth() {
try {
const res = await fetch(SSO_BASE + '/auth/check', {
credentials: 'include',
});
if (res.ok) {
const data = await res.json();
if (data.authenticated) {
document.getElementById('qz-auth-guest').style.display = 'none';
document.getElementById('qz-auth-user').style.display = 'flex';
document.getElementById('qz-user-name').textContent = data.user.name || data.user.email;
document.getElementById('qz-avatar').textContent = (data.user.name || data.user.email)[0].toUpperCase();
if (data.user.balance !== undefined) {
document.getElementById('qz-balance').textContent = '$' + data.user.balance.toFixed(2);
}
}
}
} catch (e) {
// Silent fail - show guest state
}
}
// Toggle dropdown
window.qzToggleDropdown = function() {
document.getElementById('qz-dropdown-menu').classList.toggle('active');
};
// Close dropdown on outside click
document.addEventListener('click', function(e) {
if (!e.target.closest('.qz-dropdown')) {
document.getElementById('qz-dropdown-menu').classList.remove('active');
}
});
// Logout
window.qzLogout = async function() {
try {
await fetch(SSO_BASE + '/auth/logout', {
method: 'POST',
credentials: 'include',
});
} catch (e) {}
document.cookie = 'qz_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=.quixzoom.com; path=/;';
document.cookie = 'qz_refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=.quixzoom.com; path=/;';
window.location.reload();
};
// Init
qzCheckAuth();
})();
</script>
</body>
</html>