diff --git a/index.html b/index.html index a4eb040..4d92596 100644 --- a/index.html +++ b/index.html @@ -117,6 +117,6 @@

CHOOSE YOUR COURT

- + diff --git a/render.js b/render.js index 74dba56..4f1da2c 100644 --- a/render.js +++ b/render.js @@ -208,6 +208,14 @@ function quadXZ(x1, z1, x2, z2, color) { // filled court-plane rect (screen trap ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.lineTo(c.x, c.y); ctx.lineTo(d.x, d.y); ctx.fill(); } +// Rounded panel. ctx.roundRect is native everywhere this game runs (Safari 16.4+), and the square +// fallback only matters on a browser that could not render the rest of this anyway. +function panel(x, y, w, h, r, fill, stroke) { + ctx.beginPath(); + if (ctx.roundRect) ctx.roundRect(x, y, w, h, r); else ctx.rect(x, y, w, h); + if (fill) { ctx.fillStyle = fill; ctx.fill(); } + if (stroke) { ctx.strokeStyle = stroke; ctx.stroke(); } +} 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(); @@ -601,8 +609,10 @@ function drawRack() { ctx.save(); ctx.lineWidth = 1; for (let i = 0; i < g.n; i++) { const x = g.x0 + i * g.tw, sel = G.weapon === i, dim = stock[i] <= 0 && i > 0; - ctx.fillStyle = sel ? '#123a24' : '#0a2418'; ctx.fillRect(x + 1, g.y0 + 1, g.tw - 2, g.h - 2); // sunk slot - ctx.strokeStyle = sel ? UI.pick : '#05160e'; ctx.strokeRect(x + 1.5, g.y0 + 1.5, g.tw - 3, g.h - 3); // gold frame when live + // rounded slots: five separate tiles read as five buttons, which is what they are + panel(x + 1.5, g.y0 + 1.5, g.tw - 3, g.h - 3, 4, + sel ? '#123a24' : '#0a2418', // sunk slot + sel ? UI.pick : '#05160e'); // gold frame when live // icon: left, vertically centred, padded off the edge const box = g.h - pad * 2, icx = x + pad + box / 2, icy = g.y0 + g.h / 2; ctx.globalAlpha = dim ? 0.35 : 1; @@ -1009,12 +1019,19 @@ export function render() { ctx.font = F(8); ctx.fillStyle = bd.cream; ctx.fillText(String(score.games[i]), 90, cy); // games ctx.font = F(8); ctx.fillStyle = svr ? bd.gold : bd.cream; ctx.fillText(ptTxt(i), 126, cy); // points, in the art's slot } - if (splat) { // which game you're in, permanently — the 1.1s GAME N flash was the only place it showed + if (splat) { + // This row used to be bare text on the court. That was fine over one dark green lawn and is + // unreadable over a sunlit clay crowd or a floodlit blue one, so it gets its own panel — the + // same trick the scoreboard art above it already relies on. + ctx.lineWidth = 1; + panel(0.5, bH + 1.5, bW - 1, 12, 3, 'rgba(8,26,18,0.82)', hexA(bd.hi, 0.35)); + // 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(6, 0); ctx.fillStyle = bd.dim; - ctx.fillText('GAME ' + (G.runGames + 1), bW, bH + 7); + ctx.fillText(`GAME ${G.runGames + 1} · ${G.sets[0]} SET${G.sets[0] === 1 ? '' : 'S'}`, bW - 3, bH + 7); } - if (splat && TUNE.killPips && TUNE.killsPerPoint > 1) { // kills-to-next-point pips, just under the board — no box - ctx.textAlign = 'left'; ctx.font = F(6, 0); ctx.fillStyle = bd.dim; ctx.fillText('NEXT', 2, bH + 7); + if (splat && TUNE.killPips && TUNE.killsPerPoint > 1) { // kills-to-next-point pips, on the panel above + ctx.textAlign = 'left'; ctx.font = F(6, 0); ctx.fillStyle = bd.dim; ctx.fillText('NEXT', 4, bH + 7); for (let k = 0; k < TUNE.killsPerPoint; k++) { ctx.beginPath(); ctx.arc(32 + k * 8, bH + 7, 2.4, 0, 7); if (k < G.killCount) { ctx.fillStyle = bd.gold; ctx.fill(); } @@ -1068,7 +1085,10 @@ export function render() { ctx.fillStyle = UI.warm; ctx.font = F(8); const touch = IS_TOUCH, switchHint = touch ? 'TAP THE RACK TO SWITCH' : '1-5 TO SWITCH'; const sel = ['PLAIN', 'BOMB', 'TRIPLE', 'FIRE', 'GUN'][G.weapon]; - const txt = G.mode === 'splatter' + // The DUEL is played under tennis rules — no slingshot, no rack, serve to the box (see the + // !G.interlude guards in solveShot and the aim reticle). It was still being told to pull back + // to aim, which is the one thing that does nothing there. + const txt = G.mode === 'splatter' && !G.interlude ? (G.weapon === 4 ? (touch ? 'TAP TO UNLOAD THE GUN' : 'SPACE UNLOADS THE GUN') : G.aim ? 'PULL BACK: FURTHER = HARDER' : G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}`