// Leaflet is loaded via CDN in index.html // @ts-expect-error - Leaflet global const L = window.L; export interface MapMarker { id: string; lat: number; lng: number; label: string; dreadLevel: number; // 0-3 } interface LocationInfo { lat: number; lng: number; label: string; // A standing description of the place itself, shown in the art modal — // distinct from LOCATION_VISITS' randomized per-visit outcome text in // engine.ts, which changes every time. This doesn't. blurb: string; } const LOCATIONS: Record = { rosengard: { lat: 55.5894, lng: 13.0414, label: 'Källaren i Rosengård', blurb: 'Ett källarförråd som blev något annat. Ingen av er minns exakt när. Hyresvärden vet fortfarande inte om det.', }, varnhem: { lat: 55.5982, lng: 13.0058, label: 'Värnhemstorget', blurb: 'Ett vanligt torg med spårvagnshållplats och krita på asfalten som inte suddas, hur mycket det än regnar.', }, torso: { lat: 55.6130, lng: 12.9758, label: 'Turning Torso', blurb: 'Calatrava-Santiago ritade byggnaden som en vridande ryggrad. Det visade sig inte vara en metafor.', }, oresund: { lat: 55.5789, lng: 12.9200, label: 'Øresundsbron', blurb: 'Bron till Danmark. Vattnet under den är stillare än det borde vara, oftare än det borde vara.', }, ribersborg: { lat: 55.6063, lng: 12.9652, label: 'Ribersborgsstranden', blurb: 'En strand med kallbadhus och änder som står stilla i vattenbrynet, vända mot samma håll, oavsett väder.', }, davidshall: { lat: 55.5963, lng: 13.0021, label: 'Davidshallsgatan', blurb: 'Ett bostadshus med kakel från 1923 i trapphuset. Kallt att röra vid, oavsett årstid.', }, stortorget: { lat: 55.6047, lng: 13.0038, label: 'Stortorget', blurb: 'Stadens gamla torg, statyn i mitten, klockor på fasaderna runtom som sällan visar exakt samma tid.', }, mollan: { lat: 55.5904, lng: 13.0203, label: 'Möllevångstorget', blurb: 'Marknad, dofter från femtio kök samtidigt, musik från tre högtalare som aldrig riktigt spelar samma låt.', }, universitet: { lat: 55.5721, lng: 12.9998, label: 'Malmö universitet', blurb: 'Bibliotek och föreläsningssalar. Ett rum för sällan efterfrågade samlingar som er kod alltid fungerar till.', }, }; export const LOCATION_INFO: Record = Object.fromEntries(Object.entries(LOCATIONS).map(([id, l]) => [id, { label: l.label, blurb: l.blurb }])); // Optional tiny preview shown in each location's tooltip — same // missing-asset-is-fine convention as the character portraits. Path is // tried once per marker; a 404 just means the tooltip falls back to text. const THUMB = (id: string): string => `/img/locations/${id}.png`; type LeafletMap = ReturnType; type LeafletMarker = ReturnType; let map: LeafletMap | null = null; const markers = new Map(); function dreadColor(level: number): string { return ['#1a1a2e', '#4a1a4a', '#8b0a8b', '#cc00cc'][Math.min(level, 3)]; } function dreadRadius(level: number): number { return [6, 10, 16, 24][Math.min(level, 3)]; } export function initMap(containerId: string, onClick: (id: string) => void): void { if (!L) return; map = L.map(containerId, { center: [55.597, 13.002], zoom: 12, zoomControl: false, attributionControl: false, }); L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { maxZoom: 18, }).addTo(map); for (const [id, loc] of Object.entries(LOCATIONS)) { const m = L.circleMarker([loc.lat, loc.lng], { radius: 6, color: '#1a1a2e', fillColor: '#1a1a2e', fillOpacity: 0.3, weight: 1, }).addTo(map).bindTooltip( `
${loc.label}
`, { className: 'map-tooltip', permanent: false }, ); m.on('click', () => onClick(id)); markers.set(id, m); } } export function updateMap(dread: number, areas: { rosengard: boolean; varnhem: boolean; torso: boolean }): void { if (!map) return; const levels: Record = { rosengard: areas.rosengard ? Math.min(3, Math.floor(dread / 40) + 1) : Math.min(1, Math.floor(dread / 60)), varnhem: areas.varnhem ? Math.min(3, Math.floor(dread / 35) + 1) : 0, torso: areas.torso ? 3 : Math.min(1, Math.floor(dread / 80)), oresund: Math.min(2, Math.floor(dread / 50)), ribersborg: Math.min(2, Math.floor(dread / 45)), davidshall: Math.min(1, Math.floor(dread / 70)), stortorget: Math.min(2, Math.floor(dread / 55)), mollan: Math.min(2, Math.floor(dread / 48)), universitet: Math.min(2, Math.floor(dread / 65)), }; for (const [id, marker] of markers) { const level = levels[id] ?? 0; marker.setStyle({ radius: dreadRadius(level), color: dreadColor(level), fillColor: dreadColor(level), fillOpacity: 0.15 + level * 0.2, weight: level > 0 ? 2 : 1, }); } }