{ const { attr, tier } = answer; // Determine user target value let userTarget; let cityValue; let weight; let label; if (attr === "political") { userTarget = POL_NUMERIC[tier] ?? 5; cityValue = cityPoliticalNumeric(city); weight = (tier === "polProg" || tier === "polStrict") ? 2.0 : 1.0; label = "Politik"; } else if (attr === "soul") { // Soul: average distance across all soul attrs using same tier target const rawTarget = TIER_TARGET[tier] ?? 5; let totalDist = 0; let counted = 0; SOUL_ATTRS.forEach(a => { if (city[a] === undefined) return; let cv = city[a]; // For inverted attrs within soul, flip the target const effectiveTarget = INVERTED_ATTRS.includes(a) ? (10.5 - rawTarget) : rawTarget; totalDist += Math.abs(effectiveTarget - cv); counted++; }); const avgDist = counted > 0 ? totalDist / counted : 5; weight = (tier === "elite" || tier === "hardcore") ? 2.0 : 1.0; totalWeightedDistance += avgDist * weight; maxWeightedDistance += 8.5 * weight; const pct = Math.round((1 - (avgDist * weight) / (8.5 * weight)) * 100); if (pct >= 70) matchDetails.push({ attr: "soul", pct, label: "🎭 City Vibe Match" }); return; } else { const rawTarget = TIER_TARGET[tier]; if (rawTarget === undefined) return; // For inverted attrs, flip: user answering A (elite) on pulse WANTS low pulse city userTarget = INVERTED_ATTRS.includes(attr) ? (10.5 - rawTarget) : rawTarget; cityValue = city[attr]; if (cityValue === undefined) return; weight = (tier === "elite" || tier === "hardcore") ? 2.0 : 1.0; label = attr; } const distance = Math.abs(userTarget - cityValue); const weighted = distance * weight; totalWeightedDistance += weighted; maxWeightedDistance += 8.5 * weight; // max possible distance per attr // Track good matches for "why this city" const pct = Math.round((1 - weighted / (8.5 * weight)) * 100); if (pct >= 70) matchDetails.push({ attr, pct, label }); }); // Warlord gate: cities with 3+ extreme danger attrs require hardcore intent const dangerCount = ["laptopSafety","internet","walk"].filter(a => city[a] <= 2).length; const hardcoreCount = answers.filter(a => a.tier === "hardcore" || a.tier === "polStrict" ).length; if (dangerCount >= 3 && hardcoreCount < 2) { totalWeightedDistance += 25; // penalty = large distance injection } // displayScore: 0-100 compatibility % const usedMax = maxWeightedDistance || MAX_POSSIBLE_DISTANCE; const displayScore = Math.max(0, Math.round((1 - totalWeightedDistance / usedMax) * 100)); return { score: -totalWeightedDistance, // negative distance = higher is better (for sorting) displayScore, // 0-100% for UI matchDetails: matchDetails .sort((a, b) => b.pct - a.pct) .slice(0, 3), totalWeightedDistance, }; } // ─── DEBUG: Simulate pure A-run and pure D-run ──────────────────────────────── // (runs once at module load, logs to console) function debugRuns() { const ATTRS_FOR_DEBUG = [ {attr:"laptopSafety", tier:"elite"}, {attr:"pulse", tier:"elite"}, {attr:"thirst", tier:"elite"}, {attr:"wealth", tier:"elite"}, {attr:"redTape", tier:"elite"}, {attr:"air", tier:"elite"}, {attr:"internet", tier:"elite"}, {attr:"political", tier:"polProg"}, {attr:"expatRatio", tier:"elite"}, {attr:"walk", tier:"elite"}, {attr:"soul", tier:"elite"}, ]; const D_ATTRS = ATTRS_FOR_DEBUG.map(a => ({ ...a, tier: a.tier === "polProg" ? "polStrict" : "hardcore" })); const scores = (answers) => cityData.map(city => ({ city: city.city, ...scoreCity(city, answers), })).sort((a,b) => b.displayScore - a.displayScore).slice(0,3); console.log("=== DEBUG: Pure A-Run (elite everywhere) ==="); scores(ATTRS_FOR_DEBUG).forEach((c,i) => console.log(` #${i+1} ${c.city}: ${c.displayScore}% (dist: ${c.totalWeightedDistance?.toFixed(1)})`) ); console.log("=== DEBUG: Pure D-Run (hardcore everywhere) ==="); scores(D_ATTRS).forEach((c,i) => console.log(` #${i+1} ${c.city}: ${c.displayScore}% (dist: ${c.totalWeightedDistance?.toFixed(1)})`) ); } setTimeout(debugRuns, 500); // ─── ANTI-REPEAT QUESTION SYSTEM ───────────────────────────────────────────── // 12 attribute groups → 1 random question each per session = 12 Qs per run. // Per-group used-queue resets independently when exhausted. const ATTR_GROUPS = [ "laptopSafety", // Q1–5 (5 questions) "pulse", // Q6–10 (5 questions, inverted tiers) "thirst", // Q11–15 (5 questions) "wealth", // Q16–20 (5 questions) "redTape", // Q21–25 (5 questions, inverted) "air", // Q26–30 (5 questions) "internet", // Q31–35 (5 questions) "political", // Q36–40 (5 questions, 4-tier numeric) "expatRatio", // Q41–43 (3 questions) "walk", // Q44–49 (6 questions, incl. third place) "soul", // Q50–60 (11 questions, composite scoring) ]; const sessionUsed = {}; ATTR_GROUPS.forEach(a => { sessionUsed[a] = []; }); function getNextQuestions() { const picked = []; ATTR_GROUPS.forEach(attr => { const groupPool = QUESTIONS_POOL.filter(q => q.attr === attr); if (groupPool.length === 0) return; if (sessionUsed[attr].length >= groupPool.length) sessionUsed[attr] = []; const available = groupPool.filter(q => !sessionUsed[attr].includes(q.id)); const chosen = available[Math.floor(Math.random() * available.length)]; if (chosen) { sessionUsed[attr].push(chosen.id); picked.push(chosen); } }); return picked.sort(() => Math.random() - 0.5); } function totalUsedCount() { return Object.values(sessionUsed).reduce((sum, arr) => sum + arr.length, 0); } const TOTAL_POOL_SIZE = QUESTIONS_POOL.length; const ATTR_LABELS = { laptopSafety: "🔒 Safety & Security", thirst: "💘 Dating & Social Life", pulse: "⚡ City Energy & Chaos", walk: "👟 Walkability", wealth: "💸 Purchasing Power", redTape: "📋 Bureaucracy Level", air: "☀️ Air & Sunlight", internet: "🌐 Internet Reliability", expatRatio: "🌍 Expat Community", political: "🗳️ Political Vibe", }; const FLAG_MAP = { "Germany":"🇩🇪","Portugal":"🇵🇹","USA":"🇺🇸","Japan":"🇯🇵","Netherlands":"🇳🇱", "France":"🇫🇷","UK":"🇬🇧","Spain":"🇪🇸","Italy":"🇮🇹","Austria":"🇦🇹", "Switzerland":"🇨🇭","Denmark":"🇩🇰","Sweden":"🇸🇪","Czech Republic":"🇨🇿", "Poland":"🇵🇱","Hungary":"🇭🇺","Greece":"🇬🇷","Singapore":"🇸🇬","Australia":"🇦🇺", "UAE":"🇦🇪","Canada":"🇨🇦","South Korea":"🇰🇷","China":"🇨🇳","Brazil":"🇧🇷", "Mexico":"🇲🇽","Turkey":"🇹🇷","Israel":"🇮🇱","Taiwan":"🇹🇼","India":"🇮🇳", "South Africa":"🇿🇦","Egypt":"🇪🇬","Malaysia":"🇲🇾","Iceland":"🇮🇸","Belgium":"🇧🇪", "Nigeria":"🇳🇬","Kenya":"🇰🇪","Indonesia":"🇮🇩","Colombia":"🇨🇴","Vietnam":"🇻🇳", "Bulgaria":"🇧🇬","Thailand":"🇹🇭","Estonia":"🇪🇪","Croatia":"🇭🇷","Georgia":"🇬🇪", "Bosnia":"🇧🇦","North Macedonia":"🇲🇰","Malta":"🇲🇹","Rwanda":"🇷🇼","Cambodia":"🇰🇭", "Serbia":"🇷🇸","Armenia":"🇦🇲","Ukraine":"🇺🇦","Uzbekistan":"🇺🇿","Ethiopia":"🇪🇹", "Senegal":"🇸🇳","Cuba":"🇨🇺","Bolivia":"🇧🇴","Paraguay":"🇵🇾","Uruguay":"🇺🇾", "Chile":"🇨🇱","Peru":"🇵🇪","Ecuador":"🇪🇨","Azerbaijan":"🇦🇿","Pakistan":"🇵🇰", "Myanmar":"🇲🇲","Mongolia":"🇲🇳","Nepal":"🇳🇵","Bangladesh":"🇧🇩","Saudi Arabia":"🇸🇦", "Lebanon":"🇱🇧","Tunisia":"🇹🇳","Morocco":"🇲🇦","Zimbabwe":"🇿🇼","Madagascar":"🇲🇬", "Angola":"🇦🇴","Kyrgyzstan":"🇰🇬","Tajikistan":"🇹🇯","Chad":"🇹🇩","Afghanistan":"🇦🇫", "North Korea":"🇰🇵","Somalia":"🇸🇴","Venezuela":"🇻🇪","Haiti":"🇭🇹","DRC":"🇨🇩", "Libya":"🇱🇾","Yemen":"🇾🇪","Syria":"🇸🇾","CAR":"🇨🇫","Guatemala":"🇬🇹", "Montenegro":"🇲🇪","Guyana":"🇬🇾","Suriname":"🇸🇷","Namibia":"🇳🇦","Tanzania":"🇹🇿", "Sri Lanka":"🇱🇰","Ghana":"🇬🇭","Kazakhstan":"🇰🇿","Argentina":"🇦🇷","Romania":"🇷🇴", "Samoa":"🇼🇸","Costa Rica":"🇨🇷","Laos":"🇱🇦", }; // ─── APP ────────────────────────────────────────────────────────────────────── export default function CitySoulmate() { const [phase, setPhase] = useState("intro"); const [questions, setQuestions] = useState([]); const [currentQ, setCurrentQ] = useState(0); const [answers, setAnswers] = useState([]); const [result, setResult] = useState(null); const [showWhy, setShowWhy] = useState(false); const [animIn, setAnimIn] = useState(true); const [selectedOpt, setSelectedOpt] = useState(null); const [usedCount, setUsedCount] = useState(0); const startQuiz = () => { const qs = getNextQuestions(10); setQuestions(qs); setCurrentQ(0); setAnswers([]); setResult(null); setShowWhy(false); setSelectedOpt(null); setAnimIn(true); setUsedCount(totalUsedCount()); setPhase("quiz"); }; const handleAnswer = (opt, q) => { setSelectedOpt(opt.label); setTimeout(() => { const newAnswer = { attr: q.attr, tier: opt.tier, questionText: q.text.slice(0, 45) + "…", }; const newAnswers = [...answers, newAnswer]; if (currentQ + 1 >= questions.length) { const scored = cityData.map(city => ({ ...city, ...scoreCity(city, newAnswers), })); scored.sort((a, b) => b.score - a.score); // Weighted random Top-5 selection const top5 = scored.slice(0, 5); const W = [0.40, 0.25, 0.20, 0.10, 0.05]; const rand = Math.random(); let cumul = 0, chosen = top5[0]; for (let i = 0; i < top5.length; i++) { cumul += W[i]; if (rand < cumul) { chosen = top5[i]; break; } } setResult(chosen); setPhase("result"); } else { setAnimIn(false); setTimeout(() => { setAnswers(newAnswers); setCurrentQ(currentQ + 1); setSelectedOpt(null); setAnimIn(true); }, 180); } }, 350); }; const progress = questions.length ? (currentQ / questions.length) * 100 : 0; return (
{/* Grid bg */}
{/* Glow orbs */}
{phase === "intro" && } {phase === "quiz" && questions.length > 0 && ( )} {phase === "result" && result && ( setShowWhy(!showWhy)} onRestart={startQuiz} /> )}
); } // ─── INTRO ──────────────────────────────────────────────────────────────────── function IntroScreen({ onStart, used, total }) { return (
2026 · v2.0 · 4-Option Engine

