1a12fb870b
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
270 lines
18 KiB
HTML
270 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
||
<title>quiXzoom Urban Intelligence Map</title>
|
||
<meta name="description" content="Live map showing Urban Intelligence assessments, mission availability and Contradiction Scores across cities worldwide."/>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
||
:root{
|
||
--bg:#0d1117;--surface:#161b22;--surface2:#21262d;
|
||
--border:rgba(255,255,255,0.08);--text:#e6edf3;--text2:rgba(230,237,243,0.6);
|
||
--green:#3fb950;--yellow:#d29922;--orange:#f0883e;--red:#f85149;
|
||
--blue:#58a6ff;--safe-top:env(safe-area-inset-top,0px);--safe-bottom:env(safe-area-inset-bottom,0px);
|
||
}
|
||
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);overflow:hidden}
|
||
#map{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1}
|
||
.leaflet-control-zoom{margin-top:calc(var(--safe-top) + 64px) !important;margin-left:16px !important}
|
||
.leaflet-control-zoom a{background:var(--surface) !important;color:var(--text) !important;border-color:var(--border) !important;width:34px !important;height:34px !important;line-height:34px !important;border-radius: var(--radius-md) !important;font-size:18px !important}
|
||
#hdr{position:fixed;top:0;left:0;right:0;z-index:1000;background:rgba(13,17,23,0.9);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:calc(var(--safe-top) + 10px) 16px 10px}
|
||
#hdr-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;gap:12px}
|
||
#logo{font-size:15px;font-weight:700;color:var(--text);text-decoration:none;white-space:nowrap}
|
||
#logo span{color:var(--green)}
|
||
#hdr-sub{flex:1;font-size:13px;color:var(--text2)}
|
||
.badge{font-size:10px;font-weight:700;letter-spacing:0.8px;text-transform:uppercase;background:rgba(63,185,80,0.15);color:var(--green);border:1px solid rgba(63,185,80,0.3);border-radius: var(--radius-xl);padding:3px 9px}
|
||
#ui-tog{position:fixed;top:calc(var(--safe-top) + 62px);right:16px;z-index:1000;background:rgba(22,27,34,0.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius: var(--radius-md);padding:7px 11px;display:flex;align-items:center;gap:7px;cursor:pointer;font-size:12px;font-weight:600;color:var(--text);user-select:none;transition:opacity 0.2s}
|
||
#ui-tog:active{opacity:0.7}
|
||
#ui-led{width:8px;height:8px;border-radius: var(--radius-full);background:var(--green);flex-shrink:0;transition:background 0.2s}
|
||
#ui-tog.off #ui-led{background:var(--text2)}
|
||
#panel{position:fixed;bottom:0;left:0;right:0;z-index:900;background:rgba(22,27,34,0.94);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-top:1px solid var(--border);padding:10px 16px calc(var(--safe-bottom) + 10px)}
|
||
#panel-inner{max-width:1200px;margin:0 auto}
|
||
#ph{width:36px;height:4px;border-radius: var(--radius-sm);background:rgba(255,255,255,0.12);margin:0 auto 10px}
|
||
#leg{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-bottom:8px}
|
||
.li{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text2);background:var(--surface2);border-radius: var(--radius-xl);padding:4px 10px;border:1px solid var(--border)}
|
||
.ld{width:9px;height:9px;border-radius: var(--radius-full);flex-shrink:0}
|
||
#note{font-size:11px;color:var(--text2);text-align:center;line-height:1.4}
|
||
#note a{color:var(--blue);text-decoration:none}
|
||
.leaflet-popup-content-wrapper{background:var(--surface) !important;border:1px solid var(--border) !important;border-radius: var(--radius-md) !important;box-shadow:0 8px 32px rgba(0,0,0,0.6) !important;padding:0 !important}
|
||
.leaflet-popup-content{margin:0 !important;width:260px !important;color:var(--text)}
|
||
.leaflet-popup-tip{background:var(--surface) !important}
|
||
.leaflet-popup-close-button{color:var(--text2) !important;top:8px !important;right:10px !important}
|
||
.zpop{padding:14px 16px}
|
||
.zpop-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:10px}
|
||
.zpop-name{font-size:14px;font-weight:700;color:var(--text);line-height:1.3}
|
||
.zpop-conf{font-size:11px;color:var(--text2);background:var(--surface2);border-radius: var(--radius-sm);padding:2px 7px;white-space:nowrap;flex-shrink:0;font-weight:600}
|
||
.zpop-scores{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:10px}
|
||
.zpop-sc{background:var(--surface2);border-radius: var(--radius-sm);padding:6px 8px}
|
||
.zpop-sl{font-size:10px;color:var(--text2);font-weight:500;margin-bottom:3px}
|
||
.zpop-bar{height:3px;border-radius: var(--radius-sm);background:rgba(255,255,255,0.08);margin-bottom:2px}
|
||
.zpop-fill{height:100%;border-radius: var(--radius-sm)}
|
||
.zpop-sv{font-size:12px;font-weight:700;color:var(--text)}
|
||
.zpop-c{border-radius: var(--radius-md);padding:7px 10px;font-size:11px;line-height:1.5;margin-bottom:8px}
|
||
.zpop-c.low{background:rgba(63,185,80,0.1);color:#7ee787;border:1px solid rgba(63,185,80,0.2)}
|
||
.zpop-c.medium{background:rgba(240,136,62,0.1);color:#ffa657;border:1px solid rgba(240,136,62,0.2)}
|
||
.zpop-c.high{background:rgba(248,81,73,0.1);color:#ff7b72;border:1px solid rgba(248,81,73,0.2)}
|
||
.zpop-cl{font-weight:700;display:block;margin-bottom:2px}
|
||
.zpop-d{font-size:10px;color:var(--text2);border-top:1px solid var(--border);padding-top:7px;line-height:1.4}
|
||
.mpop{padding:14px 16px}
|
||
.mpop-cat{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:var(--blue);background:rgba(88,166,255,0.1);border-radius: var(--radius-sm);padding:2px 7px;display:inline-block;margin-bottom:7px}
|
||
.mpop-title{font-size:14px;font-weight:600;color:var(--text);line-height:1.3;margin-bottom:10px}
|
||
.mpop-meta{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:10px}
|
||
.mpop-item{background:var(--surface2);border-radius: var(--radius-md);padding:7px 9px}
|
||
.mpop-lbl{font-size:10px;color:var(--text2);margin-bottom:2px;font-weight:500}
|
||
.mpop-val{font-size:13px;font-weight:700;color:var(--text)}
|
||
.mpop-reward{color:var(--green)}
|
||
.mpop-risk{display:flex;align-items:center;justify-content:space-between;gap:8px;border-top:1px solid var(--border);padding-top:9px}
|
||
.rb{font-size:10px;font-weight:700;border-radius: var(--radius-sm);padding:3px 8px}
|
||
.rb.L1{background:rgba(63,185,80,0.15);color:#7ee787}
|
||
.rb.L2{background:rgba(210,153,34,0.15);color:#e3b341}
|
||
.rb.L3{background:rgba(240,136,62,0.15);color:#ffa657}
|
||
.rb.L4{background:rgba(248,81,73,0.15);color:#ff7b72}
|
||
.rh{font-size:10px;color:var(--text2);text-align:right;line-height:1.3}
|
||
@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; }
|
||
}
|
||
/* QUIXZOOM STANDARD — Dark Theme */
|
||
:root {
|
||
--qz-bg: #0a0a0a;
|
||
--qz-surface: #111111;
|
||
--qz-surface-2: #1a1a1a;
|
||
--qz-border: rgba(255,255,255,0.08);
|
||
--qz-text: #e8e8e8;
|
||
--qz-text-dim: #a0a0a0;
|
||
--qz-text-muted: #6b6b6b;
|
||
--qz-blue: #007AFF;
|
||
--qz-blue-dark: #0056CC;
|
||
--qz-radius: 10px;
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
|
||
background: var(--qz-bg);
|
||
color: var(--qz-text);
|
||
line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* Navigation */
|
||
.qz-nav {
|
||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 0 24px; height: 56px;
|
||
background: rgba(255,255,255,0.92);
|
||
backdrop-filter: blur(16px);
|
||
border-bottom: 1px solid var(--qz-border);
|
||
}
|
||
|
||
.qz-nav-logo { font-size: 18px; font-weight: 700; color: #fff; text-decoration: none; }
|
||
.qz-nav-links { display: flex; gap: 24px; list-style: none; }
|
||
.qz-nav-links a { font-size: 14px; color: var(--qz-text-dim); text-decoration: none; transition: color 0.2s; }
|
||
.qz-nav-links a:hover { color: #fff; }
|
||
|
||
/* Buttons */
|
||
.qz-btn {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 12px 24px; border-radius: var(--qz-radius);
|
||
font-size: 15px; font-weight: 600; text-decoration: none;
|
||
transition: all 0.2s; cursor: pointer; border: none;
|
||
}
|
||
|
||
.qz-btn-primary {
|
||
background: var(--qz-blue); color: #fff;
|
||
}
|
||
.qz-btn-primary:hover {
|
||
background: var(--qz-blue-dark); transform: translateY(-1px);
|
||
}
|
||
|
||
.qz-btn-ghost {
|
||
background: transparent; color: var(--qz-text);
|
||
border: 1px solid var(--qz-border);
|
||
}
|
||
.qz-btn-ghost:hover {
|
||
border-color: var(--qz-blue); color: var(--qz-blue);
|
||
}
|
||
|
||
/* Cards */
|
||
.qz-card {
|
||
background: var(--qz-surface);
|
||
border: 1px solid var(--qz-border);
|
||
border-radius: var(--qz-radius);
|
||
padding: 24px;
|
||
transition: transform 0.2s, border-color 0.2s;
|
||
}
|
||
.qz-card:hover {
|
||
transform: translateY(-2px);
|
||
border-color: rgba(0,122,255,0.3);
|
||
}
|
||
|
||
/* Footer */
|
||
.qz-footer {
|
||
border-top: 1px solid var(--qz-border);
|
||
padding: 40px 24px;
|
||
text-align: center;
|
||
color: var(--qz-text-dim);
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Banner */
|
||
.qz-banner {
|
||
background: #001E50;
|
||
color: rgba(255,255,255,0.55);
|
||
text-align: center;
|
||
padding: 10px;
|
||
font-size: 12px;
|
||
}
|
||
.qz-banner a { color: rgba(255,255,255,0.8); text-decoration: none; }
|
||
|
||
@media (max-width: 768px) {
|
||
.qz-nav-links { display: none; }
|
||
}
|
||
</style>
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:title" content="quiXzoom Urban Intelligence Map">
|
||
<meta property="og:description" content="Live map showing Urban Intelligence assessments, mission availability and Contradiction Scores across cities worldwide.">
|
||
<meta property="og:url" content="https://www.quixzoom.com/urban-map.html">
|
||
<meta property="og:image" content="https://www.quixzoom.com/images/mission-map.jpg">
|
||
<meta property="og:image:width" content="1200">
|
||
<meta property="og:image:height" content="630">
|
||
<meta property="og:image:alt" content="quiXzoom urban mission map">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="twitter:image" content="https://www.quixzoom.com/images/mission-map.jpg">
|
||
<meta name="twitter:title" content="quiXzoom Urban Intelligence Map">
|
||
<script type="module" src="https://auth.quixzoom.com/site-auth-snippet.js"></script>
|
||
</head>
|
||
<body>
|
||
<h1 style="position:absolute;left:-9999px;">Urban Map - quiXzoom</h1>
|
||
<div id="map"></div>
|
||
<div id="hdr"><div id="hdr-inner">
|
||
<a href="https://www.quixzoom.com" id="logo">quiX<span>zoom</span></a>
|
||
<div id="hdr-sub">Urban Intelligence Map</div>
|
||
<div class="badge">BETA</div>
|
||
</div></div>
|
||
<div id="ui-tog"><div id="ui-led"></div><span>Urban Intelligence</span></div>
|
||
<div id="panel"><div id="panel-inner">
|
||
<div id="ph"></div>
|
||
<div id="leg">
|
||
<div class="li"><div class="ld" style="background:#3fb950"></div>Low risk</div>
|
||
<div class="li"><div class="ld" style="background:#d29922"></div>Normal</div>
|
||
<div class="li"><div class="ld" style="background:#f0883e"></div>Elevated</div>
|
||
<div class="li"><div class="ld" style="background:#f85149"></div>High risk</div>
|
||
<div class="li"><div class="ld" style="background:#8b0000"></div>Restricted</div>
|
||
</div>
|
||
<div id="note">Landvex Assessment — official sources + quiXzoom observations. <a href="https://www.landvex.com/methodology" target="_blank">Methodology →</a></div>
|
||
</div></div>
|
||
<script>
|
||
const map = L.map('map',{center:[20,10],zoom:3,zoomControl:true,attributionControl:false});
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19}).addTo(map);
|
||
L.control.attribution({position:'bottomleft',prefix:'© <a href="https://openstreetmap.org">OpenStreetMap</a>'}).addTo(map);
|
||
|
||
const ZONES=[
|
||
{name:'Stockholm Innerstad',a:'green',conf:96,s:{inf:9.1,saf:8.7,trf:6.2,ngt:7.8},c:'low',ct:'Observed environment aligns well with official data.',coords:[[59.340,18.040],[59.340,18.100],[59.310,18.100],[59.310,18.040]]},
|
||
{name:'Rinkeby–Kista',a:'orange',conf:81,s:{inf:6.2,saf:5.1,trf:7.1,ngt:4.2},c:'high',ct:'Deviation: official data indicates medium risk, but observations show 41% faulty street lighting, high graffiti incidence and low evening activity.',coords:[[59.390,17.910],[59.390,17.970],[59.360,17.970],[59.360,17.910]]},
|
||
{name:'Göteborg Centrum',a:'yellow',conf:88,s:{inf:7.8,saf:7.2,trf:5.9,ngt:6.5},c:'medium',ct:'Traffic risk underreported in official data vs observed congestion.',coords:[[57.720,11.950],[57.720,12.000],[57.695,12.000],[57.695,11.950]]},
|
||
{name:'Malmö Centrum',a:'yellow',conf:79,s:{inf:7.5,saf:6.4,trf:6.8,ngt:5.9},c:'medium',ct:'Night risk deviates from official statistics. More observations needed.',coords:[[55.615,13.000],[55.615,13.030],[55.595,13.030],[55.595,13.000]]},
|
||
{name:'London City',a:'green',conf:91,s:{inf:8.9,saf:8.1,trf:5.4,ngt:7.2},c:'low',ct:'High confidence. Observed environment consistent with official data.',coords:[[51.520,-0.110],[51.520,-0.050],[51.495,-0.050],[51.495,-0.110]]},
|
||
{name:'Bangkok Sukhumvit',a:'yellow',conf:74,s:{inf:7.1,saf:6.8,trf:4.2,ngt:6.1},c:'medium',ct:'Traffic risk significantly higher than official classification.',coords:[[13.760,100.550],[13.760,100.590],[13.735,100.590],[13.735,100.550]]},
|
||
{name:'Bangkok Klong Toei',a:'orange',conf:62,s:{inf:5.3,saf:4.8,trf:6.1,ngt:3.2},c:'high',ct:'Observed environment deviates significantly from official classification. Low confidence — additional observations needed.',coords:[[13.725,100.550],[13.725,100.590],[13.700,100.590],[13.700,100.550]]},
|
||
{name:'São Paulo Centro',a:'red',conf:71,s:{inf:6.1,saf:3.4,trf:4.8,ngt:2.1},c:'high',ct:'High night risk. Official data underrepresents observed conditions after 19:00.',coords:[[-23.545,-46.645],[-23.545,-46.620],[-23.570,-46.620],[-23.570,-46.645]]}
|
||
];
|
||
|
||
const AS={green:{color:'#3fb950',fo:0.12,w:1.5,d:null},yellow:{color:'#d29922',fo:0.15,w:1.5,d:'5 4'},orange:{color:'#f0883e',fo:0.20,w:2,d:'5 4'},red:{color:'#f85149',fo:0.25,w:2,d:'4 3'},darkred:{color:'#8b0000',fo:0.30,w:2.5,d:'4 3'}};
|
||
const CL={low:'Contradiction Score: Low',medium:'Contradiction Score: Medium',high:'Contradiction Score: HIGH <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg>'};
|
||
|
||
function bar(v){const p=Math.round(v*10),c=v>=7.5?'#3fb950':v>=5?'#d29922':'#f85149';return `<div class="zpop-bar"><div class="zpop-fill" style="width:${p}%;background:${c}"></div></div>`;}
|
||
|
||
function zpop(z){return `<div class="zpop">
|
||
<div class="zpop-head"><span class="zpop-name">${z.name}</span><span class="zpop-conf">Confidence ${z.conf}%</span></div>
|
||
<div class="zpop-scores">
|
||
<div class="zpop-sc"><div class="zpop-sl">Infrastructure</div>${bar(z.s.inf)}<div class="zpop-sv">${z.s.inf}/10</div></div>
|
||
<div class="zpop-sc"><div class="zpop-sl">Safety</div>${bar(z.s.saf)}<div class="zpop-sv">${z.s.saf}/10</div></div>
|
||
<div class="zpop-sc"><div class="zpop-sl">Traffic risk</div>${bar(z.s.trf)}<div class="zpop-sv">${z.s.trf}/10</div></div>
|
||
<div class="zpop-sc"><div class="zpop-sl">Night risk</div>${bar(z.s.ngt)}<div class="zpop-sv">${z.s.ngt}/10</div></div>
|
||
</div>
|
||
<div class="zpop-c ${z.c}"><span class="zpop-cl">${CL[z.c]}</span>${z.ct}</div>
|
||
<div class="zpop-d">Landvex Assessment — official sources + quiXzoom observations. Not a factual statement about actual conditions.</div>
|
||
</div>`;}
|
||
|
||
let ug=L.layerGroup().addTo(map),uOn=true;
|
||
function renderZ(){ug.clearLayers();if(!uOn)return;ZONES.forEach(z=>{const st=AS[z.a]||AS.yellow;L.polygon(z.coords,{color:st.color,fillColor:st.color,fillOpacity:st.fo,weight:st.w,dashArray:st.d}).bindPopup(zpop(z),{maxWidth:300,minWidth:260}).addTo(ug);});}
|
||
renderZ();
|
||
document.getElementById('ui-tog').onclick=()=>{uOn=!uOn;document.getElementById('ui-tog').classList.toggle('off',!uOn);renderZ();};
|
||
|
||
const RISK={L1:{l:'L1 — Public Safe',h:'All verified Zoomers'},L2:{l:'L2 — Intermediate',h:'Experience + quality history'},L3:{l:'L3 — Advanced',h:'High reputation + safety training'},L4:{l:'L4 — Restricted',h:'Manual review — team mission'}};
|
||
function rl(m){if(m.risk_level)return m.risk_level;const r=Number(m.reward_sek)||0;return r>=100?'L4':r>=60?'L3':r>=35?'L2':'L1';}
|
||
function pin(s){const c=s==='open'?'#58a6ff':s==='assigned'?'#f0883e':'#3fb950';return L.divIcon({className:'',iconSize:[30,38],iconAnchor:[15,38],popupAnchor:[0,-40],html:`<svg xmlns="http://www.w3.org/2000/svg" width="30" height="38" viewBox="0 0 30 38"><filter id="d"><feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.5)"/></filter><g filter="url(#d)"><circle cx="15" cy="15" r="12" fill="${c}" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/><path d="M15 27 L15 38" stroke="${c}" stroke-width="1.5" opacity="0.4"/></g></svg>`});}
|
||
function mpop(m){const r=rl(m),rw=m.reward_sek?`${Number(m.reward_sek).toLocaleString()} SEK`:'—',cat=(m.category||'Mission').replace(/_/g,' ');return `<div class="mpop"><div class="mpop-cat">${cat}</div><div class="mpop-title">${(m.title||'').replace(/</g,'<')}</div><div class="mpop-meta"><div class="mpop-item"><div class="mpop-lbl">Reward</div><div class="mpop-val mpop-reward">${rw}</div></div><div class="mpop-item"><div class="mpop-lbl">Status</div><div class="mpop-val">Open</div></div></div><div class="mpop-risk"><span class="rb ${r}">${RISK[r].l}</span><span class="rh">${RISK[r].h}</span></div></div>`;}
|
||
|
||
async function loadMissions(){
|
||
try{
|
||
const r=await fetch('https://www.quixzoom.com/api/qz/missions?limit=200&status=open');
|
||
const d=await r.json();
|
||
(Array.isArray(d)?d:(d.missions||[])).forEach(m=>{
|
||
if(!m.lat||!m.lng)return;
|
||
L.marker([m.lat,m.lng],{icon:pin(m.status)}).bindPopup(mpop(m),{maxWidth:280,minWidth:260}).addTo(map);
|
||
});
|
||
}catch(e){console.warn('Missions unavailable:',e);}
|
||
}
|
||
loadMissions();
|
||
</script>
|
||
</body>
|
||
</html>
|