2026-07-05 06:41:32 +00:00
<!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 >
2026-07-07 07:11:50 +00:00
/* iOS 26 / Apple Native — Light Mode */
2026-07-05 06:41:32 +00:00
: 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 : 12 px ;
--radius-lg : 16 px ;
--radius-sm : 8 px ;
--font : - apple-system , "SF Pro Display" , "Helvetica Neue" , sans-serif ;
--safe-top : env ( safe - area - inset - top , 0 px ) ;
--safe-bottom : env ( safe - area - inset - bottom , 0 px ) ;
}
* { 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 : 100 dvh ;
padding-top : calc ( var ( - - safe - top ) + 0 px ) ;
padding-bottom : calc ( var ( - - safe - bottom ) + 60 px ) ;
}
2026-07-07 07:11:50 +00:00
/* Nav Bar */
2026-07-05 06:41:32 +00:00
. nav-bar {
position : sticky ;
top : 0 ;
z-index : 100 ;
background : rgba ( 242 , 242 , 247 , 0.88 ) ;
-webkit- backdrop-filter : blur ( 20 px ) saturate ( 180 % ) ;
backdrop-filter : blur ( 20 px ) saturate ( 180 % ) ;
border-bottom : 0.5 px solid var ( - - separator ) ;
padding : 14 px 20 px 12 px ;
display : flex ;
align-items : center ;
justify-content : space-between ;
}
. nav-title { font-size : 17 px ; font-weight : 600 ; letter-spacing : -0.4 px ; }
. nav-badge {
background : var ( - - tint ) ;
color : #fff ;
font-size : 13 px ;
font-weight : 600 ;
padding : 4 px 12 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - xl ) ;
2026-07-05 06:41:32 +00:00
transition : background 0.3 s ;
}
. nav-badge . ready { background : var ( - - green ) ; }
. nav-badge . warn { background : var ( - - orange ) ; }
2026-07-07 07:11:50 +00:00
/* Page Header */
2026-07-05 06:41:32 +00:00
. page-header { padding : 24 px 20 px 8 px ; }
. page-title { font-size : 34 px ; font-weight : 700 ; letter-spacing : -0.5 px ; line-height : 1.1 ; }
. page-sub { font-size : 15 px ; color : var ( - - label - secondary ) ; margin-top : 4 px ; }
2026-07-07 07:11:50 +00:00
/* Countdown Hero */
2026-07-05 06:41:32 +00:00
. countdown-hero {
margin : 16 px 16 px 0 ;
background : linear-gradient ( 135 deg , #001E50 0 % , #0040A8 100 % ) ;
border-radius : var ( - - radius - lg ) ;
padding : 22 px 20 px 20 px ;
box-shadow : 0 4 px 20 px rgba ( 0 , 30 , 80 , 0.3 ) ;
color : #fff ;
}
. countdown-label {
font-size : 12 px ;
font-weight : 600 ;
text-transform : uppercase ;
letter-spacing : 1 px ;
color : rgba ( 255 , 255 , 255 , 0.75 ) ;
margin-bottom : 12 px ;
}
. countdown-blocks {
display : flex ;
gap : 10 px ;
align-items : flex-start ;
}
. cd-block {
flex : 1 ;
background : rgba ( 255 , 255 , 255 , 0.12 ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
padding : 12 px 6 px 10 px ;
text-align : center ;
}
. cd-num {
font-size : 32 px ;
font-weight : 700 ;
font-variant-numeric : tabular-nums ;
letter-spacing : -1 px ;
line-height : 1 ;
}
. cd-unit {
font-size : 11 px ;
font-weight : 500 ;
color : rgba ( 255 , 255 , 255 , 0.75 ) ;
text-transform : uppercase ;
letter-spacing : 0.5 px ;
margin-top : 4 px ;
}
. cd-sep {
font-size : 26 px ;
font-weight : 700 ;
color : rgba ( 255 , 255 , 255 , 0.75 ) ;
margin-top : 10 px ;
}
. countdown-launch {
margin-top : 14 px ;
font-size : 13 px ;
color : rgba ( 255 , 255 , 255 , 0.8 ) ;
text-align : center ;
}
. countdown-launch strong { color : #fff ; font-weight : 700 ; }
2026-07-07 07:11:50 +00:00
/* Progress Hero */
2026-07-05 06:41:32 +00:00
. progress-hero {
margin : 12 px 16 px 0 ;
background : var ( - - bg - card ) ;
border-radius : var ( - - radius - lg ) ;
padding : 18 px 20 px 16 px ;
box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , 0.06 ) ;
}
. progress-row {
display : flex ;
justify-content : space-between ;
align-items : flex-end ;
margin-bottom : 12 px ;
}
. progress-pct {
font-size : 42 px ;
font-weight : 700 ;
letter-spacing : -1 px ;
line-height : 1 ;
color : var ( - - tint ) ;
}
. progress-label { font-size : 14 px ; color : var ( - - label - secondary ) ; margin-top : 2 px ; }
. progress-right { text-align : right ; }
. deadline-label { font-size : 12 px ; color : var ( - - label - secondary ) ; text-transform : uppercase ; letter-spacing : 0.5 px ; }
. deadline-date { font-size : 20 px ; font-weight : 700 ; letter-spacing : -0.4 px ; }
. days-left { font-size : 13 px ; color : var ( - - orange ) ; font-weight : 600 ; margin-top : 2 px ; }
. days-left . green { color : var ( - - green ) ; }
. progress-bar-bg {
background : var ( - - fill ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
height : 10 px ;
overflow : hidden ;
}
. progress-bar-fill {
height : 100 % ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
background : linear-gradient ( 90 deg , var ( - - tint ) , #5AC8FA ) ;
transition : width 0.8 s cubic-bezier ( 0.34 , 1.56 , 0.64 , 1 ) ;
}
2026-07-07 07:11:50 +00:00
/* Stat Row */
2026-07-05 06:41:32 +00:00
. stat-row {
display : grid ;
grid-template-columns : repeat ( 4 , 1 fr ) ;
gap : 10 px ;
padding : 12 px 16 px 0 ;
}
. stat-mini {
background : var ( - - bg - card ) ;
border-radius : var ( - - radius ) ;
padding : 12 px 10 px ;
text-align : center ;
box-shadow : 0 1 px 2 px rgba ( 0 , 0 , 0 , 0.05 ) ;
}
. stat-mini-num {
font-size : 22 px ;
font-weight : 700 ;
font-variant-numeric : tabular-nums ;
letter-spacing : -0.4 px ;
}
. 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 : 10 px ;
font-weight : 500 ;
color : var ( - - label - secondary ) ;
text-transform : uppercase ;
letter-spacing : 0.3 px ;
margin-top : 2 px ;
}
2026-07-07 07:11:50 +00:00
/* Final Verification Banner */
2026-07-05 06:41:32 +00:00
. verify-banner {
margin : 22 px 16 px 0 ;
background : rgba ( 52 , 199 , 89 , 0.10 ) ;
border : 1.5 px solid rgba ( 52 , 199 , 89 , 0.35 ) ;
border-radius : var ( - - radius - lg ) ;
padding : 14 px 16 px ;
}
. verify-banner-title {
font-size : 13 px ;
font-weight : 700 ;
text-transform : uppercase ;
letter-spacing : 0.6 px ;
color : var ( - - green ) ;
margin-bottom : 10 px ;
display : flex ;
align-items : center ;
gap : 6 px ;
}
. verify-row {
display : flex ;
align-items : center ;
gap : 10 px ;
padding : 7 px 0 ;
border-bottom : 0.5 px solid rgba ( 52 , 199 , 89 , 0.2 ) ;
}
. verify-row : last-child { border-bottom : none ; padding-bottom : 0 ; }
. verify-dot {
width : 20 px ; height : 20 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - full ) ;
2026-07-05 06:41:32 +00:00
display : flex ; align-items : center ; justify-content : center ;
font-size : 11 px ;
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 : 14 px ; font-weight : 500 ; flex : 1 ; }
. verify-endpoint {
font-size : 11 px ;
font-family : "SF Mono" , "Menlo" , monospace ;
color : var ( - - label - secondary ) ;
background : var ( - - fill ) ;
padding : 2 px 6 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
}
2026-07-07 07:11:50 +00:00
/* Category Section */
2026-07-05 06:41:32 +00:00
. cat-section { margin-top : 28 px ; }
. cat-header {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 0 20 px 10 px ;
}
. cat-icon {
width : 28 px ; height : 28 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
display : flex ; align-items : center ; justify-content : center ;
font-size : 14 px ;
flex-shrink : 0 ;
}
. cat-name {
font-size : 13 px ;
font-weight : 600 ;
text-transform : uppercase ;
letter-spacing : 0.6 px ;
color : var ( - - label - secondary ) ;
flex : 1 ;
}
. cat-progress {
font-size : 12 px ;
font-weight : 600 ;
color : var ( - - label - tertiary ) ;
}
. cat-progress-bar {
height : 3 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
background : var ( - - fill ) ;
margin : 0 20 px 8 px ;
overflow : hidden ;
}
. cat-progress-fill {
height : 100 % ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
transition : width 0.6 s ease ;
}
2026-07-07 07:11:50 +00:00
/* Checklist */
2026-07-05 06:41:32 +00:00
. checklist {
background : var ( - - bg - card ) ;
border-radius : var ( - - radius - lg ) ;
margin : 0 16 px ;
overflow : hidden ;
box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , 0.06 ) ;
}
. check-item {
display : flex ;
align-items : flex-start ;
gap : 14 px ;
padding : 14 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
transition : background 0.12 s ;
-webkit- user-select : none ;
user-select : none ;
}
. check-item : last-child { border-bottom : none ; }
. check-item : active { background : var ( - - fill ) ; }
. check-circle {
width : 24 px ; height : 24 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - full ) ;
2026-07-05 06:41:32 +00:00
flex-shrink : 0 ;
display : flex ; align-items : center ; justify-content : center ;
font-size : 13 px ;
font-weight : 700 ;
margin-top : 1 px ;
transition : all 0.2 s ;
}
. check-circle . done { background : var ( - - green ) ; color : #fff ; }
. check-circle . in-prog { background : rgba ( 0 , 122 , 255 , 0.15 ) ; border : 2 px solid var ( - - tint ) ; color : var ( - - tint ) ; }
. check-circle . blocked { background : rgba ( 255 , 59 , 48 , 0.12 ) ; border : 2 px solid var ( - - red ) ; color : var ( - - red ) ; }
. check-circle . pending { border : 2 px solid var ( - - separator ) ; color : var ( - - label - tertiary ) ; background : transparent ; }
. check-content { flex : 1 ; min-width : 0 ; }
. check-title {
font-size : 15 px ;
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 : 12 px ;
color : var ( - - label - tertiary ) ;
margin-top : 3 px ;
font-style : italic ;
}
. check-meta {
display : flex ;
flex-wrap : wrap ;
gap : 6 px ;
margin-top : 5 px ;
align-items : center ;
}
. meta-chip {
font-size : 12 px ;
font-weight : 500 ;
padding : 2 px 8 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
background : var ( - - fill ) ;
color : var ( - - label - secondary ) ;
}
. meta-deadline {
font-size : 12 px ;
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 : 4 px ;
}
. owner-badge {
font-size : 11 px ;
font-weight : 600 ;
padding : 3 px 8 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
background : var ( - - fill ) ;
color : var ( - - label - secondary ) ;
}
. status-tag {
font-size : 11 px ;
font-weight : 600 ;
padding : 3 px 8 px ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - sm ) ;
2026-07-05 06:41:32 +00:00
}
. 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 ) ; }
2026-07-07 07:11:50 +00:00
/* Footer */
2026-07-05 06:41:32 +00:00
. footer {
text-align : center ;
padding : 32 px 20 px 12 px ;
font-size : 12 px ;
color : var ( - - label - tertiary ) ;
line-height : 1.6 ;
}
@ media ( max-width : 640px ) {
. hero h1 { font-size : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !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 : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !important ; }
}
< / style >
< / head >
< body >
< h1 style = "position:absolute;left:-9999px;" > Launch Checklist for quiXzoom< / h1 >
<!-- Nav Bar -->
< div class = "nav-bar" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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;" >
2026-07-07 07:11:50 +00:00
< 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
2026-07-05 06:41:32 +00:00
< / div >
< / div >
< div class = "verify-banner" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< div class = "verify-row" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< div class = "verify-text" > Alla quiXzoom API-endpoints svarar< / div >
< div class = "verify-endpoint" > 14 moduler · /api/quixzoom/*< / div >
< / div >
< div class = "verify-row" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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" >
2026-07-07 07:11:50 +00:00
< 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 >
2026-07-05 06:41:32 +00:00
< 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' ) ;
2026-07-07 07:11:50 +00:00
// Countdown
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
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>' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : ' ' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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' ,
2026-07-07 07:11:50 +00:00
icon : '' ,
2026-07-05 06:41:32 +00:00
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 } ,
]
} ,
] ;
2026-07-07 07:11:50 +00:00
// Helpers
2026-07-05 06:41:32 +00:00
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 ) {
2026-07-07 07:11:50 +00:00
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>' ;
2026-07-05 06:41:32 +00:00
if ( s === 'in-prog' ) return '→' ;
2026-07-07 07:11:50 +00:00
if ( s === 'blocked' ) return '' ;
2026-07-05 06:41:32 +00:00
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' ;
}
2026-07-07 07:11:50 +00:00
// Render
2026-07-05 06:41:32 +00:00
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' ) ;
2026-07-07 07:11:50 +00:00
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>' ;
2026-07-05 06:41:32 +00:00
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 >