Every run gets a door out: the quit chip and the LEAVE THE MATCH? ask
"all tennis screens are missing the ability to exit the game and return to the menu — right now I need to force close the app." A quiet x chip rides the top-right corner of every run (the scoreboard owns the left; the desktop key hint shifts inboard). Tapping it — or Escape on desktop — freezes the world with the existing paused flag and asks LEAVE THE MATCH?: BACK TO THE MENU in the menu's crimson, PLAY ON underneath. Quitting is the death path minus the death — restartRun, refreshSplash, splash — and banks nothing: an abandoned run never happened, statistically. Verified live both ways: PLAY ON resumes the frozen rally, quitting lands on the splash with the chip retired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1facc954e2
commit
11ca935ad0
2 files changed
+46
-3
No files matched your search
+24
-3
@@ -78,9 +78,22 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
html { height: 100vh; }
|
||||
canvas { height: calc(100vh - env(safe-area-inset-top, 0px)); }
|
||||
}
|
||||
/* desktop key hint — up in the empty top-right corner now that the rack owns the bottom edge */
|
||||
#hint { position: fixed; top: 6px; right: 10px; text-align: right;
|
||||
/* desktop key hint — top-right, shifted left of the quit chip that owns the corner now */
|
||||
#hint { position: fixed; top: 6px; right: 52px; text-align: right;
|
||||
font: 400 10px Silkscreen, monospace; color: #6b7688; }
|
||||
/* THE DOOR OUT (Oliver: every tennis screen lacked an exit — force-closing the app was the only
|
||||
way back to the menu). A quiet chip during play; tapping it freezes the world and asks. */
|
||||
#quit { position: fixed; top: calc(env(safe-area-inset-top, 0px) + 8px); right: 8px; z-index: 5; }
|
||||
#quit button { width: 34px; height: 34px; background: rgba(14,23,20,0.72); border: 2px solid var(--line);
|
||||
color: var(--dim); font: 400 15px Silkscreen, monospace; cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent; }
|
||||
#qAsk { position: fixed; inset: 0; z-index: 6; background: rgba(0,0,0,0.55);
|
||||
display: flex; align-items: center; justify-content: center; }
|
||||
#quit[hidden], #qAsk[hidden] { display: none; }
|
||||
.qBox { background: var(--solid); border: 2px solid var(--line); padding: 18px 16px;
|
||||
width: min(320px, 84vw); display: flex; flex-direction: column; gap: 10px; }
|
||||
.qBox p { margin: 0 0 6px; font: 400 12px Silkscreen, monospace; color: var(--cream);
|
||||
text-align: center; letter-spacing: 1px; }
|
||||
@media (pointer: coarse) { #hint { display: none; } }
|
||||
/* ---- THE MENU PALETTE. The canvas HUD keeps its own blue-grey and gold; the menus are
|
||||
deliberately a different room — dark court green, one acid accent, and one crimson reserved for
|
||||
@@ -355,6 +368,14 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
<canvas id="c" width="480" height="270"></canvas>
|
||||
<div id="safe"></div>
|
||||
<div id="hint">move: WASD/arrows · serve/swing: SPACE · aim: hold a direction while swinging · T: tuning panel</div>
|
||||
<div id="quit" hidden><button id="qChip">✕</button></div>
|
||||
<div id="qAsk" hidden>
|
||||
<div class="qBox">
|
||||
<p>LEAVE THE MATCH?</p>
|
||||
<button class="btn big" id="qYes">BACK TO THE MENU</button>
|
||||
<button class="btn sec" id="qNo">PLAY ON</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="intro" data-stage="splash">
|
||||
<div class="card">
|
||||
<!-- The title screen IS the menu. A separate menu stage behind it was the same logo over the
|
||||
@@ -471,6 +492,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=237"></script>
|
||||
<script type="module" src="src/main.js?v=238"></script>
|
||||
</body>
|
||||
</html>
|
||||
+22
@@ -230,6 +230,7 @@ function start(modeId = pickedMode) {
|
||||
// differ by at this point: gore is the game as it always was, classic is the same tennis with the
|
||||
// turn switched off, and zombie tennis is the match taking the court. Everything else about a mode
|
||||
// lives in the code that reads G.classic / G.match, never here.
|
||||
quitChip.hidden = false; // the door out rides every run (see the quit block below)
|
||||
G.classic = modeId === 'classic';
|
||||
// the difficulty rides on G like G.classic does, so a mid-run restart keeps the CPU you chose
|
||||
G.diff = G.classic ? (DIFFS.find(d => d.id === pickedDiff) || DIFFS[1]) : null;
|
||||
@@ -467,6 +468,7 @@ const maybeRestart = () => {
|
||||
restartRun();
|
||||
refreshSplash(); // the run just ended: the splash count has moved
|
||||
intro.hidden = false;
|
||||
quitChip.hidden = true; // the door out goes with the run it belonged to
|
||||
// stats.lastRun() survives restartRun on purpose — it lives in the stats module, not on G, which
|
||||
// has just been restored from PRISTINE. Consumed on read, so the prompt appears once.
|
||||
const run = stats.lastRun();
|
||||
@@ -476,6 +478,26 @@ const maybeRestart = () => {
|
||||
cv.addEventListener('pointerdown', maybeRestart);
|
||||
addEventListener('keydown', maybeRestart);
|
||||
|
||||
// THE DOOR OUT (Oliver: every tennis screen lacked an exit — force-closing the app was the only
|
||||
// way back). The chip shows for the whole run; tapping it (or Escape on desktop) freezes the
|
||||
// world and asks. Quitting is the death path minus the death: restartRun resets everything and
|
||||
// nothing is banked — an abandoned run never happened, statistically.
|
||||
const quitChip = document.getElementById('quit'), qAsk = document.getElementById('qAsk');
|
||||
document.getElementById('qChip').addEventListener('click', () => { paused = true; qAsk.hidden = false; });
|
||||
document.getElementById('qNo').addEventListener('click', () => { qAsk.hidden = true; paused = false; });
|
||||
document.getElementById('qYes').addEventListener('click', () => {
|
||||
qAsk.hidden = true; paused = false; quitChip.hidden = true;
|
||||
restartRun();
|
||||
refreshSplash();
|
||||
intro.hidden = false;
|
||||
go('splash');
|
||||
});
|
||||
addEventListener('keydown', e => {
|
||||
if (e.key !== 'Escape' || !intro.hidden || quitChip.hidden) return;
|
||||
qAsk.hidden = !qAsk.hidden;
|
||||
paused = !qAsk.hidden;
|
||||
});
|
||||
|
||||
// A tap on the SCOREBOARD toggles the miss-log overlay, and that is now all it does. The triple-tap
|
||||
// that opened test mode has moved to the back room on the splash (see below): a cheat you can trip
|
||||
// by accident mid-run is a bug, and this one sat on the one thing you tap to read the log.
|
||||
|
||||
Reference in new issue
Block a user