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';