Undo the bad quad, and paint the page background where it shows
The triangle was mine. Extending the out-area quad to z=60 put it past camZ (48), and proj() divides by (camZ - z) — so the scale explodes at the camera plane and goes negative beyond it, folding the quad's far edge back above the screen. The quad goes back to z=30; the full-frame clear is what covers the screen, so it never needed extending in the first place. The bottom still not being green was a second bug, in the previous commit. The gradient was set on body, but the ROOT element's background is what paints the viewport, and index.html gives html a background too — so body's never propagated and painted only its own box, which has no height because the canvas is position:fixed. It goes on documentElement now, and html gets an explicit height, because a gradient is laid out in the root's own box and that box collapses for the same reason. Verified with the canvas deliberately ending 40px short: root box fills the viewport, gradient's lower stop is rgb(41,92,45), the canvas's bottom row is the same, and the seam is invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c9699da6f1
commit
571cd4c399
3 files changed
+25
-4
No files matched your search
@@ -193,6 +193,15 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
|||||||
perspective floor reaches the bottom on any aspect ratio. If you ever add a new orientation or
|
perspective floor reaches the bottom on any aspect ratio. If you ever add a new orientation or
|
||||||
stretch the canvas further, check the bottom rows are opaque — sample `getImageData` alpha, the
|
stretch the canvas further, check the bottom rows are opaque — sample `getImageData` alpha, the
|
||||||
colour alone will not tell you, because the page behind is deliberately the same green.
|
colour alone will not tell you, because the page behind is deliberately the same green.
|
||||||
|
**Do NOT try to cover the bottom by extending the out-area quad instead.** `proj()` divides by
|
||||||
|
`(camZ - z)`, so as z approaches `TUNE.camZ` (48) the scale explodes and past it goes NEGATIVE —
|
||||||
|
a quad taken to z=60 folded its far edge back above the screen as an inverted triangle. The clear
|
||||||
|
is what covers the screen; the quad only has to cover the court.
|
||||||
|
- **The page gradient goes on `document.documentElement`, and `html` needs a real height.** Two
|
||||||
|
separate CSS traps, both hit: the ROOT element's background is what paints the viewport, so setting
|
||||||
|
it on `body` while `html` also has a background painted nothing; and a GRADIENT is laid out in the
|
||||||
|
root's own box, which collapses to zero when the canvas is `position: fixed`, so `html { height:
|
||||||
|
100dvh }` is load-bearing rather than cosmetic.
|
||||||
- **The page background behind the canvas tracks the live court apron** (`pageBackground` in
|
- **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,
|
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
|
and the slivers a phone's ROUNDED DISPLAY CORNERS clip out of the canvas — a flat dark body drew a
|
||||||
|
|||||||
+6
-1
@@ -20,6 +20,10 @@
|
|||||||
src: url('assets/fonts/Silkscreen-Regular.ttf') format('truetype'); }
|
src: url('assets/fonts/Silkscreen-Regular.ttf') format('truetype'); }
|
||||||
@font-face { font-family: 'Silkscreen'; font-weight: 700; font-display: block;
|
@font-face { font-family: 'Silkscreen'; font-weight: 700; font-display: block;
|
||||||
src: url('assets/fonts/Silkscreen-Bold.ttf') format('truetype'); }
|
src: url('assets/fonts/Silkscreen-Bold.ttf') format('truetype'); }
|
||||||
|
/* html needs a real HEIGHT: the root element's background paints the viewport, but a GRADIENT is
|
||||||
|
laid out in the root's own box — and with the canvas position:fixed the root has no content and
|
||||||
|
collapses, so the gradient render.js sets had nowhere to go. */
|
||||||
|
html { height: 100%; height: 100dvh; }
|
||||||
html, body { margin: 0; padding: 0; background: #10141c; overflow: hidden; }
|
html, body { margin: 0; padding: 0; background: #10141c; overflow: hidden; }
|
||||||
/* invisible probe: env() is only readable from CSS, so park the insets on an element and let
|
/* invisible probe: env() is only readable from CSS, so park the insets on an element and let
|
||||||
render.js measure them. The canvas stays FULL-BLEED (the stands under the notch look good) —
|
render.js measure them. The canvas stays FULL-BLEED (the stands under the notch look good) —
|
||||||
@@ -40,6 +44,7 @@
|
|||||||
top: env(safe-area-inset-top, 0px);
|
top: env(safe-area-inset-top, 0px);
|
||||||
left: env(safe-area-inset-left, 0px);
|
left: env(safe-area-inset-left, 0px);
|
||||||
width: calc(100vw - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
|
width: calc(100vw - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
|
||||||
|
height: calc(100vh - env(safe-area-inset-top, 0px));
|
||||||
height: calc(100dvh - env(safe-area-inset-top, 0px)); }
|
height: calc(100dvh - env(safe-area-inset-top, 0px)); }
|
||||||
#hint { position: fixed; bottom: 8px; width: 100%; text-align: center;
|
#hint { position: fixed; bottom: 8px; width: 100%; text-align: center;
|
||||||
font: 400 10px Silkscreen, monospace; color: #6b7688; }
|
font: 400 10px Silkscreen, monospace; color: #6b7688; }
|
||||||
@@ -80,6 +85,6 @@
|
|||||||
<p class="start">TAP TO START</p>
|
<p class="start">TAP TO START</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="main.js?v=49"></script>
|
<script type="module" src="main.js?v=50"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -71,7 +71,11 @@ function pageBackground(out) {
|
|||||||
const t = SAFE.topCss;
|
const t = SAFE.topCss;
|
||||||
const css = `linear-gradient(#10141c 0px, #10141c ${t}px, ${out} ${t}px, ${out} 100%)`;
|
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
|
if (css === bgLast) return; // only touch the DOM when it actually changed
|
||||||
bgLast = css; document.body.style.background = css;
|
bgLast = css;
|
||||||
|
// documentElement, NOT body: the ROOT element's background is what paints the viewport canvas.
|
||||||
|
// index.html gives html a background, so body's never propagates — setting it there painted only
|
||||||
|
// body's own box, which has no height (the canvas is position: fixed) and so painted nothing.
|
||||||
|
document.documentElement.style.background = css;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fitViewport() { // landscape 480x270; portrait 270 wide and as TALL as the phone is
|
export function fitViewport() { // landscape 480x270; portrait 270 wide and as TALL as the phone is
|
||||||
@@ -129,7 +133,7 @@ export function proj(x, y, z) {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- sprites ----
|
// ---------------------------------------------------------------- sprites ----
|
||||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||||
const SPRV = 49; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
const SPRV = 50; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||||
const SPR = {};
|
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',
|
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',
|
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
|
||||||
@@ -662,7 +666,10 @@ export function render() {
|
|||||||
|
|
||||||
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
||||||
const dw = COURT.dblW / 2;
|
const dw = COURT.dblW / 2;
|
||||||
quadXZ(-14, -6, 14, 60, pal.out); // out area (past the bottom on any aspect)
|
quadXZ(-14, -6, 14, 30, pal.out); // out area. NEVER let z approach
|
||||||
|
// TUNE.camZ (48): proj() divides by (camZ - z), so at z >= camZ the scale blows up and then goes
|
||||||
|
// NEGATIVE, and the quad's far edge folds back above the screen as an inverted triangle. The
|
||||||
|
// full-frame clear above is what covers the bottom of the screen; this quad does not need to.
|
||||||
quadXZ(-dw - 0.15, -0.15, dw + 0.15, COURT.L + 0.15, '#e8e8dc'); // perimeter border line
|
quadXZ(-dw - 0.15, -0.15, dw + 0.15, COURT.L + 0.15, '#e8e8dc'); // perimeter border line
|
||||||
quadXZ(-dw, 0, dw, COURT.L, pal.lawn); // lawn (doubles court)
|
quadXZ(-dw, 0, dw, COURT.L, pal.lawn); // lawn (doubles court)
|
||||||
for (let z = 0; z < COURT.L; z += 2.97) quadXZ(-dw, z, dw, Math.min(z + 1.49, COURT.L), pal.stripe); // mow stripes
|
for (let z = 0; z < COURT.L; z += 2.97) quadXZ(-dw, z, dw, Math.min(z + 1.49, COURT.L), pal.stripe); // mow stripes
|
||||||
|
|||||||
Reference in new issue
Block a user