Add intro screen, win/loss art, and a way to abandon the ritual

- Adds a full-screen intro/title screen (new 'intro' game phase) with
  a moody Malmö/Turning Torso background and a "Börja ritualen"
  button; the game no longer starts ticking until the player begins.
- Win and loss overlays now show matching generated artwork behind
  the existing text, instead of a flat scrim.
- New "Avbryt" header button lets the player abandon a run mid-game
  (with a confirm prompt) via a new 'abandoned' phase, reusing the
  existing overlay/restart flow.
- Source images converted from PNG (~2.1MB each) to WebP (~120KB
  each) and placed under public/img/ so Vite serves them as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-13 09:54:46 +02:00
co-authored by Claude Sonnet 5
parent 10c7999701
commit 4d47409e03
8 changed files with 130 additions and 10 deletions
+45 -3
View File
@@ -2,12 +2,35 @@ import type { State } from './engine.ts';
import { UPGRADES } from './upgrades.ts';
import { VERSION, CHANGELOG } from './data/version.ts';
export function initUI(app: HTMLElement, onUpgrade: (id: string) => void, onRestart: () => void, onEventChoice: (index: number) => void): void {
export function initUI(
app: HTMLElement,
onUpgrade: (id: string) => void,
onRestart: () => void,
onEventChoice: (index: number) => void,
onStart: () => void,
onAbandon: () => void,
): void {
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>
<button id="help-btn" class="help-btn" title="Hjälp &amp; Ändringslogg">? v${VERSION}</button>
<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 &amp; Ä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>
</div>
<div id="help-modal" class="help-modal">
@@ -134,6 +157,10 @@ export function initUI(app: HTMLElement, onUpgrade: (id: string) => void, onRest
`;
document.getElementById('overlay-btn')!.addEventListener('click', onRestart);
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();
});
document.getElementById('event-modal-choices')!.addEventListener('click', e => {
const btn = (e.target as Element).closest('[data-choice]') as HTMLElement | null;
@@ -217,8 +244,16 @@ export function renderState(state: State): void {
// Log
renderLog(state);
// Intro screen
document.getElementById('intro-screen')!.classList.toggle('visible', state.phase === 'intro');
// Overlay
if (state.phase !== 'playing') showOverlay(state);
const overlay = document.getElementById('overlay')!;
if (state.phase === 'won' || state.phase === 'lost' || state.phase === 'abandoned') {
showOverlay(state);
} else {
overlay.classList.remove('visible');
}
// Event choice modal
renderEventModal(state);
@@ -309,10 +344,17 @@ function showOverlay(state: State): void {
const title = document.getElementById('overlay-title')!;
const body = document.getElementById('overlay-body')!;
if (state.phase === 'won') {
ov.style.backgroundImage = "linear-gradient(rgba(6,6,14,0.72), rgba(6,6,14,0.85)), url('/img/win.webp')";
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.';
} 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.';
} else {
ov.style.backgroundImage = "linear-gradient(rgba(6,6,14,0.72), rgba(6,6,14,0.85)), url('/img/fail.webp')";
title.textContent = 'AVSLÖJAD';
title.className = 'overlay-title lost';
body.textContent = state.lostReason ?? 'Utredarna kom.';