2026-07-07 23:05:22 +02:00
|
|
|
import type { State } from './engine.ts';
|
|
|
|
|
import { UPGRADES } from './upgrades.ts';
|
2026-07-07 23:31:37 +02:00
|
|
|
import { VERSION, CHANGELOG } from './data/version.ts';
|
2026-07-07 23:05:22 +02:00
|
|
|
|
2026-07-13 09:54:46 +02:00
|
|
|
export function initUI(
|
|
|
|
|
app: HTMLElement,
|
|
|
|
|
onUpgrade: (id: string) => void,
|
|
|
|
|
onRestart: () => void,
|
|
|
|
|
onEventChoice: (index: number) => void,
|
|
|
|
|
onStart: () => void,
|
|
|
|
|
onAbandon: () => void,
|
|
|
|
|
): void {
|
2026-07-07 23:05:22 +02:00
|
|
|
app.innerHTML = `
|
|
|
|
|
<div id="header">
|
|
|
|
|
<div class="brand">DJUPET</div>
|
|
|
|
|
<div class="brand-sub">Malmö, Sverige · <span id="phase-tag" class="phase-tag early">Tidigt skede</span></div>
|
2026-07-13 09:54:46 +02:00
|
|
|
<div class="header-actions">
|
|
|
|
|
<button id="abandon-btn" class="help-btn" title="Överge kulten">Avbryt</button>
|
|
|
|
|
<button id="help-btn" class="help-btn" title="Hjälp & Ändringslogg">? v${VERSION}</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="intro-screen" style="background-image:url('/img/intro.webp')">
|
|
|
|
|
<div class="intro-scrim">
|
|
|
|
|
<div class="intro-title">DJUPET</div>
|
|
|
|
|
<div class="intro-sub">Malmö, Sverige</div>
|
|
|
|
|
<p class="intro-flavor">
|
|
|
|
|
Nio stolar i en fuktig källare i Rosengård. En av dem är alltid lite för varm.<br>
|
|
|
|
|
Ni har hört sången i väggarna. Den Sovande har inte hört er ännu.<br>
|
|
|
|
|
Det är dags att ändra på det.
|
|
|
|
|
</p>
|
|
|
|
|
<button class="overlay-btn" id="start-btn">Börja ritualen</button>
|
|
|
|
|
</div>
|
2026-07-07 23:31:37 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="help-modal" class="help-modal">
|
|
|
|
|
<div class="help-modal-inner" id="help-modal-inner">
|
|
|
|
|
<div class="help-modal-header">
|
|
|
|
|
<span class="help-modal-title">DJUPET — Hjälp & Ändringslogg</span>
|
|
|
|
|
<button id="help-close" class="help-close">✕</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="help-modal-body">
|
|
|
|
|
<section class="help-section">
|
|
|
|
|
<div class="help-section-title">Hur man spelar</div>
|
|
|
|
|
<ul class="help-list">
|
|
|
|
|
<li><strong>Andakt</strong> ackumuleras passivt. Använd den för att köpa åtgärder i vänsterpanelen.</li>
|
|
|
|
|
<li><strong>Dread</strong> ökar när ritualen fördjupas. Hög dread låser upp nya platser och händelser.</li>
|
|
|
|
|
<li><strong>Hemlighet</strong> sjunker när ni blir för uppenbara. Når den noll är det slut.</li>
|
|
|
|
|
<li><strong>Kaos</strong> byggs upp naturligt och utlöser kaotiska händelser när det når 100.</li>
|
|
|
|
|
<li><strong>Ritual</strong> låses upp via Skriften. Nå 100 för att fullborda ceremonin.</li>
|
|
|
|
|
<li>Klicka på <em>platser på kartan</em> för att besöka dem. Varje besök lämnar spår.</li>
|
|
|
|
|
<li>Händelser inträffar automatiskt. De flesta är dåliga. En del är värre.</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="help-section">
|
|
|
|
|
<div class="help-section-title">Om spelet</div>
|
|
|
|
|
<p class="help-flavor">
|
|
|
|
|
Djupet är ett idle-spel om en kult i Malmö, Sverige.<br>
|
|
|
|
|
Inspirerat av Anders Fager, Kult och Call of Cthulhu.<br>
|
|
|
|
|
Den Sovande är inte vaken ännu. Jobba på det.
|
|
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="help-section">
|
|
|
|
|
<div class="help-section-title">Ändringslogg</div>
|
|
|
|
|
${CHANGELOG.map(e => `
|
|
|
|
|
<div class="help-changelog-entry">
|
|
|
|
|
<div class="help-changelog-version">v${e.version} <span class="help-changelog-date">${e.date}</span></div>
|
|
|
|
|
<ul class="help-list">${e.changes.map(c => `<li>${c}</li>`).join('')}</ul>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-07-07 23:05:22 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="panel" id="panel-left">
|
|
|
|
|
<div class="panel-title">Resurser</div>
|
|
|
|
|
|
|
|
|
|
<div class="resource">
|
|
|
|
|
<div class="resource-label">Troende</div>
|
|
|
|
|
<div class="resource-value" id="r-troende">1</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="resource">
|
|
|
|
|
<div class="resource-label">Andakt <span id="r-andakt-rate" style="color:var(--dim);font-size:10px;"></span></div>
|
|
|
|
|
<div class="resource-value" id="r-andakt">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-08 17:28:32 +02:00
|
|
|
<div class="resource">
|
|
|
|
|
<div class="resource-label">Inflytande</div>
|
|
|
|
|
<div class="resource-value" id="r-inflytande">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
<div class="resource">
|
|
|
|
|
<div class="resource-label">Dread <span id="r-dread-rate" style="color:var(--dread);font-size:10px;"></span></div>
|
|
|
|
|
<div class="resource-value" style="color:var(--dread);" id="r-dread">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="resource bar-hemlighet" style="margin-top:16px;">
|
|
|
|
|
<div class="resource-label">Hemlighet</div>
|
|
|
|
|
<div class="resource-value" id="r-hemlighet">100</div>
|
|
|
|
|
<div class="resource-bar"><div class="resource-bar-fill" id="bar-hemlighet" style="width:100%"></div></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="resource bar-kaos">
|
|
|
|
|
<div class="resource-label">Kaos</div>
|
|
|
|
|
<div class="resource-value" id="r-kaos">0</div>
|
|
|
|
|
<div class="resource-bar"><div class="resource-bar-fill" id="bar-kaos" style="width:0%"></div></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="resource bar-ritual" id="ritual-section" style="display:none;">
|
|
|
|
|
<div class="resource-label">Ritual</div>
|
|
|
|
|
<div class="resource-value" style="color:var(--ritual);" id="r-ritual">0</div>
|
|
|
|
|
<div class="resource-bar"><div class="resource-bar-fill" id="bar-ritual" style="width:0%"></div></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:20px;">
|
|
|
|
|
<div class="panel-title">Åtgärder</div>
|
|
|
|
|
<div id="upgrades"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="map-container">
|
|
|
|
|
<div id="map"></div>
|
|
|
|
|
<div id="log"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="panel" id="panel-right">
|
|
|
|
|
<div class="panel-title">Aktiva områden</div>
|
|
|
|
|
<div id="areas" style="font-size:11px;color:var(--dim);line-height:2;">
|
|
|
|
|
<div id="area-rosengard">○ Rosengård</div>
|
|
|
|
|
<div id="area-varnhem">○ Värnhemstorget</div>
|
|
|
|
|
<div id="area-torso">○ Turning Torso</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="margin-top:20px;" id="investigator-section">
|
|
|
|
|
<div class="panel-title">Utredarna</div>
|
|
|
|
|
<div id="investigator-status" style="font-size:11px;color:var(--safe);">Inaktiva</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="overlay">
|
|
|
|
|
<div class="overlay-title" id="overlay-title"></div>
|
|
|
|
|
<div class="overlay-body" id="overlay-body"></div>
|
|
|
|
|
<button class="overlay-btn" id="overlay-btn">Börja om</button>
|
|
|
|
|
</div>
|
2026-07-12 23:17:48 +02:00
|
|
|
|
2026-07-13 00:01:51 +02:00
|
|
|
<div id="danger-vignette"></div>
|
|
|
|
|
|
2026-07-12 23:17:48 +02:00
|
|
|
<div id="event-modal" class="help-modal">
|
|
|
|
|
<div class="help-modal-inner" id="event-modal-inner">
|
|
|
|
|
<div class="help-modal-body">
|
|
|
|
|
<div class="event-modal-text" id="event-modal-text"></div>
|
|
|
|
|
<div class="event-modal-choices" id="event-modal-choices"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-07-07 23:05:22 +02:00
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
document.getElementById('overlay-btn')!.addEventListener('click', onRestart);
|
2026-07-13 09:54:46 +02:00
|
|
|
document.getElementById('start-btn')!.addEventListener('click', onStart);
|
|
|
|
|
document.getElementById('abandon-btn')!.addEventListener('click', () => {
|
|
|
|
|
if (window.confirm('Överge kulten? Cirkeln bryts och ni går skilda vägar.')) onAbandon();
|
|
|
|
|
});
|
2026-07-07 23:05:22 +02:00
|
|
|
|
2026-07-12 23:17:48 +02:00
|
|
|
document.getElementById('event-modal-choices')!.addEventListener('click', e => {
|
|
|
|
|
const btn = (e.target as Element).closest('[data-choice]') as HTMLElement | null;
|
|
|
|
|
if (btn?.dataset['choice'] !== undefined) onEventChoice(Number(btn.dataset['choice']));
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-07 23:31:37 +02:00
|
|
|
const helpModal = document.getElementById('help-modal')!;
|
|
|
|
|
const helpInner = document.getElementById('help-modal-inner')!;
|
|
|
|
|
document.getElementById('help-btn')!.addEventListener('click', () => helpModal.classList.toggle('visible'));
|
|
|
|
|
document.getElementById('help-close')!.addEventListener('click', () => helpModal.classList.remove('visible'));
|
|
|
|
|
helpModal.addEventListener('click', (e) => { if (!helpInner.contains(e.target as Node)) helpModal.classList.remove('visible'); });
|
|
|
|
|
document.addEventListener('keydown', (e) => { if (e.key === '?' || (e.key === 'Escape' && helpModal.classList.contains('visible'))) helpModal.classList.toggle('visible'); });
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
// Split the map container: map on top, log below
|
|
|
|
|
const mapContainer = document.getElementById('map-container')!;
|
2026-07-07 23:25:44 +02:00
|
|
|
mapContainer.style.cssText = 'display:flex;flex-direction:column;overflow:hidden;height:100%;';
|
2026-07-07 23:05:22 +02:00
|
|
|
const mapEl = document.getElementById('map')!;
|
2026-07-07 23:25:44 +02:00
|
|
|
mapEl.style.cssText = 'flex:0 0 45%;min-height:0;max-height:45%;';
|
2026-07-07 23:05:22 +02:00
|
|
|
const logEl = document.getElementById('log')!;
|
2026-07-07 23:25:44 +02:00
|
|
|
logEl.style.cssText = 'flex:1 1 0;overflow-y:auto;border-top:1px solid var(--border);min-height:0;';
|
2026-07-07 23:05:22 +02:00
|
|
|
|
|
|
|
|
// Upgrade click delegation
|
|
|
|
|
document.getElementById('upgrades')!.addEventListener('click', e => {
|
|
|
|
|
const btn = (e.target as Element).closest('[data-upgrade]') as HTMLElement | null;
|
|
|
|
|
if (btn?.dataset['upgrade']) onUpgrade(btn.dataset['upgrade']);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function renderState(state: State): void {
|
|
|
|
|
setText('r-troende', String(state.troende));
|
|
|
|
|
setText('r-andakt', fmt(state.andakt));
|
|
|
|
|
setText('r-andakt-rate', `+${(state.andaktRate * state.troende).toFixed(1)}/s`);
|
2026-07-08 17:28:32 +02:00
|
|
|
setText('r-inflytande', fmt(state.inflytande));
|
2026-07-07 23:05:22 +02:00
|
|
|
setText('r-dread', fmt(state.dread));
|
|
|
|
|
setText('r-dread-rate', state.dreadRate > 0 ? `+${state.dreadRate.toFixed(1)}/s` : '');
|
|
|
|
|
setText('r-hemlighet', Math.floor(state.hemlighet).toString());
|
|
|
|
|
setText('r-kaos', Math.floor(state.kaos).toString());
|
|
|
|
|
setBar('bar-hemlighet', state.hemlighet);
|
|
|
|
|
setBar('bar-kaos', state.kaos);
|
|
|
|
|
|
2026-07-13 00:01:51 +02:00
|
|
|
// Ambient visual reactivity — the deeper the dread, the more the world tilts; low hemlighet bleeds red at the edges.
|
|
|
|
|
const root = document.documentElement.style;
|
|
|
|
|
root.setProperty('--dread-intensity', Math.min(1, state.dread / 150).toFixed(3));
|
|
|
|
|
root.setProperty('--hemlighet-danger', (state.hemlighet < 40 ? (1 - state.hemlighet / 40) * 0.35 : 0).toFixed(3));
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
const ritualSection = document.getElementById('ritual-section')!;
|
|
|
|
|
if (state.ritualUnlocked) {
|
|
|
|
|
ritualSection.style.display = '';
|
|
|
|
|
setText('r-ritual', Math.floor(state.ritual).toString());
|
|
|
|
|
setBar('bar-ritual', state.ritual);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase tag
|
|
|
|
|
const phaseEl = document.getElementById('phase-tag')!;
|
|
|
|
|
if (state.ritual >= 50) {
|
|
|
|
|
phaseEl.textContent = 'Sent skede'; phaseEl.className = 'phase-tag late';
|
|
|
|
|
} else if (state.dread >= 30) {
|
|
|
|
|
phaseEl.textContent = 'Mitt skede'; phaseEl.className = 'phase-tag mid';
|
|
|
|
|
} else {
|
|
|
|
|
phaseEl.textContent = 'Tidigt skede'; phaseEl.className = 'phase-tag early';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Areas
|
|
|
|
|
setArea('area-rosengard', state.areas.rosengard, 'Rosengård');
|
|
|
|
|
setArea('area-varnhem', state.areas.varnhem, 'Värnhemstorget');
|
|
|
|
|
setArea('area-torso', state.areas.torso, 'Turning Torso');
|
|
|
|
|
|
|
|
|
|
// Investigator status
|
|
|
|
|
const inv = document.getElementById('investigator-status')!;
|
|
|
|
|
if (state.hemlighet < 20) {
|
|
|
|
|
inv.textContent = 'Nära — väldigt nära'; inv.style.color = 'var(--danger)';
|
|
|
|
|
} else if (state.hemlighet < 50) {
|
2026-07-08 17:28:32 +02:00
|
|
|
inv.textContent = 'Misstänksamma'; inv.style.color = 'var(--kaos)';
|
2026-07-07 23:05:22 +02:00
|
|
|
} else {
|
|
|
|
|
inv.textContent = 'Inaktiva'; inv.style.color = 'var(--safe)';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upgrades
|
|
|
|
|
renderUpgrades(state);
|
|
|
|
|
|
|
|
|
|
// Log
|
|
|
|
|
renderLog(state);
|
|
|
|
|
|
2026-07-13 09:54:46 +02:00
|
|
|
// Intro screen
|
|
|
|
|
document.getElementById('intro-screen')!.classList.toggle('visible', state.phase === 'intro');
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
// Overlay
|
2026-07-13 09:54:46 +02:00
|
|
|
const overlay = document.getElementById('overlay')!;
|
|
|
|
|
if (state.phase === 'won' || state.phase === 'lost' || state.phase === 'abandoned') {
|
|
|
|
|
showOverlay(state);
|
|
|
|
|
} else {
|
|
|
|
|
overlay.classList.remove('visible');
|
|
|
|
|
}
|
2026-07-12 23:17:48 +02:00
|
|
|
|
|
|
|
|
// Event choice modal
|
|
|
|
|
renderEventModal(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let lastPendingEventId: string | null = null;
|
|
|
|
|
|
|
|
|
|
function renderEventModal(state: State): void {
|
|
|
|
|
const modal = document.getElementById('event-modal')!;
|
|
|
|
|
const pending = state.pendingEvent;
|
|
|
|
|
const id = pending?.event.id ?? null;
|
|
|
|
|
if (id === lastPendingEventId) return;
|
|
|
|
|
lastPendingEventId = id;
|
|
|
|
|
|
|
|
|
|
if (!pending) {
|
|
|
|
|
modal.classList.remove('visible');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('event-modal-text')!.textContent = pending.event.text;
|
|
|
|
|
const choices = document.getElementById('event-modal-choices')!;
|
|
|
|
|
choices.innerHTML = (pending.event.choices ?? []).map((c, i) =>
|
|
|
|
|
`<button class="upgrade-btn" data-choice="${i}"><span class="up-name">${c.text}</span></button>`
|
|
|
|
|
).join('');
|
|
|
|
|
modal.classList.add('visible');
|
2026-07-07 23:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-07 23:25:44 +02:00
|
|
|
let lastUpgradeKey = '';
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
function renderUpgrades(state: State): void {
|
|
|
|
|
const container = document.getElementById('upgrades')!;
|
|
|
|
|
const available = UPGRADES.filter(u => u.available(state) && (u.once ? !state.upgrades.has(u.id) : true));
|
|
|
|
|
|
2026-07-07 23:25:44 +02:00
|
|
|
// Only rebuild DOM when availability or affordability changes
|
|
|
|
|
const key = available.map(u => {
|
2026-07-08 17:28:32 +02:00
|
|
|
const cost = u.cost(state);
|
|
|
|
|
const canAfford = (!cost.andakt || state.andakt >= cost.andakt) && (!cost.dread || state.dread >= cost.dread) && (!cost.inflytande || state.inflytande >= cost.inflytande);
|
|
|
|
|
return `${u.id}:${canAfford}:${cost.andakt ?? ''}`;
|
2026-07-07 23:25:44 +02:00
|
|
|
}).join('|');
|
|
|
|
|
|
|
|
|
|
if (key === lastUpgradeKey) return;
|
|
|
|
|
lastUpgradeKey = key;
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
if (!available.length) {
|
|
|
|
|
container.innerHTML = '<div style="font-size:11px;color:var(--dim);">Inga tillgängliga åtgärder.</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
container.innerHTML = available.map(u => {
|
2026-07-08 17:28:32 +02:00
|
|
|
const cost = u.cost(state);
|
|
|
|
|
const canAfford = (!cost.andakt || state.andakt >= cost.andakt) && (!cost.dread || state.dread >= cost.dread) && (!cost.inflytande || state.inflytande >= cost.inflytande);
|
2026-07-07 23:05:22 +02:00
|
|
|
const costs = [
|
2026-07-08 17:28:32 +02:00
|
|
|
cost.andakt ? `${cost.andakt} andakt` : '',
|
|
|
|
|
cost.dread ? `${cost.dread} dread` : '',
|
|
|
|
|
cost.inflytande ? `${cost.inflytande} inflytande` : '',
|
2026-07-07 23:05:22 +02:00
|
|
|
].filter(Boolean).join(', ');
|
|
|
|
|
return `<button class="upgrade-btn" data-upgrade="${u.id}" ${canAfford ? '' : 'disabled'}>
|
|
|
|
|
<span class="up-name">${u.name}</span>
|
|
|
|
|
<span class="up-desc">${u.desc}</span>
|
|
|
|
|
<span class="up-cost">Kostnad: ${costs || '—'}</span>
|
|
|
|
|
</button>`;
|
|
|
|
|
}).join('');
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-07 23:13:54 +02:00
|
|
|
let lastLogLength = 0;
|
|
|
|
|
|
2026-07-07 23:05:22 +02:00
|
|
|
function renderLog(state: State): void {
|
|
|
|
|
const log = document.getElementById('log')!;
|
2026-07-07 23:13:54 +02:00
|
|
|
if (state.log.length === lastLogLength) return;
|
|
|
|
|
lastLogLength = state.log.length;
|
|
|
|
|
// state.log is newest-first (unshift); render as-is, scroll to top
|
2026-07-07 23:05:22 +02:00
|
|
|
log.innerHTML = state.log.map(e =>
|
|
|
|
|
`<div class="log-entry ${e.kind}">${e.text}</div>`
|
|
|
|
|
).join('');
|
2026-07-07 23:13:54 +02:00
|
|
|
log.scrollTop = 0;
|
2026-07-13 00:01:51 +02:00
|
|
|
|
|
|
|
|
if (state.log[0]?.kind === 'kaos') {
|
|
|
|
|
const app = document.getElementById('app')!;
|
|
|
|
|
app.classList.remove('kaos-glitch');
|
|
|
|
|
void app.offsetWidth; // restart animation
|
|
|
|
|
app.classList.add('kaos-glitch');
|
|
|
|
|
}
|
2026-07-07 23:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showOverlay(state: State): void {
|
|
|
|
|
const ov = document.getElementById('overlay')!;
|
|
|
|
|
ov.classList.add('visible');
|
|
|
|
|
const title = document.getElementById('overlay-title')!;
|
|
|
|
|
const body = document.getElementById('overlay-body')!;
|
|
|
|
|
if (state.phase === 'won') {
|
2026-07-13 09:54:46 +02:00
|
|
|
ov.style.backgroundImage = "linear-gradient(rgba(6,6,14,0.72), rgba(6,6,14,0.85)), url('/img/win.webp')";
|
2026-07-07 23:05:22 +02:00
|
|
|
title.textContent = 'FULLBORDAT';
|
|
|
|
|
title.className = 'overlay-title won';
|
|
|
|
|
body.textContent = 'Det är gjort. Turning Torso pekar rakt ned. Havet är stilla — alltför stilla. Malmö fortsätter som vanligt. Det är det värsta.';
|
2026-07-13 09:54:46 +02:00
|
|
|
} else if (state.phase === 'abandoned') {
|
|
|
|
|
ov.style.backgroundImage = "linear-gradient(rgba(6,6,14,0.72), rgba(6,6,14,0.85)), url('/img/fail.webp')";
|
|
|
|
|
title.textContent = 'ÖVERGIVET';
|
|
|
|
|
title.className = 'overlay-title lost';
|
|
|
|
|
body.textContent = 'Ni lämnar källaren. Cirkeln bryts. Den Sovande sover vidare, ovetande, ofärdig.';
|
2026-07-07 23:05:22 +02:00
|
|
|
} else {
|
2026-07-13 09:54:46 +02:00
|
|
|
ov.style.backgroundImage = "linear-gradient(rgba(6,6,14,0.72), rgba(6,6,14,0.85)), url('/img/fail.webp')";
|
2026-07-07 23:05:22 +02:00
|
|
|
title.textContent = 'AVSLÖJAD';
|
|
|
|
|
title.className = 'overlay-title lost';
|
|
|
|
|
body.textContent = state.lostReason ?? 'Utredarna kom.';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setText(id: string, val: string): void {
|
|
|
|
|
const el = document.getElementById(id);
|
|
|
|
|
if (el) el.textContent = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setBar(id: string, pct: number): void {
|
|
|
|
|
const el = document.getElementById(id) as HTMLElement | null;
|
|
|
|
|
if (el) el.style.width = `${Math.min(100, Math.max(0, pct))}%`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setArea(id: string, active: boolean, label: string): void {
|
|
|
|
|
const el = document.getElementById(id);
|
|
|
|
|
if (el) {
|
|
|
|
|
el.textContent = `${active ? '●' : '○'} ${label}`;
|
|
|
|
|
el.style.color = active ? 'var(--dread)' : 'var(--dim)';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fmt(n: number): string {
|
|
|
|
|
return Math.floor(n).toLocaleString('sv-SE');
|
|
|
|
|
}
|