Fly the modals in with a swoosh instead of cutting to them

m.in (0..1 over TUNE.modalIn, 0.34 s) is the entrance clock, ticked inside
update()'s modal early-return — the one thing that animates while the world
is frozen. Icon and title fly in from the right trailing two ghost copies as
a speed streak, the body lines cascade in from the left one after another,
everything on easeOutBack so it overshoots and settles rather than
decelerating politely into place. The dim washes in over the first third. A
descending noise sweep on arrival (swish is parameterised now — same six
lines of Web Audio, opposite reading) and a low pock as it lands.

Every x is rounded: a fractional x on a nearest-neighbour scaled image
re-snaps its pixel columns, which is the boiling that once read as HUD
flicker.

Dismissal is gated on the entrance: while m.in < 1 dismissModal swallows the
gesture but doesn't clear the modal, so a tap already in the air when a popup
appears can't skip it — and swallowing rather than returning false is what
keeps that tap from falling through into a serve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-30 11:23:30 +02:00
1 parent 79fb43f60d
commit 4cf54603f3
6 files changed
+69 -15

No files matched your search

+10 -6
View File
@@ -114,18 +114,22 @@ function noiseBuf(ac, len) {
return b;
}
export function swish(vol = 0.3) { // racket cutting air — the sound of a whiff
// Filtered noise sweep. Default = the racket cutting air (rising, short) — the sound of a whiff.
// The sweep is parameterised because a DESCENDING, longer one is a panel swooshing in and settling:
// same six lines of Web Audio, opposite reading. See whoosh() below.
export function swish(vol = 0.3, f0 = 1600, f1 = 3200, dur = 0.16) {
try {
const ac = audio(), t = ac.currentTime;
const src = ac.createBufferSource(); src.buffer = noiseBuf(ac, 0.16);
const bp = ac.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.setValueAtTime(1600, t);
bp.frequency.exponentialRampToValueAtTime(3200, t + 0.12); bp.Q.value = 1.5;
const src = ac.createBufferSource(); src.buffer = noiseBuf(ac, dur);
const bp = ac.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.setValueAtTime(f0, t);
bp.frequency.exponentialRampToValueAtTime(f1, t + dur * 0.75); bp.Q.value = 1.5;
const g = ac.createGain();
g.gain.setValueAtTime(0.001, t); g.gain.exponentialRampToValueAtTime(vol, t + 0.05);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.16);
g.gain.setValueAtTime(0.001, t); g.gain.exponentialRampToValueAtTime(vol, t + dur * 0.3);
g.gain.exponentialRampToValueAtTime(0.001, t + dur);
src.connect(bp).connect(g).connect(ac.destination); src.start();
} catch (e) {}
}
export const whoosh = (vol = 0.34) => swish(vol, 4200, 700, 0.3); // a panel flying in and settling
export function pock(vol = 0.8, freq = 900) { // racket contact / bounce thock
try {