From 1aa9eb2ce87842080bafc167efe65a852a4cb25e Mon Sep 17 00:00:00 2001 From: Fredrik Johansson Date: Sat, 1 Aug 2026 21:42:28 +0200 Subject: [PATCH] Click a map marker to see its art at full size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/data/version.ts | 10 ++++++- src/main.ts | 7 +++-- src/map.ts | 70 +++++++++++++++++++++++++++++++++++---------- src/style.css | 9 ++++++ src/ui.ts | 23 +++++++++++++++ 5 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/data/version.ts b/src/data/version.ts index 076cfa8..559f5ba 100644 --- a/src/data/version.ts +++ b/src/data/version.ts @@ -1,4 +1,4 @@ -export const VERSION = '0.6.0'; +export const VERSION = '0.7.0'; export interface ChangelogEntry { version: string; @@ -7,6 +7,14 @@ export interface ChangelogEntry { } export const CHANGELOG: ChangelogEntry[] = [ + { + version: '0.7.0', + date: '2026-08-01', + changes: [ + 'Klicka på en plats på kartan för att se dess bild i fullstorlek, tillsammans med en fast beskrivning av platsen', + 'Alla 9 platser har nu konst på kartan', + ], + }, { version: '0.6.0', date: '2026-08-01', diff --git a/src/main.ts b/src/main.ts index 59f90e1..8135211 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import './style.css'; import { createState, tick, buyUpgrade, visitLocation, resolveEventChoice, startGame, abandonGame, saveState, loadState, clearSave, applyOfflineProgress, VISITABLE_LOCATION_IDS } from './engine.ts'; -import { initUI, renderState, setPausedUI } from './ui.ts'; -import { initMap, updateMap } from './map.ts'; +import { initUI, renderState, setPausedUI, showLocationArt } from './ui.ts'; +import { initMap, updateMap, LOCATION_INFO } from './map.ts'; let state = loadState() ?? createState(); @@ -74,6 +74,9 @@ function start(): void { state = result.state; visitCooldowns = result.cooldowns; renderState(state); + + const info = LOCATION_INFO[locationId]; + if (info) showLocationArt(locationId, info.label, info.blurb); }); renderState(state); diff --git a/src/map.ts b/src/map.ts index 17e7725..6b3f3fe 100644 --- a/src/map.ts +++ b/src/map.ts @@ -10,17 +10,57 @@ export interface MapMarker { dreadLevel: number; // 0-3 } -const LOCATIONS: Omit[] = [ - { id: 'rosengard', lat: 55.5894, lng: 13.0414, label: 'Källaren i Rosengård' }, - { id: 'varnhem', lat: 55.5982, lng: 13.0058, label: 'Värnhemstorget' }, - { id: 'torso', lat: 55.6130, lng: 12.9758, label: 'Turning Torso' }, - { id: 'oresund', lat: 55.5789, lng: 12.9200, label: 'Øresundsbron' }, - { id: 'ribersborg',lat: 55.6063, lng: 12.9652, label: 'Ribersborgsstranden' }, - { id: 'davidshall',lat: 55.5963, lng: 13.0021, label: 'Davidshallsgatan' }, - { id: 'stortorget',lat: 55.6047, lng: 13.0038, label: 'Stortorget' }, - { id: 'mollan', lat: 55.5904, lng: 13.0203, label: 'Möllevångstorget' }, - { id: 'universitet', lat: 55.5721, lng: 12.9998, label: 'Malmö universitet' }, -]; +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 @@ -54,7 +94,7 @@ export function initMap(containerId: string, onClick: (id: string) => void): voi maxZoom: 18, }).addTo(map); - for (const loc of LOCATIONS) { + for (const [id, loc] of Object.entries(LOCATIONS)) { const m = L.circleMarker([loc.lat, loc.lng], { radius: 6, color: '#1a1a2e', @@ -63,14 +103,14 @@ export function initMap(containerId: string, onClick: (id: string) => void): voi weight: 1, }).addTo(map).bindTooltip( `
- ${loc.label}
`, { className: 'map-tooltip', permanent: false }, ); - m.on('click', () => onClick(loc.id)); - markers.set(loc.id, m); + m.on('click', () => onClick(id)); + markers.set(id, m); } } diff --git a/src/style.css b/src/style.css index 68e29f9..2fb230a 100644 --- a/src/style.css +++ b/src/style.css @@ -652,6 +652,15 @@ body { letter-spacing: 0.06em; text-transform: uppercase; } +.lightbox-blurb { + font-size: 12px; + color: var(--dim); + line-height: 1.7; + max-width: 56ch; + text-align: center; + font-style: italic; +} +.lightbox-blurb:empty { display: none; } .lightbox-close { position: absolute; top: 20px; diff --git a/src/ui.ts b/src/ui.ts index cfce89c..cf3b985 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -191,6 +191,7 @@ export function initUI( + `; @@ -237,6 +238,7 @@ export function initUI( const lightbox = document.getElementById('lightbox')!; const lightboxImg = document.getElementById('lightbox-img') as HTMLImageElement; const lightboxCaption = document.getElementById('lightbox-caption')!; + const lightboxBlurb = document.getElementById('lightbox-blurb')!; document.getElementById('characters-grid')!.addEventListener('click', (e) => { const portrait = (e.target as Element).closest('.character-card:not(.locked) .character-portrait'); if (!portrait) return; @@ -245,6 +247,7 @@ export function initUI( if (!card || !img?.src) return; lightboxImg.src = img.src; lightboxCaption.textContent = card.querySelector('.character-name')?.textContent ?? ''; + lightboxBlurb.textContent = ''; lightbox.classList.add('visible'); }); document.getElementById('lightbox-close')!.addEventListener('click', () => lightbox.classList.remove('visible')); @@ -518,6 +521,26 @@ function showOverlay(state: State): void { } } +// Called on every map marker click, alongside the actual visit — a way to +// see the location's art at a readable size instead of the 40px tooltip +// thumbnail. Probes the image first so a location with no art yet doesn't +// pop an empty modal; the visit itself always happens regardless. +export function showLocationArt(id: string, label: string, blurb: string): void { + const src = `/img/locations/${id}.png`; + const probe = new Image(); + probe.onload = () => { + const lightbox = document.getElementById('lightbox')!; + const lightboxImg = document.getElementById('lightbox-img') as HTMLImageElement; + const lightboxCaption = document.getElementById('lightbox-caption')!; + const lightboxBlurb = document.getElementById('lightbox-blurb')!; + lightboxImg.src = src; + lightboxCaption.textContent = label; + lightboxBlurb.textContent = blurb; + lightbox.classList.add('visible'); + }; + probe.src = src; +} + export function setPausedUI(paused: boolean): void { const btn = document.getElementById('pause-btn')!; btn.textContent = paused ? 'Fortsätt' : 'Pausa';