diff --git a/HANDOFF.md b/HANDOFF.md index 8c9ff6d..004cb17 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -186,6 +186,14 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu because the wave grows with `hordeGrow` while the panic button was a flat 1 s; he chose scaling the melee over shrinking the horde. **Measure the crowd BEFORE calling `meleeSwipe`** — it kills everything in reach, so counting after counts the survivors and the cooldown barely moves. +- **The page background behind the canvas tracks the live court apron** (`pageBackground` in + render.js). It is visible in exactly two places: the strip above the canvas under the status bar, + and the slivers a phone's ROUNDED DISPLAY CORNERS clip out of the canvas — a flat dark body drew a + visible dark notch where the bottom corners curve in. It is a two-stop gradient: `#10141c` down to + the top inset, then `pal.out` for the rest. **`pal.out` must be read live**, because it lerps + between the tennis and splatter palettes and any fixed hex would only match in one mode. Written in + resolved px rather than `env()` so the declaration can never be invalid and drop the background, + and only assigned when the string actually changes. - **The safe area is handled ASYMMETRICALLY, on purpose.** TOP: index.html insets the canvas element below the status bar, because that is opaque UI the scoreboard must clear - `SAFE.top` stays 0, and adding a HUD offset there as well subtracts the notch twice. BOTTOM: the canvas deliberately runs to diff --git a/index.html b/index.html index 7978b89..9b47ef1 100644 --- a/index.html +++ b/index.html @@ -80,6 +80,6 @@

TAP TO START

- + diff --git a/render.js b/render.js index 87f45ba..bb488e3 100644 --- a/render.js +++ b/render.js @@ -51,11 +51,27 @@ export let W = 480, H = 270; // BOTTOM — the canvas deliberately runs to the PHYSICAL bottom edge so the court art fills the // screen (the home indicator is a slim bar, not opaque UI). Only the interactive RACK is // lifted clear of it, by SAFE.bottom below. -export const SAFE = { top: 0, bottom: 0 }; +export const SAFE = { top: 0, bottom: 0, topCss: 0 }; function measureSafe(scale) { const el = document.getElementById('safe'); if (!el) return; - SAFE.bottom = Math.round((parseFloat(getComputedStyle(el).paddingBottom) || 0) / scale); + const cs = getComputedStyle(el); + SAFE.bottom = Math.round((parseFloat(cs.paddingBottom) || 0) / scale); + SAFE.topCss = Math.round(parseFloat(cs.paddingTop) || 0); // CSS px — for the page background below +} + +// The page background behind the canvas. It shows in exactly two places: the strip above the canvas +// under the status bar, and the slivers a phone's ROUNDED DISPLAY CORNERS clip out of the canvas +// itself. A flat dark body therefore drew a visible dark notch where the bottom corners curve in. +// So: dark down to the top inset, then the LIVE out-area colour for the rest — live because `pal.out` +// lerps between the tennis and splatter palettes, so any fixed hex would only match in one mode. +// Written in resolved px (not env()) so the declaration can never be invalid and drop the background. +let bgLast = ''; +function pageBackground(out) { + const t = SAFE.topCss; + const css = `linear-gradient(#10141c 0px, #10141c ${t}px, ${out} ${t}px, ${out} 100%)`; + if (css === bgLast) return; // only touch the DOM when it actually changed + bgLast = css; document.body.style.background = css; } export function fitViewport() { // landscape 480x270; portrait 270 wide and as TALL as the phone is @@ -113,7 +129,7 @@ export function proj(x, y, z) { // ---------------------------------------------------------------- sprites ---- // assets/.png; game falls back to code-drawn figures until sprites load. -const SPRV = 47; // asset cache-buster — bump with main.js?v=N (see the img.src note below) +const SPRV = 48; // asset cache-buster — bump with main.js?v=N (see the img.src note below) const SPR = {}; for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_swing1','p_swing2', 'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2', @@ -595,6 +611,7 @@ export function render() { } const pal = palette(), pt = paletteT(); + pageBackground(pal.out); // keep the page behind the canvas matching the court's apron // stands: the generated crowd, drawn in columns so it can BOUNCE when it cheers. // sit the ad strip flush on the court's back edge (the out-area quad below reaches z=-6); // pinning to that projected line keeps the boards clear of the lawn in BOTH orientations