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:
co-authored by
Claude Sonnet 5
parent
10c7999701
commit
4d47409e03
@@ -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 & Ä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 & Ä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.';
|
||||
|
||||
Reference in New Issue
Block a user