A poster answers, a border promises: the question screens drop their checkbox half
Oliver named the wizard's wrongness precisely: a pre-highlighted card that also navigates is "a checkbox and a button combined in one". The border said "this is a setting", the tap said "this was an answer", and the screen left before the setting-reading of it could cash out. So the two roles live on different screens now. The question screens (PICK A GAME, DIFFICULTY, the zombie pair) carry NO selection: every poster and chip is full-bright and equal — the dimming went too, it read as disabled — and tapping answers and advances. The remembered run survives only as a small LAST tag in the card's corner: an indicator, not a selection. The final screen keeps its acid borders honestly, because SERVE UP is a real confirm — the rule is "a border means SERVE UP will use this; a bright poster means tap me to answer". Question screens rebuild on entry so LAST follows what was actually played, and the first card clears the header by the margin the old h3 rows used to give (Oliver's padding note). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
65be60b139
commit
6b795f77cc
2 files changed
+47
-33
No files matched your search
+14
-10
@@ -221,16 +221,19 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
letter-spacing: 1px; }
|
||||
/* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still
|
||||
reads at this size. -webkit-tap-highlight-color kills iOS's blue flash over the pixel art. */
|
||||
/* MODE buttons are the BANNER row: the whole button is a bright scene of its mode (main.js sets
|
||||
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. */
|
||||
/* MODE posters are pure BUTTONS, not selectors (Oliver: the remembered highlight made each one
|
||||
"a checkbox and a button combined"). Every card is full-bright and equal because every card is
|
||||
a live answer to the header's question; tapping answers and advances. The only memory shown is
|
||||
the LAST tag — an indicator, not a selection. A border that means "SERVE UP will use this"
|
||||
exists solely on the final screen, where a confirm button actually follows. */
|
||||
#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, #modeSub button.mode.on { border-color: var(--acid); filter: none; }
|
||||
position: relative; }
|
||||
.last { position: absolute; top: 7px; right: 9px; font: 400 8px Silkscreen, monospace;
|
||||
color: var(--acid); background: rgba(0,0,0,0.72); padding: 3px 6px;
|
||||
letter-spacing: 1px; border: 1px solid rgba(188,212,85,0.5); }
|
||||
#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"
|
||||
@@ -240,14 +243,15 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
/* 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 */
|
||||
/* alone on their own screens, the posters get room to be posters — and the first card clears
|
||||
the header by the same breath the h3 rows used to give the old single page */
|
||||
#stmode #modeList, #stsub #modeSub { margin-top: 12px; }
|
||||
#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); }
|
||||
cursor: pointer; -webkit-tap-highlight-color: transparent; position: relative; }
|
||||
#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;
|
||||
@@ -467,6 +471,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=234"></script>
|
||||
<script type="module" src="src/main.js?v=235"></script>
|
||||
</body>
|
||||
</html>
|
||||
+33
-23
@@ -82,17 +82,22 @@ const modeOf = id => MODES.find(x => x.id === id);
|
||||
const topOf = m => MENU.find(t => t.mode === m || (t.kids && t.kids.includes(m))) || MENU[0];
|
||||
const modeList = document.getElementById('modeList');
|
||||
const modeSub = document.getElementById('modeSub');
|
||||
const banner = (card, cls, on) => {
|
||||
// QUESTION SCREENS CARRY NO SELECTION (Oliver: a highlighted card that also navigates is "a
|
||||
// checkbox and a button combined"). Every poster is full-bright and equal — all of them are live
|
||||
// answers — and the only memory shown is the LAST tag, an indicator rather than a selection. The
|
||||
// select-and-confirm border lives solely on the final screen, where SERVE UP actually confirms.
|
||||
const banner = (card, cls, last) => {
|
||||
const b = document.createElement('button');
|
||||
b.className = cls + (on ? ' on' : '');
|
||||
b.className = cls;
|
||||
// the scrim rides IN the background stack (gradient over art), so the caption needs no extra box
|
||||
b.style.backgroundImage =
|
||||
`linear-gradient(180deg, rgba(0,0,0,0) 34%, rgba(0,0,0,0.78)), url(assets/${card.art}.png?v=${SPRV})`;
|
||||
b.innerHTML = `<span class="cb"><b>${card.name}</b><i>${card.sub}</i></span>`;
|
||||
b.innerHTML = `<span class="cb"><b>${card.name}</b><i>${card.sub}</i></span>`
|
||||
+ (last ? '<u class="last">LAST</u>' : '');
|
||||
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
|
||||
// TAP ADVANCES. Each screen's tap both answers 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 = '';
|
||||
@@ -100,12 +105,11 @@ function buildSub() {
|
||||
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' : '');
|
||||
b.textContent = d.name;
|
||||
b.className = 'chip';
|
||||
b.innerHTML = d.name + (d.id === pickedDiff ? '<u class="last">LAST</u>' : '');
|
||||
b.addEventListener('click', () => {
|
||||
pickedDiff = d.id;
|
||||
try { localStorage.setItem('diff', d.id); } catch {}
|
||||
for (const el of modeSub.children) el.classList.toggle('on', el === b);
|
||||
summary();
|
||||
go('setup');
|
||||
});
|
||||
@@ -115,25 +119,28 @@ function buildSub() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
for (const t of MENU) {
|
||||
const b = banner(t.mode ? modeOf(t.mode) : t, 'mode', topOf(pickedMode) === t);
|
||||
b.addEventListener('click', () => {
|
||||
// a group remembers which child you were on; entering it fresh lands on its first card
|
||||
pickMode(t.mode || (t.kids.includes(pickedMode) ? pickedMode : t.kids[0]));
|
||||
for (const el of modeList.children) el.classList.toggle('on', el === b);
|
||||
buildSub();
|
||||
summary();
|
||||
go(t.diffs || t.kids ? 'modesub' : 'setup');
|
||||
});
|
||||
modeList.appendChild(b);
|
||||
// the top posters are rebuilt on every visit so the LAST tag follows what was actually played
|
||||
function buildTops() {
|
||||
modeList.innerHTML = '';
|
||||
for (const t of MENU) {
|
||||
const b = banner(t.mode ? modeOf(t.mode) : t, 'mode', topOf(pickedMode) === t);
|
||||
b.addEventListener('click', () => {
|
||||
// a group remembers which child you were on; entering it fresh lands on its first card
|
||||
pickMode(t.mode || (t.kids.includes(pickedMode) ? pickedMode : t.kids[0]));
|
||||
buildSub();
|
||||
summary();
|
||||
go(t.diffs || t.kids ? 'modesub' : 'setup');
|
||||
});
|
||||
modeList.appendChild(b);
|
||||
}
|
||||
}
|
||||
buildTops();
|
||||
buildSub();
|
||||
|
||||
// --- tour tiles, built from the table. Same "tap selects, SERVE UP starts" rule as the court rows —
|
||||
@@ -238,7 +245,9 @@ 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('mode'));
|
||||
// entering a question screen rebuilds it, so the LAST tag always names the run actually played
|
||||
const goMode = () => { buildTops(); go('mode'); };
|
||||
document.getElementById('mStart').addEventListener('click', goMode);
|
||||
document.getElementById('mStats').addEventListener('click', openStats);
|
||||
document.getElementById('mRanks').addEventListener('click', () => { go('ranks'); stats.drawRanks(); });
|
||||
|
||||
@@ -291,10 +300,11 @@ 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('subBack').addEventListener('click', goMode);
|
||||
document.getElementById('sBack').addEventListener('click', () => {
|
||||
const t = topOf(pickedMode);
|
||||
go(t.diffs || t.kids ? 'modesub' : 'mode');
|
||||
if (t.diffs || t.kids) { buildSub(); go('modesub'); }
|
||||
else goMode();
|
||||
});
|
||||
document.getElementById('bsBack').addEventListener('click', () => go('secret'));
|
||||
|
||||
@@ -423,7 +433,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('mode');
|
||||
if (e.key === 'Enter' || e.key === ' ') goMode();
|
||||
else if (e.key === 's' || e.key === 'S') openStats();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user