2026-07-05 06:41:32 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
2026-07-12 12:41:35 +00:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-07-05 06:41:32 +00:00
< title > Earn as a Zoomer — quiXzoom< / title >
2026-07-12 12:41:35 +00:00
< meta name = "description" content = "Earn money as a quiXzoom Zoomer. Free to join. No fees. Take photo missions near you and get paid per approved mission." >
2026-07-07 07:11:50 +00:00
< link rel = "canonical" href = "https://www.quixzoom.com/earn/" >
2026-07-12 12:41:35 +00:00
< link rel = "icon" type = "image/svg+xml" href = "/favicon.svg" >
2026-07-05 06:41:32 +00:00
< style >
2026-07-12 12:41:35 +00:00
: root {
--bg-dark : #1a1a1a ; --bg-white : #FFFFFF ; --bg-off : #F7F7F5 ;
--blue : #0066FF ; --blue-dark : #0052CC ; --blue-pale : rgba ( 0 , 102 , 255 , .07 ) ;
--green : #00C853 ; --text-dark : #1a1a1a ; --text-body : #3a3a3a ; --text-muted : #6B6B6B ;
--radius-card : 16 px ; --shadow-hover : 0 16 px 48 px rgba ( 0 , 0 , 0 , .14 ) ;
--transition : .22 s cubic-bezier ( .25 , .46 , .45 , .94 ) ;
--text-sm : .875 rem ; --text-base : 1 rem ; --text-lg : 1.125 rem ; --text-xl : 1.375 rem ; --text-2xl : 1.75 rem ;
--radius-sm : 8 px ; --radius-md : 14 px ; --radius-lg : 24 px ;
2026-07-05 06:41:32 +00:00
}
2026-07-12 12:41:35 +00:00
* , * :: before , * :: after { box-sizing : border-box ; margin : 0 ; padding : 0 }
html { scroll-behavior : smooth ; -webkit- text-size-adjust : 100 % }
body { font-family : - apple-system , BlinkMacSystemFont , 'Inter' , 'Helvetica Neue' , sans-serif ; font-size : 1 rem ; line-height : 1.65 ; color : var ( - - text - body ) ; background : var ( - - bg - white ) ; -webkit- font-smoothing : antialiased ; overflow-x : hidden }
a { color : inherit ; text-decoration : none }
ul { list-style : none }
button { cursor : pointer ; font-family : inherit }
h1 { font-size : clamp ( 2.5 rem , 5 vw , 4 rem ) ; font-weight : 900 ; letter-spacing : -.05 em ; line-height : 1 }
h2 { font-size : clamp ( 1.75 rem , 3 vw , 2.5 rem ) ; font-weight : 800 ; letter-spacing : -.04 em ; line-height : 1.1 }
h3 { font-size : var ( - - text - xl ) ; font-weight : 700 ; letter-spacing : -.02 em ; line-height : 1.3 }
. container { width : 100 % ; max-width : 1160 px ; margin : 0 auto ; padding : 0 24 px }
. section-pad { padding : 80 px 0 }
. section-header { text-align : center ; margin-bottom : 56 px }
. section-header h2 { color : var ( - - text - dark ) ; margin-bottom : 16 px }
. section-header p { font-size : var ( - - text - lg ) ; color : var ( - - text - muted ) ; max-width : 580 px ; margin : 0 auto }
/* NAV */
nav { position : sticky ; top : 0 ; z-index : 300 ; background : var ( - - bg - white ) ; border-bottom : 1 px solid rgba ( 0 , 0 , 0 , .08 ) ; box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , .05 ) }
. nav-inner { display : flex ; align-items : center ; justify-content : space-between ; height : 68 px ; gap : 32 px }
. nav-logo { font-size : 1.375 rem ; font-weight : 900 ; color : var ( - - text - dark ) ; letter-spacing : -.04 em }
. nav-logo . x { color : var ( - - blue ) }
. nav-links { display : flex ; align-items : center ; gap : 32 px }
. nav-links a { color : var ( - - text - muted ) ; font-size : var ( - - text - base ) ; font-weight : 500 ; transition : color .15 s }
. nav-links a : hover { color : var ( - - text - dark ) }
. btn-nav { background : var ( - - blue ) ; color : var ( - - text - dark ) ; border : none ; border-radius : var ( - - radius - lg ) ; padding : 10 px 24 px ; font-size : var ( - - text - base ) ; font-weight : 700 ; transition : background .15 s ; white-space : nowrap }
. btn-nav : hover { background : var ( - - blue - dark ) }
. mobile-menu { display : none }
@ media ( max-width : 768px ) {
. nav-links { display : none }
. hamburger { display : flex ; flex-direction : column ; gap : 5 px ; cursor : pointer ; padding : 8 px ; background : none ; border : none }
. hamburger span { display : block ; width : 22 px ; height : 2 px ; background : var ( - - text - dark ) ; border-radius : var ( - - radius - sm ) }
. mobile-menu { display : none ; position : fixed ; inset : 0 ; top : 68 px ; background : var ( - - bg - white ) ; z-index : 299 ; padding : 24 px ; flex-direction : column ; gap : 0 ; border-top : 1 px solid rgba ( 0 , 0 , 0 , .08 ) }
. mobile-menu . open { display : flex }
. mobile-menu a { color : var ( - - text - body ) ; font-size : 1.1 rem ; font-weight : 500 ; padding : 18 px 0 ; border-bottom : 1 px solid rgba ( 0 , 0 , 0 , .08 ) ; display : block }
. mobile-menu . btn-mobile { display : block ; margin-top : 24 px ; background : var ( - - blue ) ; color : var ( - - text - dark ) ; text-align : center ; border-radius : var ( - - radius - lg ) ; padding : 16 px 24 px ; font-size : 1 rem ; font-weight : 700 }
}
/* HERO */
. hero-earn { background : var ( - - bg - off ) ; padding : 80 px 0 64 px }
. hero-earn-inner { max-width : 720 px }
. hero-earn h1 { color : var ( - - text - dark ) ; margin-bottom : 24 px }
. hero-earn p { font-size : clamp ( 1.0625 rem , 2 vw , 1.25 rem ) ; color : var ( - - text - body ) ; line-height : 1.6 ; margin-bottom : 32 px }
. hero-badge { display : inline-flex ; align-items : center ; gap : 8 px ; background : var ( - - blue - pale ) ; border : 1 px solid rgba ( 0 , 102 , 255 , .15 ) ; border-radius : 100 px ; padding : 8 px 18 px ; font-size : var ( - - text - sm ) ; font-weight : 600 ; color : var ( - - blue ) ; margin-bottom : 28 px }
. hero-cta { display : flex ; gap : 12 px ; flex-wrap : wrap }
. btn-primary { background : var ( - - blue ) ; color : var ( - - text - dark ) ; border : none ; border-radius : var ( - - radius - lg ) ; padding : 14 px 32 px ; font-size : 1 rem ; font-weight : 700 ; transition : background .15 s ; text-decoration : none ; display : inline-block }
. btn-primary : hover { background : var ( - - blue - dark ) }
. btn-ghost { background : transparent ; border : 2 px solid rgba ( 0 , 0 , 0 , .15 ) ; color : var ( - - text - dark ) ; border-radius : var ( - - radius - lg ) ; padding : 14 px 32 px ; font-size : 1 rem ; font-weight : 700 ; transition : border-color .15 s , background .15 s ; text-decoration : none ; display : inline-block }
. btn-ghost : hover { border-color : var ( - - blue ) ; background : var ( - - blue - pale ) }
/* STATS */
. stats-bar { background : var ( - - blue ) ; padding : 48 px 0 }
. stats-grid { display : grid ; grid-template-columns : repeat ( 4 , 1 fr ) ; gap : 0 ; text-align : center }
. stat-item { padding : 20 px 16 px ; position : relative }
. stat-item : not ( : last-child ) :: after { content : '' ; position : absolute ; right : 0 ; top : 20 % ; height : 60 % ; width : 1 px ; background : rgba ( 10 , 10 , 10 , .3 ) }
. stat-value { font-size : clamp ( 1.75 rem , 3 vw , 2.5 rem ) ; font-weight : 900 ; color : var ( - - text - dark ) ; letter-spacing : -.04 em ; line-height : 1 ; display : block ; margin-bottom : 8 px }
. stat-label { font-size : .9 rem ; color : rgba ( 10 , 10 , 10 , .9 ) ; font-weight : 500 }
@ media ( max-width : 600px ) { . stats-grid { grid-template-columns : repeat ( 2 , 1 fr ) ; gap : 1 px ; background : rgba ( 10 , 10 , 10 , .2 ) } . stat-item { background : var ( - - blue ) } . stat-item :: after { display : none } }
/* STEPS */
. steps-grid { display : grid ; grid-template-columns : repeat ( 3 , 1 fr ) ; gap : 28 px ; margin-top : 40 px }
. step-card { background : var ( - - bg - off ) ; border-radius : var ( - - radius - card ) ; padding : 40 px 36 px }
. step-number { display : inline-flex ; align-items : center ; justify-content : center ; width : 48 px ; height : 48 px ; background : var ( - - blue ) ; color : var ( - - text - dark ) ; border-radius : 50 % ; font-size : var ( - - text - xl ) ; font-weight : 900 ; margin-bottom : 24 px }
. step-card h3 { color : var ( - - text - dark ) ; margin-bottom : 12 px ; font-size : var ( - - text - xl ) }
. step-card p { color : var ( - - text - muted ) ; font-size : .9625 rem ; line-height : 1.6 }
@ media ( max-width : 768px ) { . steps-grid { grid-template-columns : 1 fr ; gap : 20 px } }
/* EARNINGS */
. earnings-grid { display : grid ; grid-template-columns : repeat ( 3 , 1 fr ) ; gap : 24 px ; margin-top : 40 px }
. tier-card { background : var ( - - bg - off ) ; border : 2 px solid #E8E8E8 ; border-radius : var ( - - radius - card ) ; padding : 36 px 28 px ; text-align : center }
. tier-card . featured { border-color : var ( - - blue ) ; transform : scale ( 1.03 ) }
. tier-card h3 { font-size : 1.5 rem ; font-weight : 900 ; margin-bottom : 4 px ; color : var ( - - text - dark ) }
. tier-missions { font-size : .9 rem ; color : var ( - - text - muted ) ; margin-bottom : 20 px }
. tier-earnings { font-size : 1.375 rem ; font-weight : 900 ; color : var ( - - blue ) ; letter-spacing : -.03 em ; margin-bottom : 16 px }
. tier-features { text-align : left }
. tier-features li { display : flex ; align-items : flex-start ; gap : 10 px ; font-size : var ( - - text - base ) ; padding : 6 px 0 }
. tier-features li :: before { content : '' ; display : block ; width : 18 px ; height : 18 px ; min-width : 18 px ; background : var ( - - green ) url ( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='8' viewBox='0 0 10 8'%3E%3Cpath d='M1 4l2.5 2.5L9 1' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E" ) no-repeat center ; border-radius : 50 % ; margin-top : 2 px }
@ media ( max-width : 768px ) { . earnings-grid { grid-template-columns : 1 fr } }
/* CTA */
. cta-box { background : var ( - - bg - off ) ; border-radius : var ( - - radius - card ) ; padding : 48 px 32 px ; text-align : center }
. cta-box h3 { font-size : var ( - - text -2 xl ) ; color : var ( - - text - dark ) ; margin-bottom : 12 px }
. cta-box p { color : var ( - - text - muted ) ; margin-bottom : 24 px }
/* FOOTER */
footer { background : var ( - - bg - white ) ; border-top : 1 px solid rgba ( 0 , 0 , 0 , .08 ) ; padding : 48 px 0 32 px }
. footer-inner { display : flex ; align-items : center ; justify-content : space-between ; gap : 24 px ; flex-wrap : wrap }
. footer-copy { color : rgba ( 10 , 10 , 10 , .6 ) ; font-size : var ( - - text - sm ) }
. footer-links { display : flex ; gap : 20 px }
. footer-links a { color : rgba ( 10 , 10 , 10 , .6 ) ; font-size : var ( - - text - sm ) ; transition : color .15 s }
. footer-links a : hover { color : var ( - - text - dark ) }
. footer-tagline { font-size : .8 rem ; color : rgba ( 10 , 10 , 10 , .55 ) ; margin-top : 8 px ; font-style : italic }
@ media ( max-width : 768px ) { . footer-inner { flex-direction : column ; align-items : flex-start } }
2026-07-05 06:41:32 +00:00
< / style >
2026-07-14 09:58:53 +00:00
< script type = "module" src = "https://auth.quixzoom.com/site-auth-snippet.js" > < / script >
2026-07-05 06:41:32 +00:00
< / head >
< body >
2026-07-12 12:41:35 +00:00
< nav >
< div class = "container" >
< div class = "nav-inner" >
< a href = "/" class = "nav-logo" > qui< span class = "x" > X< / span > zoom< / a >
< div class = "nav-links" >
< a href = "/order/" > Order< / a >
< a href = "/pricing/" > Pricing< / a >
< a href = "/earn/" style = "color:var(--text-dark);font-weight:600" > Earn as Zoomer< / a >
< a href = "/for-organisations/" > For Organisations< / a >
< a href = "/about" > About< / a >
< / div >
< a href = "https://app.quixzoom.com/join" class = "btn-nav" > Join Free →< / a >
< button class = "hamburger" id = "hamburger" aria-label = "Menu" onclick = "document.getElementById('mobile-menu').classList.toggle('open')" >
< span > < / span > < span > < / span > < span > < / span >
< / button >
< / div >
< / div >
< / nav >
< div class = "mobile-menu" id = "mobile-menu" >
< a href = "/order/" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > Order Field Photography< / a >
< a href = "/pricing/" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > Pricing< / a >
< a href = "/earn/" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > Earn as a Zoomer< / a >
< a href = "/for-organisations/" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > For Organisations< / a >
< a href = "/about" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > About< / a >
< a class = "btn-mobile" href = "https://app.quixzoom.com/join" onclick = "document.getElementById('mobile-menu').classList.remove('open')" > Join Free →< / a >
< / div >
< main >
<!-- HERO -->
< section class = "hero-earn" >
< div class = "container" >
< div class = "hero-earn-inner" >
< div class = "hero-badge" > Free to Join · No Fees< / div >
< h1 > Earn as a Zoomer< / h1 >
< p > Take photo missions near you. Get paid per approved mission. No platform fee — you keep 100% of your earnings. Free to join, no commitment.< / p >
< div class = "hero-cta" >
< a href = "https://app.quixzoom.com/join" class = "btn-primary" > Join Free →< / a >
< a href = "/" class = "btn-ghost" > Learn More< / a >
< / div >
< / div >
< / div >
< / section >
<!-- STATS -->
< section class = "stats-bar" >
< div class = "container" >
< div class = "stats-grid" >
2026-07-14 09:58:53 +00:00
< div class = "stat-item" > < span class = "stat-value" > $3– 30< / span > < span class = "stat-label" > Per mission< / span > < / div >
2026-07-12 12:41:35 +00:00
< div class = "stat-item" > < span class = "stat-value" > 0%< / span > < span class = "stat-label" > Platform fee< / span > < / div >
< div class = "stat-item" > < span class = "stat-value" > Weekly< / span > < span class = "stat-label" > Payouts< / span > < / div >
< div class = "stat-item" > < span class = "stat-value" > Free< / span > < span class = "stat-label" > To join< / span > < / div >
< / div >
< / div >
< / section >
<!-- HOW IT WORKS -->
< section class = "section-pad" >
< div class = "container" >
< div class = "section-header" >
< h2 > How it works< / h2 >
< p > Three steps from signup to payout.< / p >
< / div >
< div class = "steps-grid" >
< div class = "step-card" >
< div class = "step-number" > 1< / div >
< h3 > Register< / h3 >
< p > Sign up in under 60 seconds. Verify your identity once. Link your bank account via Stripe Connect. No fees, no commitment.< / p >
< / div >
< div class = "step-card" >
< div class = "step-number" > 2< / div >
< h3 > Claim Missions< / h3 >
< p > Browse active missions on the map. Each shows location, required shots, and exact payment. Claim the ones you want — no forced assignments.< / p >
< / div >
< div class = "step-card" >
< div class = "step-number" > 3< / div >
< h3 > Get Paid< / h3 >
2026-07-14 09:58:53 +00:00
< p > Submit your photos. AI reviews instantly. On approval, QX Credits are added to your balance. Cash out weekly — minimum $250.< / p >
2026-07-12 12:41:35 +00:00
< / div >
< / div >
< / div >
< / section >
<!-- EARNINGS -->
< section class = "section-pad" style = "background:var(--bg-off)" >
< div class = "container" >
< div class = "section-header" >
< h2 > How much can you earn?< / h2 >
< p > Your earnings depend on how many missions you complete. No hourly rate — you control your schedule.< / p >
< / div >
< div class = "earnings-grid" >
< div class = "tier-card" >
< h3 > Casual< / h3 >
< p class = "tier-missions" > 2– 4 missions/week< / p >
2026-07-14 09:58:53 +00:00
< p class = "tier-earnings" > $60– 480< span style = "font-size:.875rem;color:var(--text-muted)" > / month< / span > < / p >
2026-07-12 12:41:35 +00:00
< ul class = "tier-features" >
< li > Supplementary income< / li >
< li > No minimum hours< / li >
< li > All standard missions< / li >
< / ul >
< / div >
< div class = "tier-card featured" >
< h3 > Active< / h3 >
< p class = "tier-missions" > 8– 12 missions/week< / p >
2026-07-14 09:58:53 +00:00
< p class = "tier-earnings" > $240– 1,440< span style = "font-size:.875rem;color:var(--text-muted)" > / month< / span > < / p >
2026-07-12 12:41:35 +00:00
< ul class = "tier-features" >
< li > Consistent volume< / li >
< li > Priority mission access< / li >
< li > Build platform rating< / li >
< / ul >
< / div >
< div class = "tier-card" >
< h3 > Pro< / h3 >
< p class = "tier-missions" > 15+ missions/week< / p >
2026-07-14 09:58:53 +00:00
< p class = "tier-earnings" > $450– 2,400+< span style = "font-size:.875rem;color:var(--text-muted)" > / month< / span > < / p >
2026-07-12 12:41:35 +00:00
< ul class = "tier-features" >
< li > Maximum volume< / li >
< li > Premium missions< / li >
< li > Performance bonuses< / li >
< / ul >
< / div >
< / div >
< / div >
< / section >
<!-- CTA -->
< section class = "section-pad" >
< div class = "container" >
< div class = "cta-box" >
< h3 > Ready to start earning?< / h3 >
< p > Join free. No fees. No commitment. Launching August 2026.< / p >
< a href = "https://app.quixzoom.com/join" class = "btn-primary" > Join as a Zoomer →< / a >
< / div >
< / div >
< / section >
< / main >
< footer >
< div class = "container" >
< div class = "footer-inner" >
< div >
< p class = "footer-copy" > © 2026 quiXzoom · Operated by Landvex AB< / p >
< p class = "footer-tagline" > Raw data by quiXzoom. Intelligence by Landvex.< / p >
< / div >
< div class = "footer-links" >
< a href = "/" > Home< / a >
< a href = "/order/" > Order< / a >
< a href = "/pricing/" > Pricing< / a >
< a href = "/privacy/" > Privacy< / a >
< a href = "/terms/" > Terms< / a >
< / div >
< / div >
< / div >
< / footer >
2026-07-14 09:58:53 +00:00
<!-- 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 : 48 px ;
background : #0A0A0A ;
border-bottom : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 0 20 px ;
z-index : 9999 ;
font-family : - apple-system , BlinkMacSystemFont , 'SF Pro Display' , 'Segoe UI' , Roboto , sans-serif ;
}
. qz-auth-bar . qz-logo {
font-size : 18 px ;
font-weight : 700 ;
color : #fff ;
text-decoration : none ;
letter-spacing : -0.5 px ;
}
. qz-auth-bar . qz-logo span {
color : #007AFF ;
}
. qz-auth-bar . qz-nav {
display : flex ;
gap : 8 px ;
align-items : center ;
}
. qz-auth-bar . qz-btn {
padding : 8 px 16 px ;
border-radius : 8 px ;
font-size : 14 px ;
font-weight : 500 ;
cursor : pointer ;
transition : all 0.2 s ;
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 : 8 px ;
color : white ;
font-size : 14 px ;
}
. qz-auth-bar . qz-avatar {
width : 32 px ;
height : 32 px ;
border-radius : 50 % ;
background : #007AFF ;
display : flex ;
align-items : center ;
justify-content : center ;
font-weight : 600 ;
font-size : 14 px ;
}
. qz-auth-bar . qz-dropdown {
position : relative ;
}
. qz-auth-bar . qz-dropdown-menu {
position : absolute ;
top : 40 px ;
right : 0 ;
background : #1C1C1E ;
border : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
border-radius : 12 px ;
padding : 8 px ;
min-width : 200 px ;
display : none ;
box-shadow : 0 8 px 32 px rgba ( 0 , 0 , 0 , 0.4 ) ;
}
. qz-auth-bar . qz-dropdown-menu . active {
display : block ;
}
. qz-auth-bar . qz-dropdown-item {
padding : 10 px 12 px ;
border-radius : 8 px ;
color : white ;
text-decoration : none ;
display : flex ;
align-items : center ;
gap : 10 px ;
font-size : 14 px ;
transition : background 0.2 s ;
}
. qz-auth-bar . qz-dropdown-item : hover {
background : rgba ( 255 , 255 , 255 , 0.1 ) ;
}
. qz-auth-bar . qz-dropdown-divider {
height : 1 px ;
background : rgba ( 255 , 255 , 255 , 0.1 ) ;
margin : 8 px 0 ;
}
. qz-auth-bar . qz-balance {
background : rgba ( 0 , 122 , 255 , 0.15 ) ;
color : #007AFF ;
padding : 4 px 10 px ;
border-radius : 6 px ;
font-size : 13 px ;
font-weight : 600 ;
}
@ media ( max-width : 640px ) {
. qz-auth-bar . qz-nav . qz-btn-ghost : not ( . qz-user ) {
display : none ;
}
}
< / style >
2026-07-14 15:27:33 +00:00
<!--
quiXzoom Universal Auth Snippet v2.0
Add this to ALL quiXzoom sites for seamless cross - domain login
Features:
- Silent login (auto - refresh tokens)
- Cross - domain cookie sync
- Real - time auth state updates
- Mobile app compatible
-->
<!-- Auth bar styles -->
< style >
# qz-auth-bar {
position : fixed ;
top : 0 ;
left : 0 ;
right : 0 ;
height : 48 px ;
background : #0A0A0A ;
border-bottom : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 0 20 px ;
z-index : 9999 ;
font-family : - apple-system , BlinkMacSystemFont , 'SF Pro Display' , 'Segoe UI' , Roboto , sans-serif ;
}
# qz-auth-bar . qz-logo {
font-size : 18 px ;
font-weight : 700 ;
color : #fff ;
text-decoration : none ;
letter-spacing : -0.5 px ;
}
# qz-auth-bar . qz-logo span { color : #007AFF ; }
# qz-auth-bar . qz-nav {
display : flex ;
gap : 8 px ;
align-items : center ;
}
# qz-auth-bar . qz-btn {
padding : 8 px 16 px ;
border-radius : 8 px ;
font-size : 14 px ;
font-weight : 500 ;
cursor : pointer ;
transition : all 0.2 s ;
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-menu {
position : relative ;
}
# qz-auth-bar . qz-dropdown {
position : absolute ;
top : 40 px ;
right : 0 ;
background : #1C1C1E ;
border : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
border-radius : 12 px ;
padding : 8 px ;
min-width : 220 px ;
display : none ;
box-shadow : 0 8 px 32 px rgba ( 0 , 0 , 0 , 0.4 ) ;
}
# qz-auth-bar . qz-dropdown . active { display : block ; }
# qz-auth-bar . qz-dropdown-item {
padding : 10 px 12 px ;
border-radius : 8 px ;
color : white ;
text-decoration : none ;
display : flex ;
align-items : center ;
gap : 10 px ;
font-size : 14 px ;
transition : background 0.2 s ;
}
# qz-auth-bar . qz-dropdown-item : hover { background : rgba ( 255 , 255 , 255 , 0.1 ) ; }
# qz-auth-bar . qz-dropdown-divider {
height : 1 px ;
background : rgba ( 255 , 255 , 255 , 0.1 ) ;
margin : 8 px 0 ;
}
# qz-auth-bar . qz-avatar {
width : 32 px ;
height : 32 px ;
border-radius : 50 % ;
background : #007AFF ;
display : flex ;
align-items : center ;
justify-content : center ;
font-weight : 600 ;
font-size : 14 px ;
color : white ;
}
# qz-auth-bar . qz-balance {
background : rgba ( 0 , 122 , 255 , 0.15 ) ;
color : #007AFF ;
padding : 4 px 10 px ;
border-radius : 6 px ;
font-size : 13 px ;
font-weight : 600 ;
}
< / style >
<!-- Auth bar HTML -->
< div id = "qz-auth-bar" >
2026-07-14 09:58:53 +00:00
< a href = "https://quixzoom.com" class = "qz-logo" > qui< span > X< / span > zoom< / a >
< div class = "qz-nav" >
2026-07-14 15:27:33 +00:00
<!-- Guest view -->
< div id = "qz-guest-view" >
2026-07-14 09:58:53 +00:00
< 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 >
2026-07-14 15:27:33 +00:00
<!-- Authenticated view -->
< div id = "qz-user-view" style = "display:none;" >
2026-07-14 09:58:53 +00:00
< span class = "qz-balance" id = "qz-balance" > $0.00< / span >
2026-07-14 15:27:33 +00:00
< div class = "qz-user-menu" >
< button class = "qz-btn qz-btn-ghost" onclick = "qzToggleMenu()" style = "display:flex;align-items:center;gap:8px;" >
< div class = "qz-avatar" id = "qz-avatar" > ?< / div >
< span id = "qz-user-name" style = "color:white;" > User< / 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 >
2026-07-14 09:58:53 +00:00
< / button >
2026-07-14 15:27:33 +00:00
< div class = "qz-dropdown" id = "qz-dropdown" >
2026-07-14 09:58:53 +00:00
< a href = "https://app.quixzoom.com/dashboard" class = "qz-dropdown-item" >
2026-07-14 15:27:33 +00:00
< 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 >
2026-07-14 09:58:53 +00:00
Dashboard
< / a >
< a href = "https://app.quixzoom.com/wallet" class = "qz-dropdown-item" >
2026-07-14 15:27:33 +00:00
< 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 >
2026-07-14 09:58:53 +00:00
Plånbok
< / a >
< a href = "https://app.quixzoom.com/missions" class = "qz-dropdown-item" >
2026-07-14 15:27:33 +00:00
< 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 >
2026-07-14 09:58:53 +00:00
Mina uppdrag
< / a >
< div class = "qz-dropdown-divider" > < / div >
< a href = "https://app.quixzoom.com/settings" class = "qz-dropdown-item" >
2026-07-14 15:27:33 +00:00
< 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 >
2026-07-14 09:58:53 +00:00
Inställningar
< / a >
< a href = "#" onclick = "qzLogout(); return false;" class = "qz-dropdown-item" >
2026-07-14 15:27:33 +00:00
< 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 >
2026-07-14 09:58:53 +00:00
Logga ut
< / a >
< / div >
< / div >
< / div >
< / div >
< / div >
2026-07-14 15:27:33 +00:00
<!-- SSO Client Script -->
2026-07-14 09:58:53 +00:00
< script >
( function ( ) {
const SSO _BASE = 'https://auth.quixzoom.com' ;
2026-07-14 15:27:33 +00:00
// Check auth status on load
2026-07-14 09:58:53 +00:00
async function qzCheckAuth ( ) {
try {
2026-07-14 15:27:33 +00:00
const res = await fetch ( SSO _BASE + '/auth/silent' , {
method : 'GET' ,
2026-07-14 09:58:53 +00:00
credentials : 'include' ,
} ) ;
2026-07-14 15:27:33 +00:00
2026-07-14 09:58:53 +00:00
if ( res . ok ) {
const data = await res . json ( ) ;
if ( data . authenticated ) {
2026-07-14 15:27:33 +00:00
qzShowUser ( data . user ) ;
return ;
2026-07-14 09:58:53 +00:00
}
}
2026-07-14 15:27:33 +00:00
// Try check endpoint
const checkRes = await fetch ( SSO _BASE + '/auth/check' , {
credentials : 'include' ,
} ) ;
if ( checkRes . ok ) {
const checkData = await checkRes . json ( ) ;
if ( checkData . authenticated && checkData . user ) {
qzShowUser ( checkData . user ) ;
return ;
}
}
qzShowGuest ( ) ;
2026-07-14 09:58:53 +00:00
} catch ( e ) {
2026-07-14 15:27:33 +00:00
console . log ( '[QZAuth] Check failed, showing guest' ) ;
qzShowGuest ( ) ;
2026-07-14 09:58:53 +00:00
}
}
2026-07-14 15:27:33 +00:00
function qzShowUser ( user ) {
document . getElementById ( 'qz-guest-view' ) . style . display = 'none' ;
document . getElementById ( 'qz-user-view' ) . style . display = 'flex' ;
document . getElementById ( 'qz-user-name' ) . textContent = user . name || user . email ;
document . getElementById ( 'qz-avatar' ) . textContent = ( user . name || user . email ) [ 0 ] . toUpperCase ( ) ;
// Dispatch event for other scripts
window . dispatchEvent ( new CustomEvent ( 'qz-auth-change' , {
detail : { authenticated : true , user }
} ) ) ;
}
function qzShowGuest ( ) {
document . getElementById ( 'qz-guest-view' ) . style . display = 'flex' ;
document . getElementById ( 'qz-user-view' ) . style . display = 'none' ;
window . dispatchEvent ( new CustomEvent ( 'qz-auth-change' , {
detail : { authenticated : false }
} ) ) ;
}
window . qzToggleMenu = function ( ) {
document . getElementById ( 'qz-dropdown' ) . classList . toggle ( 'active' ) ;
2026-07-14 09:58:53 +00:00
} ;
2026-07-14 15:27:33 +00:00
2026-07-14 09:58:53 +00:00
window . qzLogout = async function ( ) {
try {
await fetch ( SSO _BASE + '/auth/logout' , {
method : 'POST' ,
credentials : 'include' ,
} ) ;
} catch ( e ) { }
2026-07-14 15:27:33 +00:00
// Clear all cookies
2026-07-14 09:58:53 +00:00
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=/;' ;
2026-07-14 15:27:33 +00:00
document . cookie = 'qz_auth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=.quixzoom.com; path=/;' ;
qzShowGuest ( ) ;
2026-07-14 09:58:53 +00:00
window . location . reload ( ) ;
} ;
2026-07-14 15:27:33 +00:00
// Close dropdown on outside click
document . addEventListener ( 'click' , function ( e ) {
if ( ! e . target . closest ( '.qz-user-menu' ) ) {
document . getElementById ( 'qz-dropdown' ) . classList . remove ( 'active' ) ;
}
} ) ;
// Check auth on page load
2026-07-14 09:58:53 +00:00
qzCheckAuth ( ) ;
2026-07-14 15:27:33 +00:00
// Re-check auth periodically (every 5 minutes)
setInterval ( qzCheckAuth , 5 * 60 * 1000 ) ;
// Listen for storage events (login from other tabs)
window . addEventListener ( 'storage' , function ( e ) {
if ( e . key === 'qz_auth_event' ) {
const event = JSON . parse ( e . newValue || '{}' ) ;
if ( event . type === 'login' ) {
qzCheckAuth ( ) ;
} else if ( event . type === 'logout' ) {
qzShowGuest ( ) ;
}
}
} ) ;
2026-07-14 09:58:53 +00:00
} ) ( ) ;
< / script >
2026-07-05 06:41:32 +00:00
< / body >
2026-07-12 12:41:35 +00:00
< / html >