From 65be60b139fba075245848a809ea989d5e4edb1c Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Fri, 14 Aug 2026 11:35:44 +0200 Subject: [PATCH] The setup became a wizard: one decision per screen, tap advances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "the mode setup is really overwhelming now" — three posters plus a sub-row plus player tiles plus court meters was a wall. Discussed, Oliver picked the wizard with tap-advance over next-buttons and the accordion: PICK A GAME (three posters, now 148px tall — alone on a screen they get to be posters), then the mode's own second step only when it has one (classic: DIFFICULTY chips stacked tall; zombie tennis: ARCADE and THE TOURNAMENT as full posters; gore skips straight through), then MATCH SETUP holding player + court + SERVE UP with the mode riding the summary footer. Tapping selects AND advances — one decision per screen is the wizard's whole promise, and a NEXT under a single row of choices is a second tap for nothing. Back walks the chain the way it came (who-and-where returns to the second step only when the mode has one). Every step comes up pre-selected from last time, so a returning player is three taps from serving. Three new stages on the intro's existing data-stage switcher; splash Enter goes to 'mode' now. Co-Authored-By: Claude Fable 5 --- index.html | 45 ++++++++++++++++++++++++++++----------------- src/main.js | 21 +++++++++++++++++---- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 62c74a0..86263d8 100644 --- a/index.html +++ b/index.html @@ -140,11 +140,13 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { /* ONE overlay, four panels, swapped by #intro[data-stage]. Keeping the backdrop and the card frame alive across every screen is the point — a screen that unmounts the whole overlay flashes the court through between menus. */ - #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub { display: none; } #intro[data-stage="splash"] #splash { display: flex; flex-direction: column; flex: 1; } /* flex COLUMN, not block: it is what lets the footer's margin-top:auto reach the bottom of a screen whose content does not fill it */ #intro[data-stage="setup"] #setup, + #intro[data-stage="mode"] #stmode, + #intro[data-stage="modesub"] #stsub, #intro[data-stage="stats"] #stats, #intro[data-stage="ranks"] #ranks, #intro[data-stage="secret"] #secret, @@ -235,16 +237,15 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { at cream-on-crowd was the report from the first live look */ #modeList i, #modeSub i { color: #fff; font-size: 10px; margin-bottom: 0; text-shadow: 0 1px 0 #000, 1px 0 0 #000, -1px 0 0 #000, 0 -1px 0 #000, 0 0 7px #000; } - /* THE SECOND STEP (config.MENU): the picked poster's own choices, inset so they read as its - children. Sub-mode cards are the same poster at ~2/3 height, centred on their scene rather - than bottom-anchored (a 64px window off the bottom of these scenes is all court and shins); - the difficulty chips are plain acid-outline buttons — art for EASY/MEDIUM/HARD would be - forced, and the chip row reads faster anyway. */ - #modeSub { display: flex; flex-wrap: wrap; gap: 8px; padding-left: 14px; margin: 0 0 8px; } - #modeSub:empty { display: none; } - #modeSub button.mode.sm { flex: 1 1 100%; min-height: 64px; margin: 0; background-position: center; } - #modeSub button.chip { flex: 1; padding: 12px 0; background: var(--panel); border: 2px solid var(--line); - color: var(--cream); font: 400 11px Silkscreen, monospace; letter-spacing: 1px; + /* THE SECOND STEP (config.MENU) has its own SCREEN in the wizard: sub-mode cards are + full-height posters centred on their scene, and the difficulty chips are plain acid-outline + buttons stacked tall — art for EASY/MEDIUM/HARD would be forced, and the chip reads faster. */ + /* alone on their own screens, the posters get room to be posters */ + #stmode button.mode, #modeSub button.mode { min-height: 148px; } + #modeSub { display: flex; flex-wrap: wrap; gap: 8px; } + #modeSub button.mode { background-position: center; } + #modeSub button.chip { flex: 1 1 100%; padding: 22px 0; background: var(--panel); border: 2px solid var(--line); + color: var(--cream); font: 400 13px Silkscreen, monospace; letter-spacing: 2px; cursor: pointer; -webkit-tap-highlight-color: transparent; } #modeSub button.chip.on { border-color: var(--acid); color: var(--acid); background: rgba(188,212,85,0.09); } #courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%; @@ -378,14 +379,24 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { + +
+

