The HUD lifts off the crowd, and the frame is dark at both ends
A drop shadow under the scoreboard, the game/sets panel, the BLOOD panel and every rack tile — and the same numbers in CSS on the quit chip, because half the overlay lifting off the backdrop while the other half sits flat on it is worse than neither. Canvas shadow offsets are device pixels and are not scaled by the transform, so hudShadow multiplies by RES; without that a 3px shadow is three quarters of a logical pixel on a 4x phone. And the top of the frame now fades out of black the way the bottom already fades into it (TUNE.standTop) — Oliver: "the bottom of the screen falls off into a black tone.. please make the top come out of a black tone". The stand comes up out of the dark instead of being cut off at the top edge, which also gives the scoreboard something to sit on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0c9ea4f0d
commit
b34a9159a7
4 files changed
+57
-4
No files matched your search
@@ -305,6 +305,10 @@ export const TUNE = {
|
||||
// the night court) replaces it rather than adding to it — stacking the two is what made those two
|
||||
// unreadable: "it is too dim to see anything on the centre court and the dark court".
|
||||
standBack: 0.22,
|
||||
// ...and how dark the TOP of the frame is before the stand fades up out of it — the mirror of the
|
||||
// run-off apron that already takes the bottom of the screen down to black. Oliver: "the bottom of
|
||||
// the screen falls off into a black tone.. please make the top come out of a black tone."
|
||||
standTop: 0.78,
|
||||
// ---- damage. Bodies with hp (racket boss 'r', brute 'g') take damage in these units, so a big
|
||||
// boss is just a bigger hp number and every weapon stays proportional. Fractions are legal: they
|
||||
// bank on zb.chip and only whole points come off hp (the wound SKINS switch on hp thresholds).
|
||||
|
||||
+37
-2
@@ -509,6 +509,19 @@ function paintBackdrop(c, pal, pt) {
|
||||
// went wrong".
|
||||
const dim = standDim(pt);
|
||||
if (dim > 0) { c.fillStyle = `rgba(12,8,20,${dim})`; c.fillRect(0, 0, W, standsBottom); }
|
||||
// THE PICTURE COMES OUT OF THE DARK AT THE TOP, the way it falls into it at the bottom. Same
|
||||
// colour and the same idea as the run-off apron below the near baseline (further down this
|
||||
// function): the stand is the far edge of the frame, and a hard cut at y = 0 reads as a
|
||||
// photograph that has been cropped rather than as a stadium with a roof somewhere above it.
|
||||
// It also happens to be where the scoreboard sits, which is the second reason it earns its keep.
|
||||
if (TUNE.standTop > 0) {
|
||||
const deep = standsBottom * 0.42;
|
||||
const g = c.createLinearGradient(0, -8, 0, deep);
|
||||
g.addColorStop(0, `rgba(6,10,6,${TUNE.standTop})`);
|
||||
g.addColorStop(1, 'rgba(6,10,6,0)');
|
||||
c.fillStyle = g;
|
||||
c.fillRect(-8, -8, W + 16, deep + 8);
|
||||
}
|
||||
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
||||
const dw = COURT.dblW / 2;
|
||||
quadXZ(-14, -6, 14, 30, pal.out); // out area. NEVER let z approach
|
||||
@@ -617,6 +630,17 @@ function panel(x, y, w, h, r, fill, stroke) {
|
||||
if (fill) { ctx.fillStyle = fill; ctx.fill(); }
|
||||
if (stroke) { ctx.strokeStyle = stroke; ctx.stroke(); }
|
||||
}
|
||||
// A DROP SHADOW UNDER THE HUD. The board, the blood panel and the rack sit on a crowd now, and an
|
||||
// overlay lifts off a busy backdrop with a shadow — the same thing shadowText already does for loose
|
||||
// text, one pixel at a time. Canvas shadow offsets are NOT scaled by the transform (they are device
|
||||
// pixels), so they are multiplied by RES here, or a 2 px shadow reads as half a logical pixel on a
|
||||
// 4x phone. Call hudShadow(false) as soon as the element is drawn: the state is sticky and would
|
||||
// otherwise land under every glyph that follows.
|
||||
function hudShadow(on, off = 3, blur = 3, a = 0.68) {
|
||||
ctx.shadowColor = on ? `rgba(0,0,0,${a})` : 'transparent';
|
||||
ctx.shadowBlur = on ? blur * RES : 0;
|
||||
ctx.shadowOffsetX = ctx.shadowOffsetY = on ? off * RES : 0;
|
||||
}
|
||||
function drawShadow(px, py, rx, ry, alpha = 0.28) {
|
||||
ctx.fillStyle = `rgba(0,0,0,${alpha})`;
|
||||
ctx.beginPath(); ctx.ellipse(px, py, rx, Math.max(1, ry), 0, 0, 7); ctx.fill();
|
||||
@@ -1183,7 +1207,9 @@ function drawBlood() {
|
||||
// different coordinate systems and only the CSS one is fixed.
|
||||
const chip = Math.round(44 * W / (cv.clientWidth || innerWidth));
|
||||
const w = Math.max(46, pw + 10), h = 24, x = W - 6 - chip - w, y = 6;
|
||||
hudShadow(true);
|
||||
panel(x + 0.5, y + 0.5, w - 1, h - 1, 3, 'rgba(26,6,8,0.82)', 'rgba(163,74,66,0.45)');
|
||||
hudShadow(false);
|
||||
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.font = F(7, 0); ctx.fillStyle = hp <= 2 ? UI.bad : '#89a08f';
|
||||
ctx.fillText('BLOOD', x + w / 2, y + 7);
|
||||
@@ -1902,9 +1928,11 @@ function drawRack() {
|
||||
// The LIVE one has to read at a glance ("the active weapon should be more pronounced"):
|
||||
// a visibly brighter well and a 2px gold frame, not the same hairline every other tile gets.
|
||||
if (sel) ctx.lineWidth = 2;
|
||||
hudShadow(true, 2, 0, 0.5); // hard, no blur: this runs once per slot per frame
|
||||
panel(x + 1.5, y0 + 1.5, g.tw - 3, g.h - 3, 4,
|
||||
sel ? '#1c5230' : '#0a2418', // sunk slot — the live one sits visibly lit
|
||||
sel ? UI.pick : '#05160e'); // gold frame when live
|
||||
hudShadow(false);
|
||||
if (sel) ctx.lineWidth = 1;
|
||||
// THE BURST COUNTDOWN IS THE TILE ITSELF. It was a 1.5 px bar along the tile's bottom edge —
|
||||
// which is both the rounded frame AND the very bottom of a phone screen, so it was invisible
|
||||
@@ -2706,9 +2734,14 @@ export function render() {
|
||||
const bW = 150, bH = 42; // fixed: keeps the ornate PNG undistorted (art is 300x84 = 150x42)
|
||||
const plate = zBoard || SPR.scoreboard;
|
||||
if (benchOn) { /* an art test has no score — not even the empty plate */ }
|
||||
else if (plate) ctx.drawImage(plate, 0, 0, bW, bH); // generated ornate board backdrop
|
||||
else { // code-drawn fallback until the PNG loads
|
||||
else if (plate) {
|
||||
hudShadow(true);
|
||||
ctx.drawImage(plate, 0, 0, bW, bH); // generated ornate board backdrop
|
||||
hudShadow(false);
|
||||
} else { // code-drawn fallback until the PNG loads
|
||||
hudShadow(true);
|
||||
R(0, 0, bW, bH, bd.face);
|
||||
hudShadow(false);
|
||||
R(0, 0, bW, 1, bd.hi); R(0, 0, 1, bH, bd.hi);
|
||||
R(0, bH - 1, bW, 1, bd.lo); R(bW - 1, 0, 1, bH, bd.lo);
|
||||
R(1, 1, 5, bH - 2, bd.spine); R(1, 1, 5, 1, bd.spineHi); R(6, 1, 1, bH - 2, bd.lo);
|
||||
@@ -2747,7 +2780,9 @@ export function render() {
|
||||
// 6px was the smallest type in the game and it read as texture rather than as words. Both
|
||||
// strings are 7 and the panel grew to carry them — they are the only permanent readout of how
|
||||
// far into a run you are.
|
||||
hudShadow(true);
|
||||
panel(0.5, bH + 1.5, bW - 1, 14, 3, 'rgba(8,26,18,0.82)', hexA(bd.hi, 0.35));
|
||||
hudShadow(false);
|
||||
// which game you're in, and the sets banked, permanently — the 1.1s GAME N flash and the
|
||||
// scoreboard's own per-row sets column were the only places either showed.
|
||||
ctx.textAlign = 'right'; ctx.font = F(7, 0); ctx.fillStyle = bd.dim;
|
||||
|
||||
Reference in new issue
Block a user