CITY
SOULMATE

The world's most brutally honest city matcher

{[ ["🏙️","149 Cities","from Zurich to Mogadishu"], ["❓","60 Questions","4 honest options each"], ["🔄","Anti-Repeat","Each question appears only once per session"], ["⚖️","Smart Scoring","Extreme cities demand extreme answers"], ].map(([icon,title,sub]) => (
{icon}
{title}
{sub}
))}
{used > 0 && (
Session: {used}/{total} questions seen {used >= total && · Pool reset ✓}
)}

10 QUESTIONS · 4 OPTIONS · WEIGHTED SCORING · NO FILTER

); } // ─── QUIZ ───────────────────────────────────────────────────────────────────── function QuizScreen({ q, qIndex, total, progress, onAnswer, animIn, selectedOpt }) { const optionLetters = ["A","B","C","D"]; const optionColors = ["#00d4aa","#4488ff","#ff8c42","#ff3264"]; return (
{/* Header */}
{q.cat} {qIndex+1} / {total}
{/* Progress */}
{/* Question */}

{q.text}

{q.opts.map((opt, i) => { const isSelected = selectedOpt === opt.label; const col = optionColors[i]; return ( ); })}
{/* Dots */}
{Array.from({length:total}).map((_,i) => (
))}
); } // ─── RESULT ─────────────────────────────────────────────────────────────────── const CITY_COLORS = { "Berlin":["50,50,80","180,30,60","255,140,60"], "Lisbon":["220,120,40","180,80,30","240,200,120"], "New York City":["30,30,50","60,100,180","220,220,240"], "Tokyo":["180,20,60","40,40,80","255,200,220"], "Amsterdam":["30,100,160","180,140,60","200,220,240"], "Paris":["80,60,100","200,160,80","230,210,190"], "London":["40,50,60","180,30,30","160,180,200"], "Barcelona":["220,80,30","240,180,40","60,140,200"], "Rome":["200,120,40","160,60,30","240,200,140"], "Vienna":["80,80,100","200,180,120","160,140,180"], "Zurich":["60,100,140","200,200,220","100,160,200"], "Copenhagen":["60,120,160","200,160,80","180,220,240"], "Stockholm":["40,80,120","180,200,220","220,180,100"], "Munich":["40,100,60","200,160,40","160,100,60"], "Madrid":["200,60,40","240,160,40","80,60,100"], "Prague":["100,60,40","180,140,80","60,80,120"], "Warsaw":["160,30,40","200,180,160","60,60,80"], "Budapest":["180,100,40","120,60,80","200,180,120"], "Athens":["200,180,120","60,100,160","240,220,180"], "Singapore":["0,160,140","40,40,60","180,240,220"], "Sydney":["30,120,200","220,160,60","180,220,255"], "Dubai":["200,160,60","40,40,50","255,220,120"], "Toronto":["160,30,40","60,80,120","200,200,220"], "San Francisco":["220,100,40","60,80,140","255,180,100"], "Seoul":["60,40,120","200,40,80","180,160,240"], "Hong Kong":["42,19,92","113,36,126","228,67,164"], "São Paulo":["46,19,92","118,36,126","228,67,155"], "Mexico City":["33,19,92","102,36,126","228,67,185"], "Istanbul":["19,92,45","36,126,116","67,159,228"], "Tel Aviv":["71,19,92","126,36,104","228,67,101"], "Melbourne":["19,92,21","36,126,87","67,212,228"], "Taipei":["92,89,19","81,126,36","67,228,73"], "Osaka":["92,19,56","126,39,36","228,159,67"], "Mumbai":["26,92,19","36,126,77","67,228,226"], "Cape Town":["19,74,92","36,55,126","120,67,228"], "Cairo":["86,92,19","70,126,36","67,228,94"], "Kuala Lumpur":["92,81,19","91,126,36","78,228,67"], "Reykjavik":["19,70,92","36,50,126","130,67,228"], "Brussels":["19,92,27","36,126,95","67,197,228"], "Los Angeles":["92,22,19","126,88,36","209,228,67"], "Chicago":["41,19,92","112,36,126","228,67,167"], "Delhi":["25,92,19","36,126,77","67,228,227"], "Bangalore":["92,19,86","126,36,69","228,94,67"], "Lagos":["19,92,41","36,126,111","67,167,228"], "Nairobi":["45,19,92","116,36,126","228,67,159"], "Canggu":["92,19,34","126,66,36","228,208,67"], "Medellin":["92,74,19","100,126,36","94,228,67"], "Da Nang":["19,19,92","85,36,126","228,67,214"], "Bansko":["81,19,92","126,36,92","228,67,80"], "Las Palmas":["19,92,71","36,103,126","67,100,228"], "Chiang Mai":["92,52,19","126,125,36","143,228,67"], "Playa del Carmen":["78,92,19","60,126,36","67,228,112"], "Tbilisi":["92,69,19","105,126,36","104,228,67"], "Tallinn":["33,19,92","102,36,126","228,67,184"], "Split":["92,19,25","126,78,36","227,228,67"], "Tulum":["92,87,19","84,126,36","67,228,69"], "Bali (Ubud)":["19,92,74","36,100,126","67,95,228"], "Porto":["73,92,19","54,126,36","67,228,122"], "Valencia":["92,82,19","90,126,36","76,228,67"], "Oaxaca":["19,92,33","36,126,102","67,183,228"], "Chiang Rai":["19,92,28","36,126,95","67,196,228"], "Medellín":["92,47,19","126,119,36","154,228,67"], "Florianópolis":["74,92,19","55,126,36","67,228,121"], "Madeira":["19,61,92","36,40,126","148,67,228"], "Phuket":["34,92,19","36,126,67","67,228,209"], "Penang":["92,65,19","111,126,36","115,228,67"], "Ho Chi Minh City":["19,89,92","36,73,126","88,67,228"], "Hoi An":["54,19,92","126,36,124","228,67,138"], "Koh Lanta":["92,19,20","126,83,36","217,228,67"], "Batumi":["81,19,92","126,36,92","228,67,80"], "Diani Beach":["92,19,43","126,56,36","228,188,67"], "Mostar":["50,19,92","123,36,126","228,67,146"], "Ohrid":["19,19,92","85,36,126","228,67,215"], "Plovdiv":["22,19,92","88,36,126","228,67,210"], "Valletta":["92,29,19","126,97,36","193,228,67"], "San Cristóbal":["46,19,92","117,36,126","228,67,157"], "Cali":["92,19,73","126,36,54","228,122,67"], "Kigali":["92,19,23","126,81,36","222,228,67"], "Phnom Penh":["92,54,19","124,126,36","137,228,67"], "Zadar":["92,19,22","126,81,36","222,228,67"], "Palermo":["92,39,19","126,109,36","171,228,67"], "Brno":["92,19,45","126,53,36","228,183,67"], "Lake Atitlán":["92,23,19","126,89,36","207,228,67"], "Antigua":["19,92,57","36,121,126","67,132,228"], "Siem Reap":["38,19,92","108,36,126","228,67,172"], "Uvita":["19,92,56","36,122,126","67,134,228"], "Luang Prabang":["92,19,52","126,44,36","228,168,67"], "Holbox":["75,19,92","126,36,99","228,67,92"], "Sayulita":["26,19,92","94,36,126","228,67,199"], "Kotor":["26,19,92","93,36,126","228,67,199"], "Georgetown":["19,92,60","36,117,126","67,125,228"], "Paramaribo":["19,88,92","36,72,126","90,67,228"], "Funchal":["92,72,19","103,126,36","99,228,67"], "Nha Trang":["19,72,92","36,52,126","125,67,228"], "Tarifa":["19,92,28","36,126,95","67,196,228"], "Ponta Delgada":["92,19,42","126,57,36","228,190,67"], "Skopje":["22,19,92","89,36,126","228,67,208"], "Timișoara":["61,19,92","126,36,115","228,67,122"], "Iași":["60,19,92","126,36,117","228,67,125"], "Huatulco":["92,72,19","102,126,36","98,228,67"], "Apia":["92,19,58","126,37,36","228,155,67"], "Windhoek":["31,92,19","36,126,70","67,228,214"], "Dar es Salaam":["19,35,92","66,36,126","207,67,228"], "Colombo":["36,92,19","36,126,64","67,228,204"], "Accra":["20,92,19","36,126,84","67,216,228"], "Almaty":["39,92,19","36,126,60","67,228,196"], "Buenos Aires":["92,20,19","126,86,36","213,228,67"], "Belgrade":["19,81,92","36,64,126","105,67,228"], "Bogotá":["88,19,92","126,36,83","228,70,67"], "Kyiv":["30,19,92","98,36,126","228,67,191"], "Tashkent":["48,92,19","36,126,49","67,228,176"], "Yerevan":["92,19,72","126,36,53","228,124,67"], "Addis Ababa":["80,19,92","126,36,93","228,67,82"], "Dakar":["19,44,92","55,36,126","187,67,228"], "Havana":["19,92,52","36,126,125","67,142,228"], "La Paz":["92,19,71","126,36,51","228,127,67"], "Asunción":["19,92,41","36,126,112","67,166,228"], "Montevideo":["23,92,19","36,126,80","67,223,228"], "Santiago":["19,89,92","36,73,126","87,67,228"], "Lima":["19,92,26","36,126,93","67,200,228"], "Quito":["92,19,79","126,36,62","228,109,67"], "Baku":["26,19,92","94,36,126","228,67,199"], "Islamabad":["19,92,39","36,126,109","67,172,228"], "Karachi":["56,19,92","126,36,122","228,67,134"], "Yangon":["19,46,92","52,36,126","181,67,228"], "Ulaanbaatar":["92,34,19","126,103,36","182,228,67"], "Kathmandu":["92,19,43","126,56,36","228,189,67"], "Dhaka":["29,92,19","36,126,72","67,228,219"], "Riyadh":["43,19,92","114,36,126","228,67,162"], "Beirut":["92,90,19","80,126,36","67,228,76"], "Tunis":["19,61,92","36,39,126","149,67,228"], "Casablanca":["25,19,92","91,36,126","228,67,203"], "Marrakech":["92,19,68","126,36,48","228,132,67"], "Harare":["19,34,92","66,36,126","207,67,228"], "Antananarivo":["19,92,61","36,115,126","67,122,228"], "Luanda":["92,19,56","126,39,36","228,159,67"], "Bishkek":["92,26,19","126,94,36","199,228,67"], "Dushanbe":["92,37,19","126,107,36","176,228,67"], "N'Djamena":["92,89,19","81,126,36","67,228,73"], "Kabul":["92,50,19","126,122,36","148,228,67"], "Pyongyang":["92,19,88","126,36,72","228,90,67"], "Mogadishu":["19,70,92","36,50,126","130,67,228"], "Caracas":["19,92,44","36,126,115","67,160,228"], "Port-au-Prince":["24,92,19","36,126,79","67,225,228"], "Kinshasa":["19,92,62","36,115,126","67,121,228"], "Tripoli":["92,19,78","126,36,60","228,111,67"], "Sanaa":["29,92,19","36,126,72","67,228,219"], "Homs":["92,19,60","126,36,38","228,150,67"], "Bangui":["86,19,92","126,36,85","228,67,67"], }; // ─── HERO BANNER ────────────────────────────────────────────────────────────── function HeroBanner({ city, col }) { const colors = CITY_COLORS[city.city] || ["40,40,70","100,60,100","180,100,140"]; const [p, s, a] = colors; const bg = "linear-gradient(135deg, rgba("+p+",0.95) 0%, rgba("+s+",0.80) 50%, rgba("+a+",0.60) 100%)"; const fade = "linear-gradient(to top, rgba(0,0,0,0.80) 0%, rgba(0,0,0,0.15) 55%, transparent 100%)"; return (
{/* Bottom fade — text legibility */}
{/* Soul badge — top right */}
{city.soul}
{/* City + country — bottom left */}

