2026-07-07 23:05:22 +02:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
Click a map marker to see its art at full size
Reuses the lightbox already built for the character gallery. Clicking
any location still triggers the visit as before, but now also probes
for that location's art and, if it exists, pops it up full-size with
a caption — no modal for locations that don't have art yet, the visit
still happens either way.
Also added a standing "about this place" blurb per location (map.ts's
new LOCATION_INFO), shown under the art. This is distinct from
LOCATION_VISITS' randomized per-visit outcome text in engine.ts, which
still changes every time — the blurb is a fixed description of the
place itself, not what just happened there.
Verified via headless browser: clicking Turning Torso's marker pops
the lightbox with its real art, label, and blurb, no console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:42:28 +02:00
|
|
|
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<string, LocationInfo> = {
|
|
|
|
|
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<string, { label: string; blurb: string }> =
|
|
|
|
|
Object.fromEntries(Object.entries(LOCATIONS).map(([id, l]) => [id, { label: l.label, blurb: l.blurb }]));
|
2026-07-07 23:05:22 +02:00
|
|
|
|
Location thumbnails on the map, full-art archive mode, more content
- Map markers now show a tiny thumbnail preview in their tooltip when
art exists for that location (same missing-asset-is-fine fallback
as character portraits — onerror just removes the broken img).
- Two new locations, both free-roam from the start: Möllevångstorget
and Malmö universitet (the latter ties into the researcher
character's arc). VISITABLE_LOCATION_IDS picks them up automatically
since it's derived from the data, not a separate list.
- Sällskapet gets a second mode: "Fullständigt arkiv" shows every
character's real portrait and bio regardless of story progress
(bypassing the usedEventIds lock), and clicking any known portrait
opens a lightbox at full size instead of the cropped card thumbnail.
- All 9 character portraits are now in and wired (fixed two more
filename mismatches along the way: anneli-karin and the rest use
hyphens, not underscores, matching what was actually generated).
- More content: 2 new location-visit variants each for the two new
areas, 6 new Standard events (including a found 1930s photograph
that seems to show Lindqvist in a room nobody recognizes), 4 new
Kaos events tied to the new locations and existing ones.
- Caught and fixed a bug in my own new content before commit: an
event's requires.completedEventId pointed at an upgrade id, which
can never appear in usedEventIds — switched to a minRitual check
that actually gates on the right precondition.
Verified: 15/15 tests, clean typecheck and build, and a real
headless-browser pass — all 9 map markers present, full-archive mode
correctly bypassing locks, and the lightbox opening at full size with
a caption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:09:43 +02:00
|
|
|
// 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`;
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
type LeafletMap = ReturnType<typeof L.map>;
|
|
|
|
|
type LeafletMarker = ReturnType<typeof L.circleMarker>;
|
|
|
|
|
|
|
|
|
|
let map: LeafletMap | null = null;
|
|
|
|
|
const markers = new Map<string, LeafletMarker>();
|
|
|
|
|
|
|
|
|
|
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)];
|
|
|
|
|
}
|
|
|
|
|
|
Add help modal, changelog, map visits, Dockerfile, CI
- Help modal (? button or keyboard shortcut): how-to-play in Swedish,
flavor text, full changelog sourced from src/data/version.ts
- src/data/version.ts: versioned changelog entries, in-universe Malmö voice
- Map zone clicks: each location on the Leaflet map is now clickable.
Unlocked cult areas give resources + log entries. Locked locations show
lore hints. 30s cooldown per location so it's a decision, not a spam button.
- Dockerfile + nginx.conf + docker-compose.yml (port 5684)
- Gitea CI workflow (identical to 2048 — derives image from repo path, needs
TKNTKN secret, pushes :latest and :sha tags)
- README.md with mechanics summary and docker instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:31:37 +02:00
|
|
|
export function initMap(containerId: string, onClick: (id: string) => void): void {
|
2026-07-07 23:05:22 +02:00
|
|
|
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);
|
|
|
|
|
|
Click a map marker to see its art at full size
Reuses the lightbox already built for the character gallery. Clicking
any location still triggers the visit as before, but now also probes
for that location's art and, if it exists, pops it up full-size with
a caption — no modal for locations that don't have art yet, the visit
still happens either way.
Also added a standing "about this place" blurb per location (map.ts's
new LOCATION_INFO), shown under the art. This is distinct from
LOCATION_VISITS' randomized per-visit outcome text in engine.ts, which
still changes every time — the blurb is a fixed description of the
place itself, not what just happened there.
Verified via headless browser: clicking Turning Torso's marker pops
the lightbox with its real art, label, and blurb, no console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:42:28 +02:00
|
|
|
for (const [id, loc] of Object.entries(LOCATIONS)) {
|
2026-07-07 23:05:22 +02:00
|
|
|
const m = L.circleMarker([loc.lat, loc.lng], {
|
|
|
|
|
radius: 6,
|
|
|
|
|
color: '#1a1a2e',
|
|
|
|
|
fillColor: '#1a1a2e',
|
|
|
|
|
fillOpacity: 0.3,
|
|
|
|
|
weight: 1,
|
Location thumbnails on the map, full-art archive mode, more content
- Map markers now show a tiny thumbnail preview in their tooltip when
art exists for that location (same missing-asset-is-fine fallback
as character portraits — onerror just removes the broken img).
- Two new locations, both free-roam from the start: Möllevångstorget
and Malmö universitet (the latter ties into the researcher
character's arc). VISITABLE_LOCATION_IDS picks them up automatically
since it's derived from the data, not a separate list.
- Sällskapet gets a second mode: "Fullständigt arkiv" shows every
character's real portrait and bio regardless of story progress
(bypassing the usedEventIds lock), and clicking any known portrait
opens a lightbox at full size instead of the cropped card thumbnail.
- All 9 character portraits are now in and wired (fixed two more
filename mismatches along the way: anneli-karin and the rest use
hyphens, not underscores, matching what was actually generated).
- More content: 2 new location-visit variants each for the two new
areas, 6 new Standard events (including a found 1930s photograph
that seems to show Lindqvist in a room nobody recognizes), 4 new
Kaos events tied to the new locations and existing ones.
- Caught and fixed a bug in my own new content before commit: an
event's requires.completedEventId pointed at an upgrade id, which
can never appear in usedEventIds — switched to a minRitual check
that actually gates on the right precondition.
Verified: 15/15 tests, clean typecheck and build, and a real
headless-browser pass — all 9 map markers present, full-archive mode
correctly bypassing locks, and the lightbox opening at full size with
a caption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:09:43 +02:00
|
|
|
}).addTo(map).bindTooltip(
|
|
|
|
|
`<div class="map-tooltip-inner">
|
Click a map marker to see its art at full size
Reuses the lightbox already built for the character gallery. Clicking
any location still triggers the visit as before, but now also probes
for that location's art and, if it exists, pops it up full-size with
a caption — no modal for locations that don't have art yet, the visit
still happens either way.
Also added a standing "about this place" blurb per location (map.ts's
new LOCATION_INFO), shown under the art. This is distinct from
LOCATION_VISITS' randomized per-visit outcome text in engine.ts, which
still changes every time — the blurb is a fixed description of the
place itself, not what just happened there.
Verified via headless browser: clicking Turning Torso's marker pops
the lightbox with its real art, label, and blurb, no console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:42:28 +02:00
|
|
|
<img class="map-tooltip-thumb" src="${THUMB(id)}" alt=""
|
Location thumbnails on the map, full-art archive mode, more content
- Map markers now show a tiny thumbnail preview in their tooltip when
art exists for that location (same missing-asset-is-fine fallback
as character portraits — onerror just removes the broken img).
- Two new locations, both free-roam from the start: Möllevångstorget
and Malmö universitet (the latter ties into the researcher
character's arc). VISITABLE_LOCATION_IDS picks them up automatically
since it's derived from the data, not a separate list.
- Sällskapet gets a second mode: "Fullständigt arkiv" shows every
character's real portrait and bio regardless of story progress
(bypassing the usedEventIds lock), and clicking any known portrait
opens a lightbox at full size instead of the cropped card thumbnail.
- All 9 character portraits are now in and wired (fixed two more
filename mismatches along the way: anneli-karin and the rest use
hyphens, not underscores, matching what was actually generated).
- More content: 2 new location-visit variants each for the two new
areas, 6 new Standard events (including a found 1930s photograph
that seems to show Lindqvist in a room nobody recognizes), 4 new
Kaos events tied to the new locations and existing ones.
- Caught and fixed a bug in my own new content before commit: an
event's requires.completedEventId pointed at an upgrade id, which
can never appear in usedEventIds — switched to a minRitual check
that actually gates on the right precondition.
Verified: 15/15 tests, clean typecheck and build, and a real
headless-browser pass — all 9 map markers present, full-archive mode
correctly bypassing locks, and the lightbox opening at full size with
a caption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:09:43 +02:00
|
|
|
onerror="this.remove()">
|
|
|
|
|
<span>${loc.label}</span>
|
|
|
|
|
</div>`,
|
|
|
|
|
{ className: 'map-tooltip', permanent: false },
|
|
|
|
|
);
|
Click a map marker to see its art at full size
Reuses the lightbox already built for the character gallery. Clicking
any location still triggers the visit as before, but now also probes
for that location's art and, if it exists, pops it up full-size with
a caption — no modal for locations that don't have art yet, the visit
still happens either way.
Also added a standing "about this place" blurb per location (map.ts's
new LOCATION_INFO), shown under the art. This is distinct from
LOCATION_VISITS' randomized per-visit outcome text in engine.ts, which
still changes every time — the blurb is a fixed description of the
place itself, not what just happened there.
Verified via headless browser: clicking Turning Torso's marker pops
the lightbox with its real art, label, and blurb, no console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:42:28 +02:00
|
|
|
m.on('click', () => onClick(id));
|
|
|
|
|
markers.set(id, m);
|
2026-07-07 23:05:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateMap(dread: number, areas: { rosengard: boolean; varnhem: boolean; torso: boolean }): void {
|
|
|
|
|
if (!map) return;
|
|
|
|
|
|
|
|
|
|
const levels: Record<string, number> = {
|
|
|
|
|
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)),
|
Location thumbnails on the map, full-art archive mode, more content
- Map markers now show a tiny thumbnail preview in their tooltip when
art exists for that location (same missing-asset-is-fine fallback
as character portraits — onerror just removes the broken img).
- Two new locations, both free-roam from the start: Möllevångstorget
and Malmö universitet (the latter ties into the researcher
character's arc). VISITABLE_LOCATION_IDS picks them up automatically
since it's derived from the data, not a separate list.
- Sällskapet gets a second mode: "Fullständigt arkiv" shows every
character's real portrait and bio regardless of story progress
(bypassing the usedEventIds lock), and clicking any known portrait
opens a lightbox at full size instead of the cropped card thumbnail.
- All 9 character portraits are now in and wired (fixed two more
filename mismatches along the way: anneli-karin and the rest use
hyphens, not underscores, matching what was actually generated).
- More content: 2 new location-visit variants each for the two new
areas, 6 new Standard events (including a found 1930s photograph
that seems to show Lindqvist in a room nobody recognizes), 4 new
Kaos events tied to the new locations and existing ones.
- Caught and fixed a bug in my own new content before commit: an
event's requires.completedEventId pointed at an upgrade id, which
can never appear in usedEventIds — switched to a minRitual check
that actually gates on the right precondition.
Verified: 15/15 tests, clean typecheck and build, and a real
headless-browser pass — all 9 map markers present, full-archive mode
correctly bypassing locks, and the lightbox opening at full size with
a caption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:09:43 +02:00
|
|
|
mollan: Math.min(2, Math.floor(dread / 48)),
|
|
|
|
|
universitet: Math.min(2, Math.floor(dread / 65)),
|
2026-07-07 23:05:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|