// app-map.jsx — Interactive Europe map for Civify geography lessons // Country outlines come from app-map-data.js (real Natural Earth geometry). // Exports: window.EuropeMap, window.MapExercise, window.CIVIFY_MAP_NAMES (function () { const { useState } = React; const GEO = window.CIVIFY_MAP_GEO; const LANDS = GEO.LANDS; // interactive countries: { code: { name, d } } const BG_LANDS = GEO.BG; // non-interactive context shapes const VIEW = `0 0 ${GEO.W} ${GEO.H}`; const NAMES = {}; Object.keys(LANDS).forEach((k) => (NAMES[k] = LANDS[k].name)); // ── inject scoped styles once ─────────────────────────────── if (!document.getElementById('cf-map-styles')) { const s = document.createElement('style'); s.id = 'cf-map-styles'; s.textContent = ` .cf-map{border-radius:20px;border:1px solid var(--line);overflow:hidden; background:color-mix(in srgb,#6FA8CE 18%, var(--bg));} .cf-map svg{display:block;width:100%;height:auto;} .cf-map .land{fill:var(--card);stroke:color-mix(in srgb,var(--soft) 45%, transparent); stroke-width:0.9;stroke-linejoin:round;stroke-linecap:round;} .cf-map .land.bg{fill:color-mix(in srgb,var(--card) 45%, transparent); stroke:color-mix(in srgb,var(--soft) 22%, transparent);pointer-events:none;} .cf-map .land.pick{cursor:pointer;} @media (hover:hover){ .cf-map .land.pick:hover{fill:color-mix(in srgb,var(--o) 20%, var(--card));} } .cf-map .land.sel{fill:color-mix(in srgb,var(--o) 55%, var(--card)); stroke:var(--o);stroke-width:1.6;} .cf-map .land.hl{fill:color-mix(in srgb,var(--o) 55%, var(--card)); stroke:var(--o);stroke-width:1.6;animation:cfMapHl 1.6s ease-in-out infinite;} @media (prefers-reduced-motion: reduce){ .cf-map .land.hl{animation:none;} } @keyframes cfMapHl{0%,100%{opacity:1;}50%{opacity:.55;}} .cf-map .land.right{fill:#5CBE8E;stroke:#2FA36B;stroke-width:1.6;} .cf-map .land.wrong{fill:#EE9090;stroke:#E25555;stroke-width:1.6;} .cf-map .land.done-right{fill:#5CBE8E;stroke:#2FA36B;stroke-width:1.2;} .cf-map .land.done-miss{fill:color-mix(in srgb,var(--soft) 38%, var(--card)); stroke:color-mix(in srgb,var(--soft) 70%, transparent);stroke-width:1.2;} .cf-map .land.flash-wrong{fill:#EE9090;stroke:#E25555;stroke-width:1.6;animation:cfMapFlash .45s ease;} @keyframes cfMapFlash{0%{fill:#E25555;}100%{fill:#EE9090;}} .cf-map-cap{display:flex;align-items:center;justify-content:center;min-height:34px;margin-top:12px;} .cf-map-chip{display:inline-flex;align-items:center;gap:7px;background:var(--card); border:1px solid var(--line);border-radius:999px;padding:7px 14px; font-weight:800;font-size:13.5px;color:var(--ink);} .cf-map-chip .swatch{width:9px;height:9px;border-radius:99px;background:var(--o);flex-shrink:0;} .cf-map-chip.ok .swatch{background:#2FA36B;} .cf-map-chip.no .swatch{background:#E25555;} .cf-map-hint{font-size:12.5px;font-weight:600;color:var(--soft);} /* ── Seterra-style geography game ── */ .cf-game{position:absolute;inset:0;background:var(--bg);display:flex;flex-direction:column;z-index:45;} .cf-gtop{padding:58px 18px 10px;display:flex;align-items:center;gap:12px;flex-shrink:0;} .cf-gtop .cf-x{width:34px;height:34px;border-radius:99px;border:none;background:#f0e7dd;color:var(--soft); display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;} .cf-gprog{flex:1;height:11px;border-radius:99px;background:#efe6dc;overflow:hidden;} .cf-gprog > i{display:block;height:100%;background:linear-gradient(90deg,#46B377,#1F8A5B);border-radius:99px; transition:width .35s cubic-bezier(.4,0,.2,1);} .cf-gscore{display:inline-flex;align-items:center;gap:6px;background:var(--card);border:1px solid var(--line); border-radius:999px;padding:6px 12px;font-weight:800;font-size:13.5px;color:#1F8A5B;flex-shrink:0;} .cf-gbody{flex:1;display:flex;flex-direction:column;padding:8px 18px 14px;min-height:0;} .cf-gask{flex-shrink:0;padding:6px 2px 14px;} .cf-gask .cf-kick{color:#1F8A5B;} .cf-gask .name{font-family:'Sora',sans-serif;font-size:27px;font-weight:800;letter-spacing:-.02em;line-height:1.1;} .cf-gmapwrap{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;} .cf-gmapwrap .cf-map{width:auto;height:100%;max-width:100%;display:flex;align-items:center;justify-content:center; background:none;border:none;overflow:visible;} .cf-gmapwrap .cf-map svg{width:auto;height:100%;max-width:100%;max-height:100%;} .cf-gfeed{flex-shrink:0;min-height:30px;display:flex;align-items:center;justify-content:center; margin-top:10px;font-weight:700;font-size:14px;text-align:center;} .cf-gfeed.ok{color:#1E7D4E;} .cf-gfeed.no{color:#C0392B;} /* result */ .cf-gresult{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;align-items:center; justify-content:center;text-align:center;padding:32px;color:#fff; background:linear-gradient(160deg,#3FAE73,#1F8A5B 70%);} .cf-gresult .tw{width:96px;height:96px;border-radius:30px;background:rgba(255,255,255,.16); display:flex;align-items:center;justify-content:center;margin-bottom:22px;animation:cfPop .5s cubic-bezier(.2,1.4,.4,1);} @keyframes cfPop{from{transform:scale(.4);opacity:0;}} .cf-gresult h2{font-family:'Sora',sans-serif;font-size:30px;font-weight:800;margin:0 0 6px;} .cf-gresult .pct{font-family:'Sora',sans-serif;font-size:54px;font-weight:800;margin:6px 0;} .cf-gresult p{margin:0 0 26px;opacity:.92;font-weight:500;} .cf-gresult .row{display:flex;gap:12px;width:100%;max-width:320px;} .cf-gbtn{flex:1;border:none;border-radius:15px;padding:15px;font-family:'Sora',sans-serif;font-weight:700; font-size:15px;cursor:pointer;} .cf-gbtn.primary{background:#fff;color:#1F8A5B;} .cf-gbtn.ghost{background:rgba(255,255,255,.18);color:#fff;} `; document.head.appendChild(s); } // ── Map ───────────────────────────────────────────────────── // selected: picked country code · highlight: pulsing country (identify mode) // checked + target: show right/wrong state · onPick: tap handler // statuses: { code: 'correct' | 'missed' } (game mode) · flashWrong: code flashing red function EuropeMap({ selected, highlight, target, checked, onPick, statuses, flashWrong }) { const interactive = !!onPick && !checked; return (
{score} av {total} länder rätt på första försöket