{city.city}

{city.country}

); } function ResultScreen({ city, showWhy, onToggleWhy, onRestart }) { const flag = FLAG_MAP[city.country] || "🌍"; const soulColor = () => { const s = city.soul.toLowerCase(); if (s.includes("dystopia")||s.includes("chaos")||s.includes("anarchic")||s.includes("survival")) return "#ff3264"; if (s.includes("wealthy")||s.includes("steril-rich")||s.includes("gold")||s.includes("steril")) return "#ffd700"; if (s.includes("zen")||s.includes("slow")||s.includes("paradise")||s.includes("nature")) return "#00d4aa"; if (s.includes("hustle")||s.includes("grinder")||s.includes("burnout")||s.includes("speed")) return "#ff8c42"; if (s.includes("lonely")||s.includes("lonely")||s.includes("isolation")||s.includes("isolated")) return "#8888ff"; return "#ff3264"; }; const col = soulColor(); const statRows = [ {icon:"🔒",label:"Safety",val:city.laptopSafety,key:"laptopSafety"}, {icon:"💘",label:"Dating",val:city.thirst,key:"thirst"}, {icon:"⚡",label:"Chaos",val:city.pulse,key:"pulse"}, {icon:"💸",label:"Value",val:city.wealth,key:"wealth"}, {icon:"☀️",label:"Air",val:city.air,key:"air"}, {icon:"👟",label:"Walk",val:city.walk,key:"walk"}, {icon:"🌐",label:"Net",val:city.internet,key:"internet"}, {icon:"📋",label:"Tape",val:city.redTape,key:"redTape"}, ]; const barColor = (v) => v >= 8 ? "#00d4aa" : v >= 5 ? "#4488ff" : v >= 3 ? "#ff8c42" : "#ff3264"; return (
{/* Match header */}
🎯 Your City Soulmate 2026
{flag}
{/* Hero card */}
{/* Hero visual */} {/* Score bar */}
Match Score {city.displayScore ?? 0}% Match
{/* Political badge */}
{city.political==="Liberal"?"🟢":"🟡"} {city.political} · {city.country}
{/* Stats grid with mini bars */}
{statRows.map(s => (
{s.icon}
{s.val}
{s.label}
))}
{/* LOCAL INTEL */}
🔍 Local Intel
🎯 Best For

{city.bestFor}

🤫 Secret Tip

{city.secretTip}

{/* Hard Truth */}
⚠️ The Hard Truth

"{city.truth}"

{/* Why this city */}
{showWhy && city.matchDetails && city.matchDetails.length > 0 && (

Top Match Factors

{city.matchDetails.map((m,i)=>(
{ATTR_LABELS[m.attr]||m.attr}
{m.label}
{m.pct}%
))}
)}
{/* Anti-repeat indicator */}
{totalUsedCount()}/{QUESTIONS_POOL.length} questions seen from pool {totalUsedCount() >= QUESTIONS_POOL.length && " · Pool resets on next start"}
{/* Restart */}

149 CITIES · 4-OPTION SCORING · ANTI-REPEAT · CORE-CATEGORY GATE

); }