2026-07-07 23:05:22 +02:00
|
|
|
import './style.css';
|
Add offline progress, tab haunting, character gallery, more flavor
Real depth additions, not a reskin of another idle game:
- applyOfflineProgress: the cult continues while the tab is closed or
backgrounded (rAF throttles hard when hidden, so real time drifts
well past simulated time). Approximates tick()'s per-second rates
over the elapsed window, capped at 24h, with a narrative "while you
were away" log line scaled to how long that was. Can end the game
in a win or loss if the numbers would have gotten there. Re-checked
on every tab-visibility return too, not just page load.
- Tab-title haunting: document.title cycles short eerie lines
("Han väntar…", "Havet kallar…") while the tab is hidden, restores
on return.
- New late-game upgrade "Viskningen leder er" — once bought, the cult
visits map locations on its own occasionally without a click. A
small loss-of-agency horror beat, not just a QoL toggle.
- Log's newest line now types itself out, with characters
occasionally swapped for occult symbols (☿☠ψ) at a rate scaled to
current dread. Older lines stay static and legible — this never
applies retroactively.
- New "Sällskapet" character gallery: cards for every named recurring
character, locked to a silhouette + "???" until you've actually met
them (checked against usedEventIds). Portraits are optional — missing
art falls back to an initial-letter placeholder, so this works
incrementally as art gets added. First five real portraits included.
- Real bug fix: several Kaos events referenced characters (Göran,
Bengt-Åke, the Sydsvenskan journalist) as if already introduced,
but had no ordering dependency on the Standard event that actually
introduces them — so a fresh cultist could show up in the log
already "familiar" before you'd ever met them. Gated with
requires.completedEventId.
- More flavor text throughout: two new variants per location visit,
6 new Standard events, 4 new Kaos events.
Evaluated three other ideas from the same source and declined them:
a generic milestone-triggered log (Djupet's log already reacts to
real state, not arbitrary point totals), an investigator combat
minigame (hemlighet draining to zero already does this job, and
tower-defense clicking would fight the game's actual tone), and a
node-sequencing ritual minigame (real scope for a twitch mechanic
that doesn't match the rest of the game's pacing).
Verified: 15/15 tests passing, clean typecheck and production build,
and a real headless-browser pass — offline catch-up after a
simulated 5-hour gap, tab-title cycling and restore, and the
character gallery rendering real portraits alongside locked cards.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:53:26 +02:00
|
|
|
import { createState, tick, buyUpgrade, visitLocation, resolveEventChoice, startGame, abandonGame, saveState, loadState, clearSave, applyOfflineProgress, VISITABLE_LOCATION_IDS } from './engine.ts';
|
Fix silently-dropped log entries past the 40-line cap, add pause, clarify locations panel
- The log view only re-rendered when state.log.length changed, but the
log is capped at 40 entries — so once a run had logged 40+ things
(easy in a normal session), every entry after that point, including
choice-event outcomes, upgrade purchases, and location visits,
stopped appearing even though the underlying state updated. Fixed
by tracking a monotonic logSeq counter that increments on every
push regardless of the cap, and rendering off that instead of
array length. Consolidated the repeated unshift+cap logic into one
pushLog() helper so future call sites can't reintroduce the bug.
- Added a pause button (and "PAUSAD" indicator) that freezes the tick
loop without touching game state, so a hectic run can be paused to
think without it counting as a save-scummy state mutation. Manual
actions (buying upgrades, visiting locations) still work while
paused, since that's usually the point of pausing.
- The "Aktiva områden" sidebar only ever listed the 3 story-gated
core areas, while the map has 7 clickable locations — making it
look like 4 of the map pins didn't really exist. Split the panel
into "Ritualens kärnplatser" (the 3 gated ones, unlock status shown
as before) and "Övriga platser" (the 4 always-open flavor
locations), so the sidebar and map agree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 16:49:37 +02:00
|
|
|
import { initUI, renderState, setPausedUI } from './ui.ts';
|
2026-07-07 23:05:22 +02:00
|
|
|
import { initMap, updateMap } from './map.ts';
|
|
|
|
|
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
let state = loadState() ?? createState();
|
Add offline progress, tab haunting, character gallery, more flavor
Real depth additions, not a reskin of another idle game:
- applyOfflineProgress: the cult continues while the tab is closed or
backgrounded (rAF throttles hard when hidden, so real time drifts
well past simulated time). Approximates tick()'s per-second rates
over the elapsed window, capped at 24h, with a narrative "while you
were away" log line scaled to how long that was. Can end the game
in a win or loss if the numbers would have gotten there. Re-checked
on every tab-visibility return too, not just page load.
- Tab-title haunting: document.title cycles short eerie lines
("Han väntar…", "Havet kallar…") while the tab is hidden, restores
on return.
- New late-game upgrade "Viskningen leder er" — once bought, the cult
visits map locations on its own occasionally without a click. A
small loss-of-agency horror beat, not just a QoL toggle.
- Log's newest line now types itself out, with characters
occasionally swapped for occult symbols (☿☠ψ) at a rate scaled to
current dread. Older lines stay static and legible — this never
applies retroactively.
- New "Sällskapet" character gallery: cards for every named recurring
character, locked to a silhouette + "???" until you've actually met
them (checked against usedEventIds). Portraits are optional — missing
art falls back to an initial-letter placeholder, so this works
incrementally as art gets added. First five real portraits included.
- Real bug fix: several Kaos events referenced characters (Göran,
Bengt-Åke, the Sydsvenskan journalist) as if already introduced,
but had no ordering dependency on the Standard event that actually
introduces them — so a fresh cultist could show up in the log
already "familiar" before you'd ever met them. Gated with
requires.completedEventId.
- More flavor text throughout: two new variants per location visit,
6 new Standard events, 4 new Kaos events.
Evaluated three other ideas from the same source and declined them:
a generic milestone-triggered log (Djupet's log already reacts to
real state, not arbitrary point totals), an investigator combat
minigame (hemlighet draining to zero already does this job, and
tower-defense clicking would fight the game's actual tone), and a
node-sequencing ritual minigame (real scope for a twitch mechanic
that doesn't match the rest of the game's pacing).
Verified: 15/15 tests passing, clean typecheck and production build,
and a real headless-browser pass — offline catch-up after a
simulated 5-hour gap, tab-title cycling and restore, and the
character gallery rendering real portraits alongside locked cards.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:53:26 +02:00
|
|
|
|
|
|
|
|
// The cult doesn't pause just because the tab isn't in front of you —
|
|
|
|
|
// catch up on what happened while you were gone, once, on load.
|
|
|
|
|
if (state.phase === 'playing') {
|
|
|
|
|
state = applyOfflineProgress(state, Date.now() - (state.lastRealTime ?? Date.now()));
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
let visitCooldowns = new Map<string, number>();
|
Fix silently-dropped log entries past the 40-line cap, add pause, clarify locations panel
- The log view only re-rendered when state.log.length changed, but the
log is capped at 40 entries — so once a run had logged 40+ things
(easy in a normal session), every entry after that point, including
choice-event outcomes, upgrade purchases, and location visits,
stopped appearing even though the underlying state updated. Fixed
by tracking a monotonic logSeq counter that increments on every
push regardless of the cap, and rendering off that instead of
array length. Consolidated the repeated unshift+cap logic into one
pushLog() helper so future call sites can't reintroduce the bug.
- Added a pause button (and "PAUSAD" indicator) that freezes the tick
loop without touching game state, so a hectic run can be paused to
think without it counting as a save-scummy state mutation. Manual
actions (buying upgrades, visiting locations) still work while
paused, since that's usually the point of pausing.
- The "Aktiva områden" sidebar only ever listed the 3 story-gated
core areas, while the map has 7 clickable locations — making it
look like 4 of the map pins didn't really exist. Split the panel
into "Ritualens kärnplatser" (the 3 gated ones, unlock status shown
as before) and "Övriga platser" (the 4 always-open flavor
locations), so the sidebar and map agree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 16:49:37 +02:00
|
|
|
let paused = false;
|
2026-07-07 23:05:22 +02:00
|
|
|
let lastTime = 0;
|
|
|
|
|
const TARGET_FPS = 30;
|
|
|
|
|
const FRAME_MS = 1000 / TARGET_FPS;
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
const SAVE_INTERVAL_MS = 10_000;
|
|
|
|
|
let lastSaveTime = 0;
|
2026-07-07 23:05:22 +02:00
|
|
|
|
Add offline progress, tab haunting, character gallery, more flavor
Real depth additions, not a reskin of another idle game:
- applyOfflineProgress: the cult continues while the tab is closed or
backgrounded (rAF throttles hard when hidden, so real time drifts
well past simulated time). Approximates tick()'s per-second rates
over the elapsed window, capped at 24h, with a narrative "while you
were away" log line scaled to how long that was. Can end the game
in a win or loss if the numbers would have gotten there. Re-checked
on every tab-visibility return too, not just page load.
- Tab-title haunting: document.title cycles short eerie lines
("Han väntar…", "Havet kallar…") while the tab is hidden, restores
on return.
- New late-game upgrade "Viskningen leder er" — once bought, the cult
visits map locations on its own occasionally without a click. A
small loss-of-agency horror beat, not just a QoL toggle.
- Log's newest line now types itself out, with characters
occasionally swapped for occult symbols (☿☠ψ) at a rate scaled to
current dread. Older lines stay static and legible — this never
applies retroactively.
- New "Sällskapet" character gallery: cards for every named recurring
character, locked to a silhouette + "???" until you've actually met
them (checked against usedEventIds). Portraits are optional — missing
art falls back to an initial-letter placeholder, so this works
incrementally as art gets added. First five real portraits included.
- Real bug fix: several Kaos events referenced characters (Göran,
Bengt-Åke, the Sydsvenskan journalist) as if already introduced,
but had no ordering dependency on the Standard event that actually
introduces them — so a fresh cultist could show up in the log
already "familiar" before you'd ever met them. Gated with
requires.completedEventId.
- More flavor text throughout: two new variants per location visit,
6 new Standard events, 4 new Kaos events.
Evaluated three other ideas from the same source and declined them:
a generic milestone-triggered log (Djupet's log already reacts to
real state, not arbitrary point totals), an investigator combat
minigame (hemlighet draining to zero already does this job, and
tower-defense clicking would fight the game's actual tone), and a
node-sequencing ritual minigame (real scope for a twitch mechanic
that doesn't match the rest of the game's pacing).
Verified: 15/15 tests passing, clean typecheck and production build,
and a real headless-browser pass — offline catch-up after a
simulated 5-hour gap, tab-title cycling and restore, and the
character gallery rendering real portraits alongside locked cards.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:53:26 +02:00
|
|
|
const WHISPER_INTERVAL_TICKS = 30 * 45; // 45s at 30fps
|
|
|
|
|
const WHISPER_RETRY_TICKS = 30 * 5; // retry sooner if every location was on cooldown
|
|
|
|
|
let nextWhisperTick = 0;
|
|
|
|
|
|
|
|
|
|
const BASE_TITLE = document.title;
|
|
|
|
|
const HAUNT_MESSAGES = ['Han väntar…', 'Vakna honom…', 'Havet kallar…', 'Kom tillbaka…', '☿ Källaren minns ☿'];
|
|
|
|
|
let hauntTimer: number | undefined;
|
|
|
|
|
let hauntIndex = 0;
|
|
|
|
|
|
|
|
|
|
document.addEventListener('visibilitychange', () => {
|
|
|
|
|
if (document.hidden) {
|
|
|
|
|
if (state.phase !== 'playing') return;
|
|
|
|
|
hauntIndex = 0;
|
|
|
|
|
hauntTimer = window.setInterval(() => {
|
|
|
|
|
document.title = HAUNT_MESSAGES[hauntIndex % HAUNT_MESSAGES.length]!;
|
|
|
|
|
hauntIndex += 1;
|
|
|
|
|
}, 2500);
|
|
|
|
|
} else {
|
|
|
|
|
if (hauntTimer !== undefined) { window.clearInterval(hauntTimer); hauntTimer = undefined; }
|
|
|
|
|
document.title = BASE_TITLE;
|
|
|
|
|
// Coming back to the tab is itself a kind of "return" — re-check for
|
|
|
|
|
// offline progress in case the tab was merely backgrounded, not
|
|
|
|
|
// reloaded (requestAnimationFrame throttles hard in hidden tabs, so
|
|
|
|
|
// real time can drift a long way ahead of simulated game time).
|
|
|
|
|
if (state.phase === 'playing') {
|
|
|
|
|
state = applyOfflineProgress(state, Date.now() - (state.lastRealTime ?? Date.now()));
|
|
|
|
|
state = { ...state, lastRealTime: Date.now() };
|
|
|
|
|
renderState(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
function start(): void {
|
|
|
|
|
const app = document.getElementById('app')!;
|
|
|
|
|
|
|
|
|
|
initUI(app,
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
(id) => { state = buyUpgrade(state, id); renderState(state); saveState(state); },
|
2026-07-07 23:05:22 +02:00
|
|
|
() => {
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
clearSave();
|
2026-07-07 23:05:22 +02:00
|
|
|
state = createState();
|
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
|
|
|
visitCooldowns = new Map();
|
2026-07-07 23:05:22 +02:00
|
|
|
document.getElementById('overlay')!.classList.remove('visible');
|
|
|
|
|
renderState(state);
|
2026-07-12 23:17:48 +02:00
|
|
|
},
|
2026-07-13 09:54:46 +02:00
|
|
|
(index) => { state = resolveEventChoice(state, index); renderState(state); saveState(state); },
|
|
|
|
|
() => { state = startGame(state); renderState(state); saveState(state); },
|
Fix silently-dropped log entries past the 40-line cap, add pause, clarify locations panel
- The log view only re-rendered when state.log.length changed, but the
log is capped at 40 entries — so once a run had logged 40+ things
(easy in a normal session), every entry after that point, including
choice-event outcomes, upgrade purchases, and location visits,
stopped appearing even though the underlying state updated. Fixed
by tracking a monotonic logSeq counter that increments on every
push regardless of the cap, and rendering off that instead of
array length. Consolidated the repeated unshift+cap logic into one
pushLog() helper so future call sites can't reintroduce the bug.
- Added a pause button (and "PAUSAD" indicator) that freezes the tick
loop without touching game state, so a hectic run can be paused to
think without it counting as a save-scummy state mutation. Manual
actions (buying upgrades, visiting locations) still work while
paused, since that's usually the point of pausing.
- The "Aktiva områden" sidebar only ever listed the 3 story-gated
core areas, while the map has 7 clickable locations — making it
look like 4 of the map pins didn't really exist. Split the panel
into "Ritualens kärnplatser" (the 3 gated ones, unlock status shown
as before) and "Övriga platser" (the 4 always-open flavor
locations), so the sidebar and map agree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 16:49:37 +02:00
|
|
|
() => { state = abandonGame(state); renderState(state); saveState(state); },
|
|
|
|
|
() => { paused = !paused; setPausedUI(paused); }
|
2026-07-07 23:05:22 +02:00
|
|
|
);
|
|
|
|
|
|
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
|
|
|
initMap('map', (locationId) => {
|
|
|
|
|
const result = visitLocation(state, locationId, visitCooldowns);
|
|
|
|
|
state = result.state;
|
|
|
|
|
visitCooldowns = result.cooldowns;
|
|
|
|
|
renderState(state);
|
|
|
|
|
});
|
2026-07-07 23:05:22 +02:00
|
|
|
renderState(state);
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(loop);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loop(now: number): void {
|
|
|
|
|
if (now - lastTime >= FRAME_MS) {
|
|
|
|
|
lastTime = now;
|
Fix silently-dropped log entries past the 40-line cap, add pause, clarify locations panel
- The log view only re-rendered when state.log.length changed, but the
log is capped at 40 entries — so once a run had logged 40+ things
(easy in a normal session), every entry after that point, including
choice-event outcomes, upgrade purchases, and location visits,
stopped appearing even though the underlying state updated. Fixed
by tracking a monotonic logSeq counter that increments on every
push regardless of the cap, and rendering off that instead of
array length. Consolidated the repeated unshift+cap logic into one
pushLog() helper so future call sites can't reintroduce the bug.
- Added a pause button (and "PAUSAD" indicator) that freezes the tick
loop without touching game state, so a hectic run can be paused to
think without it counting as a save-scummy state mutation. Manual
actions (buying upgrades, visiting locations) still work while
paused, since that's usually the point of pausing.
- The "Aktiva områden" sidebar only ever listed the 3 story-gated
core areas, while the map has 7 clickable locations — making it
look like 4 of the map pins didn't really exist. Split the panel
into "Ritualens kärnplatser" (the 3 gated ones, unlock status shown
as before) and "Övriga platser" (the 4 always-open flavor
locations), so the sidebar and map agree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 16:49:37 +02:00
|
|
|
if (state.phase === 'playing' && !paused) {
|
2026-07-07 23:05:22 +02:00
|
|
|
state = tick(state);
|
Add offline progress, tab haunting, character gallery, more flavor
Real depth additions, not a reskin of another idle game:
- applyOfflineProgress: the cult continues while the tab is closed or
backgrounded (rAF throttles hard when hidden, so real time drifts
well past simulated time). Approximates tick()'s per-second rates
over the elapsed window, capped at 24h, with a narrative "while you
were away" log line scaled to how long that was. Can end the game
in a win or loss if the numbers would have gotten there. Re-checked
on every tab-visibility return too, not just page load.
- Tab-title haunting: document.title cycles short eerie lines
("Han väntar…", "Havet kallar…") while the tab is hidden, restores
on return.
- New late-game upgrade "Viskningen leder er" — once bought, the cult
visits map locations on its own occasionally without a click. A
small loss-of-agency horror beat, not just a QoL toggle.
- Log's newest line now types itself out, with characters
occasionally swapped for occult symbols (☿☠ψ) at a rate scaled to
current dread. Older lines stay static and legible — this never
applies retroactively.
- New "Sällskapet" character gallery: cards for every named recurring
character, locked to a silhouette + "???" until you've actually met
them (checked against usedEventIds). Portraits are optional — missing
art falls back to an initial-letter placeholder, so this works
incrementally as art gets added. First five real portraits included.
- Real bug fix: several Kaos events referenced characters (Göran,
Bengt-Åke, the Sydsvenskan journalist) as if already introduced,
but had no ordering dependency on the Standard event that actually
introduces them — so a fresh cultist could show up in the log
already "familiar" before you'd ever met them. Gated with
requires.completedEventId.
- More flavor text throughout: two new variants per location visit,
6 new Standard events, 4 new Kaos events.
Evaluated three other ideas from the same source and declined them:
a generic milestone-triggered log (Djupet's log already reacts to
real state, not arbitrary point totals), an investigator combat
minigame (hemlighet draining to zero already does this job, and
tower-defense clicking would fight the game's actual tone), and a
node-sequencing ritual minigame (real scope for a twitch mechanic
that doesn't match the rest of the game's pacing).
Verified: 15/15 tests passing, clean typecheck and production build,
and a real headless-browser pass — offline catch-up after a
simulated 5-hour gap, tab-title cycling and restore, and the
character gallery rendering real portraits alongside locked cards.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:53:26 +02:00
|
|
|
|
|
|
|
|
// "Viskningen leder er" — once purchased, the cult moves on its own
|
|
|
|
|
// occasionally without a click. Skip a location currently on
|
|
|
|
|
// cooldown rather than burning the whole interval on a no-op visit.
|
|
|
|
|
if (state.upgrades.has('whisper_guidance') && state.tick >= nextWhisperTick) {
|
|
|
|
|
const tickNow = state.tick;
|
|
|
|
|
const available = VISITABLE_LOCATION_IDS.filter(id => (visitCooldowns.get(id) ?? 0) <= tickNow);
|
|
|
|
|
if (available.length) {
|
|
|
|
|
const id = available[Math.floor(Math.random() * available.length)]!;
|
|
|
|
|
const result = visitLocation(state, id, visitCooldowns);
|
|
|
|
|
state = result.state;
|
|
|
|
|
visitCooldowns = result.cooldowns;
|
|
|
|
|
nextWhisperTick = state.tick + WHISPER_INTERVAL_TICKS;
|
|
|
|
|
} else {
|
|
|
|
|
nextWhisperTick = state.tick + WHISPER_RETRY_TICKS;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
renderState(state);
|
|
|
|
|
updateMap(state.dread, state.areas);
|
|
|
|
|
}
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
if (now - lastSaveTime >= SAVE_INTERVAL_MS) {
|
|
|
|
|
lastSaveTime = now;
|
Add offline progress, tab haunting, character gallery, more flavor
Real depth additions, not a reskin of another idle game:
- applyOfflineProgress: the cult continues while the tab is closed or
backgrounded (rAF throttles hard when hidden, so real time drifts
well past simulated time). Approximates tick()'s per-second rates
over the elapsed window, capped at 24h, with a narrative "while you
were away" log line scaled to how long that was. Can end the game
in a win or loss if the numbers would have gotten there. Re-checked
on every tab-visibility return too, not just page load.
- Tab-title haunting: document.title cycles short eerie lines
("Han väntar…", "Havet kallar…") while the tab is hidden, restores
on return.
- New late-game upgrade "Viskningen leder er" — once bought, the cult
visits map locations on its own occasionally without a click. A
small loss-of-agency horror beat, not just a QoL toggle.
- Log's newest line now types itself out, with characters
occasionally swapped for occult symbols (☿☠ψ) at a rate scaled to
current dread. Older lines stay static and legible — this never
applies retroactively.
- New "Sällskapet" character gallery: cards for every named recurring
character, locked to a silhouette + "???" until you've actually met
them (checked against usedEventIds). Portraits are optional — missing
art falls back to an initial-letter placeholder, so this works
incrementally as art gets added. First five real portraits included.
- Real bug fix: several Kaos events referenced characters (Göran,
Bengt-Åke, the Sydsvenskan journalist) as if already introduced,
but had no ordering dependency on the Standard event that actually
introduces them — so a fresh cultist could show up in the log
already "familiar" before you'd ever met them. Gated with
requires.completedEventId.
- More flavor text throughout: two new variants per location visit,
6 new Standard events, 4 new Kaos events.
Evaluated three other ideas from the same source and declined them:
a generic milestone-triggered log (Djupet's log already reacts to
real state, not arbitrary point totals), an investigator combat
minigame (hemlighet draining to zero already does this job, and
tower-defense clicking would fight the game's actual tone), and a
node-sequencing ritual minigame (real scope for a twitch mechanic
that doesn't match the rest of the game's pacing).
Verified: 15/15 tests passing, clean typecheck and production build,
and a real headless-browser pass — offline catch-up after a
simulated 5-hour gap, tab-title cycling and restore, and the
character gallery rendering real portraits alongside locked cards.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:53:26 +02:00
|
|
|
state = { ...state, lastRealTime: Date.now() };
|
Add lore depth, fix recruit click bug, add localStorage save
- 20+ new events drawing from Anders Fager, Kult, and Call of Cthulhu:
ICA cashiers who never blink, SJ trains stopping in non-existent tunnels,
Kult lictors as city officials, death servants finishing lectures, Deep Ones,
investigators dreaming about us, stars being right over Lund Observatory
- Rewrite upgrade descriptions to match the register: mundane detail,
matter-of-fact horror, no melodrama
- Add a new mid-game upgrade (Paktens Förnyelse) to smooth the progression
- Fix recruit button: was destroying DOM every 30fps tick, killing mousedown→click
events; now only rebuilds when availability/affordability actually changes
- Fix double andakt deduction in recruit effect
- Fix map growing over log (proper flex constraints)
- Add localStorage save/load with versioned key (SAVE_VERSION bump invalidates
old saves); autosaves every 10s and on upgrade purchase, clears on restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:25:44 +02:00
|
|
|
saveState(state);
|
|
|
|
|
}
|
2026-07-07 23:05:22 +02:00
|
|
|
}
|
|
|
|
|
requestAnimationFrame(loop);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start();
|