From 7b15e1049486271ccf0dca95fbbc428c192c0226 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Thu, 30 Jul 2026 19:15:43 +0200 Subject: [PATCH] Carry the court colour into the page background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A phone's rounded display corners clip slivers out of the canvas, and the flat dark body showed through them as a dark notch where the bottom corners curve in. The page background is a two-stop gradient now: dark down to the top safe-area inset, where it sits behind the status bar, then the court's out-area colour for everything below. That colour is read live from the palette rather than hardcoded, because it lerps between the tennis and splatter courts — bright green to dark olive — so a fixed hex would only match in one mode. Written in resolved pixels rather than env() so the declaration can't be invalid and drop the background entirely, and only assigned when the string actually changes. Co-Authored-By: Claude Opus 5 --- HANDOFF.md | 8 ++++++++ index.html | 2 +- render.js | 23 ++++++++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) 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