Files
boc/quixzoom-landing-fixed/launch-checklist.html
T

838 lines
35 KiB
HTML
Raw Normal View History

<!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>
</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 EUR 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>
</body>
</html>