The menu grows a second step, classic learns difficulty, and the rally feel unifies

Oliver's restructure: mode first, then what the mode asks — classic deals
EASY / MEDIUM / HARD chips, ZOMBIE TENNIS deals two sub-mode posters (ARCADE,
the exhibition ladder as was, and THE TOURNAMENT), and SPLATTER GORE stands
alone as the third top card. config.MENU is the grouping, MODES stays the four
run modes, and pickedMode stays the run-mode id — start(), stats and the
summary never learn the menu got taller. The sub-row sits inset under the
posters; sub-cards are the poster at 2/3 height centred on their scene.

DIFFICULTY is the one real gameplay addition: config.DIFFS scales the three
dials the classic CPU already reads (flub, quality cap, aiSpeed), MEDIUM
pinned by test to the shipped classicFlub/classicQualityMax so the middle chip
IS yesterday's game. Rides on G.diff next to G.classic, so restarts keep it.

AND THE FEEL SPLIT OLIVER REPORTED IS REAL: "in classic tennis I tend to miss
balls way more often" — the match's three contact helpers (matchReach 1.35,
the matchAssist floor, the short-ball chase) were all G.match-gated, so
classic played with a 35% smaller racket and no rescue on the dying drop
shot. rallyFeel() = match || classic now carries all three; gore's opener
keeps the old prologue feel on purpose.

The ARCADE poster took two rolls: the first came back a flat sticker line-up
("very constructed, not artsy"), the second anchors on the zombie poster
itself as a MOOD reference — one challenger lit at the net, the queue receding
into purple haze. Rolls in _refcheck/modebtn. The cup's stats mapping also
joins MODE_MATCH (it banks through endRun as MATCH and read back as GORE).
SPRV 117, main.js v233, 66 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-14 10:29:51 +02:00
1 parent d3bbd8e157
commit e4c917e23d
7 files changed
+141 -34

No files matched your search

+21 -8
View File
@@ -223,18 +223,30 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
the art + a scrim gradient as the background stack) with the caption written over the bottom.
Unpicked buttons sit dimmed and desaturated so the chosen mode is the one that glows — the
same "one bright thing means selected" rule the menu palette already follows. */
#modeList button.mode { display: flex; align-items: flex-end; width: 100%; min-height: 96px;
#modeList button.mode, #modeSub button.mode { display: flex; align-items: flex-end; width: 100%; min-height: 96px;
margin: 0 0 8px; padding: 7px 9px; background-size: cover; background-position: center bottom;
image-rendering: pixelated; border: 2px solid var(--line); color: inherit; font: inherit;
text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
filter: saturate(0.45) brightness(0.62); transition: filter 0.15s; }
#modeList button.mode.on { border-color: var(--acid); filter: none; }
#modeList b { text-shadow: 0 1px 0 #000, 0 0 6px rgba(0,0,0,0.9); }
#modeList button.mode.on, #modeSub button.mode.on { border-color: var(--acid); filter: none; }
#modeList b, #modeSub b { text-shadow: 0 1px 0 #000, 0 0 6px rgba(0,0,0,0.9); }
/* the subtitle carries the mode's one-line rules over busy art, so it gets WHITE at full
opacity, a size up from the court rows' 9px, and a solid black underlay — "barely readable"
at cream-on-crowd was the report from the first live look */
#modeList i { color: #fff; font-size: 10px; margin-bottom: 0;
#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;
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%;
margin: 0 0 8px; padding: 7px; background: var(--panel);
border: 2px solid var(--line); color: inherit; font: inherit; text-align: left;
@@ -242,9 +254,9 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
#courtList button.court.on { border-color: var(--acid); background: rgba(188,212,85,0.09); }
#courtList img { width: 92px; height: 66px; display: block; image-rendering: pixelated; flex: none;
object-fit: cover; }
#courtList .cb, #modeList .cb { flex: 1; min-width: 0; }
#courtList b, #modeList b { display: block; font: 400 12px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
#courtList i, #modeList i { display: block; margin: 3px 0 6px; font: 400 9px Silkscreen, monospace; color: var(--dim); font-style: normal; }
#courtList .cb, #modeList .cb, #modeSub .cb { flex: 1; min-width: 0; }
#courtList b, #modeList b, #modeSub b { display: block; font: 400 12px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
#courtList i, #modeList i, #modeSub i { display: block; margin: 3px 0 6px; font: 400 9px Silkscreen, monospace; color: var(--dim); font-style: normal; }
/* The two meters read straight off the court's own bounce numbers (see main.js), so they cannot
drift away from what the ball actually does on it. */
.meter { display: flex; align-items: center; gap: 6px; margin-top: 4px;
@@ -370,6 +382,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
<div class="hdr"><button class="chev" id="sBack">&lsaquo;</button><h2>MATCH SETUP</h2></div>
<h3>01 · MODE</h3>
<div id="modeList"></div>
<div id="modeSub"></div>
<h3>02 · PLAYER</h3>
<div id="tours"></div>
<h3>03 · COURT</h3>
@@ -443,6 +456,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
</div>
</div>
</div>
<script type="module" src="src/main.js?v=232"></script>
<script type="module" src="src/main.js?v=233"></script>
</body>
</html>