Every screen deals itself in, every time
The entrance is no longer a one-off on the boot: dealStage re-arms the class at the funnel every stage change already passes, so the title screen flies its plaque and buttons in whenever you arrive at it. The sub-menus join in — their header, content and footer arrive one after the other instead of the panel dropping in as one slab — and the poster wall deals its three rooms rather than its list, since a moving child inside a moving parent starts at twice the offset and reads as a stutter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aab83dacdc
commit
3324fe9cda
3 files changed
+67
-18
No files matched your search
+14
-4
@@ -596,10 +596,20 @@ is in the HTML now and main.js only ever takes it off. The keyboard-hint strip h
|
||||
(`body:has(#splash.busy) #hint`), so the loading screen really is court, title and bar.
|
||||
|
||||
**THE MENU DEALS ITSELF IN.** The canvas modals have always arrived on an easeOutBack overshoot;
|
||||
the splash now does the same once, on the boot: plaque, fixture, OTHER GAMES, the pair, the build
|
||||
line fly in from the left 70ms apart (`@keyframes dealIn`, hung off `#splash:not(.busy)` so the
|
||||
class removal IS the trigger). It fires on the boot only — coming back from a run must not make
|
||||
you wait for a flourish you have already seen — and `prefers-reduced-motion` turns it off.
|
||||
every menu screen does the same now — on EVERY arrival, not just the boot (Oliver, twice: "immer
|
||||
reinfliegen" and "auch für die untermenüs"). `@keyframes dealIn` flies an element in from the left
|
||||
on that overshoot, and `main.dealStage()` re-arms the `deal` class on #intro at the one funnel
|
||||
every stage change passes (remove, force a reflow, add — an animation whose class never left
|
||||
cannot restart, and CSS has no replay switch).
|
||||
- THE SPLASH deals its own five: plaque, fixture, OTHER GAMES, the pair, the build line, 70ms apart.
|
||||
- EVERY OTHER SCREEN deals its BANDS — header, content, footer — through
|
||||
`#intro.deal > .card > div:not(#splash) > *` with an nth-child stagger. The panel's old
|
||||
whole-slab `drop` retired: a slab says "a screen appeared", a deal says "the machine is laying
|
||||
this out for you".
|
||||
- THE POSTER WALL deals its three ROOMS instead of its list, and the list itself stops animating —
|
||||
a moving child inside a moving parent starts at twice the offset and reads as a stutter.
|
||||
- `prefers-reduced-motion` turns all of it off, and the splash holds its deal until the boot gate
|
||||
lifts (there is nothing to deal while the loading state is up).
|
||||
|
||||
**EVERY SCREEN OPENS AT ITS TOP.** `#intro` is the one scroller the menus share, so a scroll earned
|
||||
on the stats screen outlived it — go back and the splash came up scrolled, buttons off screen
|
||||
|
||||
+37
-13
@@ -196,8 +196,32 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#intro[data-stage="player"] #player,
|
||||
#intro[data-stage="promo"] #promo,
|
||||
#intro[data-stage="card"] #card,
|
||||
#intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1;
|
||||
animation: drop 0.3s ease-out; }
|
||||
#intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1; }
|
||||
/* ...and every screen DEALS ITS BANDS IN (2026-08-31, Oliver: "wäre natürlich auch toll für die
|
||||
untermenüs"). The panel used to drop in as one slab; now its header, its content and its
|
||||
footer arrive one after the other on the same overshoot the splash and the canvas modals use.
|
||||
The children of the stage, not the stage itself — a slab that drops says "a screen appeared",
|
||||
a deal says "the machine is laying this out for you". The splash keeps its own finer stagger
|
||||
below (its two bands would otherwise fly as two lumps), hence the :not. */
|
||||
#intro.deal > .card > div:not(#splash) > * {
|
||||
animation: dealIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(1) { animation-delay: 0.02s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(2) { animation-delay: 0.09s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(3) { animation-delay: 0.16s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(4) { animation-delay: 0.23s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(n+5) { animation-delay: 0.30s; }
|
||||
/* the poster wall deals its ROOMS, not one lump: the list is the whole screen there, so letting
|
||||
it arrive as a single band wastes the entrance. The list itself stops animating — a moving
|
||||
child inside a moving parent starts at twice the offset and reads as a stutter. */
|
||||
#intro.deal #stmode > #modeList { animation: none; }
|
||||
#intro.deal #modeList > .mgroup {
|
||||
animation: dealIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(1) { animation-delay: 0.09s; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(2) { animation-delay: 0.17s; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(3) { animation-delay: 0.25s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#intro.deal > .card > div:not(#splash) > *,
|
||||
#intro.deal #modeList > .mgroup { animation: none; } }
|
||||
/* the overlay is HIDDEN during play, not removed: a restart puts it straight back, and the
|
||||
display:grid above would otherwise win over the hidden attribute */
|
||||
#intro[hidden] { display: none; }
|
||||
@@ -522,26 +546,26 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
/* THE DEAL-IN (2026-08-31, Oliver: "es wäre toll, wenn sich die buttons nach dem laden aufbauen
|
||||
würden — arcade style"). The canvas modals have always arrived on an easeOutBack overshoot
|
||||
rather than decelerating politely into place; this is that same snap, in CSS, for the one
|
||||
moment the menu has an entrance to make. It fires ONCE, on the boot: the rule hangs off
|
||||
:not(.busy), so it triggers when main.js takes the loading state off and never again — coming
|
||||
back from a run must not make you wait for a flourish you have already seen. */
|
||||
moment the menu has an entrance to make. It plays on EVERY arrival at the title screen, not
|
||||
just the boot (Oliver) — main.dealSplash re-arms the class, because an animation whose class
|
||||
never left cannot restart itself. */
|
||||
@keyframes dealIn {
|
||||
from { opacity: 0; transform: translateX(-56px); }
|
||||
60% { opacity: 1; }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
#splash:not(.busy) #plaque,
|
||||
#splash:not(.busy) .foot.bare > * {
|
||||
#intro.deal #splash #plaque,
|
||||
#intro.deal #splash .foot.bare > * {
|
||||
animation: dealIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
|
||||
#splash:not(.busy) #plaque { animation-delay: 0.02s; }
|
||||
#splash:not(.busy) #mStart { animation-delay: 0.12s; }
|
||||
#splash:not(.busy) #mOther { animation-delay: 0.19s; }
|
||||
#intro.deal #splash #plaque { animation-delay: 0.02s; }
|
||||
#intro.deal #splash #mStart { animation-delay: 0.12s; }
|
||||
#intro.deal #splash #mOther { animation-delay: 0.19s; }
|
||||
/* the pair of secondary buttons is a CLASS, so it needs the same weight as the shorthand above
|
||||
to override its delay — the id selectors around it get there on their own */
|
||||
#splash:not(.busy) .foot.bare > .two { animation-delay: 0.26s; }
|
||||
#splash:not(.busy) #build { animation-delay: 0.33s; }
|
||||
#intro.deal #splash .foot.bare > .two { animation-delay: 0.26s; }
|
||||
#intro.deal #splash #build { animation-delay: 0.33s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#splash:not(.busy) #plaque, #splash:not(.busy) .foot.bare > * { animation: none; } }
|
||||
#intro.deal #splash #plaque, #intro.deal #splash .foot.bare > * { animation: none; } }
|
||||
/* the boot bar wears the stats screen's kill bar, doing duty as a loading bar. While #splash is
|
||||
.busy it stands in the buttons' place — same band, same height, so nothing jumps when it goes. */
|
||||
#boot { display: none; margin-bottom: 10px; }
|
||||
|
||||
+16
-1
@@ -93,8 +93,22 @@ const go = stage => {
|
||||
if (stage === 'train') buildTrain();
|
||||
intro.dataset.stage = stage;
|
||||
intro.scrollTop = 0;
|
||||
dealStage(stage);
|
||||
menuMusic();
|
||||
};
|
||||
// THE ARCADE ENTRANCE, re-armed. The title screen deals its plaque and buttons in from the left
|
||||
// every time you arrive at it — off a run, off the stats, off the boot. Removing the class and
|
||||
// forcing a reflow before adding it back is the whole trick: an animation whose class never left
|
||||
// cannot restart, and there is no "replay" switch in CSS.
|
||||
function dealStage(stage) {
|
||||
// the title screen waits for the boot: while the loading state is up there is nothing to deal
|
||||
// (the element is looked up here, not read off the const below: go() can fire before that line
|
||||
// has run, and a temporal-dead-zone throw in the menu funnel would take every screen with it)
|
||||
if (stage === 'splash' && document.getElementById('splash').classList.contains('busy')) return;
|
||||
intro.classList.remove('deal');
|
||||
void intro.offsetWidth;
|
||||
intro.classList.add('deal');
|
||||
}
|
||||
|
||||
// --- MODE selection in TWO STEPS (config.MENU + config.MODES): three posters on top, and the
|
||||
// picked one deals its own second step underneath — classic deals the difficulty chips, zombie
|
||||
@@ -1001,7 +1015,8 @@ const tick = setInterval(function watchLoad() {
|
||||
clearInterval(tick);
|
||||
// fonts.ready resolves once nothing is pending — including the three faces warmed above — so the
|
||||
// first canvas frame is never the system serif.
|
||||
(document.fonts ? document.fonts.ready : Promise.resolve()).then(() => splash.classList.remove('busy'));
|
||||
(document.fonts ? document.fonts.ready : Promise.resolve())
|
||||
.then(() => { splash.classList.remove('busy'); dealStage('splash'); });
|
||||
}, 100);
|
||||
|
||||
// THE FIRST TAP ANYWHERE ON THE OVERLAY STARTS THE THEME, and does nothing else — the screens have
|
||||
|
||||
Reference in new issue
Block a user