PICK A GAME

+
+
+
+

DIFFICULTY

+
+

MATCH SETUP

-

01 · MODE

-
-
-

02 · PLAYER

+

01 · PLAYER

-

03 · COURT

+

02 · COURT

@@ -456,6 +467,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
- + diff --git a/src/main.js b/src/main.js index c7d8d7f..725a9db 100644 --- a/src/main.js +++ b/src/main.js @@ -92,9 +92,12 @@ const banner = (card, cls, on) => { return b; }; const pickMode = id => { pickedMode = id; try { localStorage.setItem('mode', id); } catch {} }; +// TAP ADVANCES. Each screen's tap both selects and moves on — the wizard's whole promise is one +// decision per screen, and a NEXT button under a single row of choices is a second tap for nothing. function buildSub() { modeSub.innerHTML = ''; const t = topOf(pickedMode); + document.getElementById('subTitle').textContent = t.diffs ? 'DIFFICULTY' : (t.name || ''); if (t.diffs) for (const d of DIFFS) { const b = document.createElement('button'); b.className = 'chip' + (d.id === pickedDiff ? ' on' : ''); @@ -104,15 +107,17 @@ function buildSub() { try { localStorage.setItem('diff', d.id); } catch {} for (const el of modeSub.children) el.classList.toggle('on', el === b); summary(); + go('setup'); }); modeSub.appendChild(b); } else if (t.kids) for (const id of t.kids) { - const b = banner(modeOf(id), 'mode sm', id === pickedMode); + const b = banner(modeOf(id), 'mode', id === pickedMode); b.addEventListener('click', () => { pickMode(id); for (const el of modeSub.children) el.classList.toggle('on', el === b); summary(); + go('setup'); }); modeSub.appendChild(b); } @@ -125,6 +130,7 @@ for (const t of MENU) { for (const el of modeList.children) el.classList.toggle('on', el === b); buildSub(); summary(); + go(t.diffs || t.kids ? 'modesub' : 'setup'); }); modeList.appendChild(b); } @@ -232,7 +238,7 @@ const openStats = () => { for (const el of document.getElementById('stModes').children) el.classList.toggle('on', el.dataset.m === stMode); stats.drawStats(stPeriod, SPRV, stMode); go('stats'); }; -document.getElementById('mStart').addEventListener('click', () => go('setup')); +document.getElementById('mStart').addEventListener('click', () => go('mode')); document.getElementById('mStats').addEventListener('click', openStats); document.getElementById('mRanks').addEventListener('click', () => { go('ranks'); stats.drawRanks(); }); @@ -281,8 +287,15 @@ nbName.addEventListener('keydown', e => { if (e.key === 'Enter') document.getEle // ZOMBIE TENNIS did nothing at all. A default parameter only applies to `undefined`, and a click // handler always passes something. document.getElementById('cStart').addEventListener('click', () => start()); -for (const id of ['sBack', 'stBack', 'rkBack', 'scBack']) +for (const id of ['mBack', 'stBack', 'rkBack', 'scBack']) document.getElementById(id).addEventListener('click', () => go('splash')); +// the wizard walks back the way it came: who-and-where returns to the mode's own second step when +// it has one, the second step returns to the posters +document.getElementById('subBack').addEventListener('click', () => go('mode')); +document.getElementById('sBack').addEventListener('click', () => { + const t = topOf(pickedMode); + go(t.diffs || t.kids ? 'modesub' : 'mode'); +}); document.getElementById('bsBack').addEventListener('click', () => go('secret')); // ---- THE BACK ROOM: five taps in the TOP-LEFT CORNER of the splash (#hot, an invisible box in @@ -410,7 +423,7 @@ addEventListener('keydown', e => { if (e.key === 'Escape') return go('splash'); if (stage === 'splash') { if (!loaded()) return; // the boot bar hides the buttons; a key must not walk past it - if (e.key === 'Enter' || e.key === ' ') go('setup'); + if (e.key === 'Enter' || e.key === ' ') go('mode'); else if (e.key === 's' || e.key === 'S') openStats(); return; }