Files
djupet/src/main.ts

119 lines
4.3 KiB
TypeScript
Raw Normal View History

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';
import { initUI, renderState, setPausedUI } from './ui.ts';
import { initMap, updateMap } from './map.ts';
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()));
}
let visitCooldowns = new Map<string, number>();
let paused = false;
let lastTime = 0;
const TARGET_FPS = 30;
const FRAME_MS = 1000 / TARGET_FPS;
const SAVE_INTERVAL_MS = 10_000;
let lastSaveTime = 0;
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);
}
}
});
function start(): void {
const app = document.getElementById('app')!;
initUI(app,
(id) => { state = buyUpgrade(state, id); renderState(state); saveState(state); },
() => {
clearSave();
state = createState();
visitCooldowns = new Map();
document.getElementById('overlay')!.classList.remove('visible');
renderState(state);
},
(index) => { state = resolveEventChoice(state, index); renderState(state); saveState(state); },
() => { state = startGame(state); renderState(state); saveState(state); },
() => { state = abandonGame(state); renderState(state); saveState(state); },
() => { paused = !paused; setPausedUI(paused); }
);
initMap('map', (locationId) => {
const result = visitLocation(state, locationId, visitCooldowns);
state = result.state;
visitCooldowns = result.cooldowns;
renderState(state);
});
renderState(state);
requestAnimationFrame(loop);
}
function loop(now: number): void {
if (now - lastTime >= FRAME_MS) {
lastTime = now;
if (state.phase === 'playing' && !paused) {
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;
}
}
renderState(state);
updateMap(state.dread, state.areas);
}
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() };
saveState(state);
}
}
requestAnimationFrame(loop);
}
start();