From 3466a3f46ff6c9213d32344477e5f71eeb759a43 Mon Sep 17 00:00:00 2001 From: Ashton Honnecke Date: Thu, 6 Aug 2026 13:49:47 -0600 Subject: [PATCH] highway: make the missed-note gem visible + provider-color-driven MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The note-state provider (feedBack#254) lets a scorer flag a note 'miss', but `_paintGemGlow` rendered every miss as a single hardcoded-red circle at globalAlpha 0.4·alpha over the gem. Two problems: 1. It ignored the provider's `color` (which hit/active already honor), so a scorer can't pick a non-colliding hue — and the hardcoded red sits right on top of the E string's own color (#ff3c3c), making a missed E unreadable. 2. At 0.4α over a same-size circle, with the provider's short transient alpha, a miss is effectively invisible. Now the miss glow honors `ns.color` (parsed for the radial-glow stops, fallback purple — deliberately away from the red E string), and renders a radial glow spilling to r·2.1 plus a crisp ring at r·1.2 so a missed note reads at a glance. Center tint stays light (0.32α) so the note colour + fret number (drawn on top) remain readable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- static/js/highway-state-primitives.js | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/static/js/highway-state-primitives.js b/static/js/highway-state-primitives.js index 3ddc3412..00c1e342 100644 --- a/static/js/highway-state-primitives.js +++ b/static/js/highway-state-primitives.js @@ -92,11 +92,35 @@ export function _paintGemGlow(hwState, cx, cy, r, stringIdx, ns) { if (!ns || !hwState.ctx) return; hwState.ctx.save(); if (ns.state === 'miss') { - hwState.ctx.globalAlpha = 0.4 * ns.alpha; - hwState.ctx.fillStyle = '#ff2828'; + // Bold, unmissable red so a missed note reads at a glance the whole way + // down (persistent misses arrive via the note-state provider with + // alpha 1). A radial glow spilling well past the gem + a crisp ring, + // with only a light tint at the center so the note colour + fret number + // (drawn on top by the caller) stay readable. + const a = ns.alpha; + // Color is provider-driven (default purple — deliberately NOT red, which + // collides with the E string). Parse "#rrggbb" → rgb so the radial glow + // can vary alpha per stop. + const raw = String(ns.color || '#c04bff'); + const valid = /^#?[0-9a-fA-F]{6}$/.test(raw); + const hexColor = valid ? (raw[0] === '#' ? raw : '#' + raw) : '#c04bff'; + const h = hexColor.slice(1); + const rgb = `${parseInt(h.slice(0, 2), 16)},${parseInt(h.slice(2, 4), 16)},${parseInt(h.slice(4, 6), 16)}`; + const glow = hwState.ctx.createRadialGradient(cx, cy, r * 0.2, cx, cy, r * 2.1); + glow.addColorStop(0, `rgba(${rgb},${0.32 * a})`); + glow.addColorStop(0.55, `rgba(${rgb},${0.55 * a})`); + glow.addColorStop(1, `rgba(${rgb},0)`); + hwState.ctx.globalAlpha = 1; + hwState.ctx.fillStyle = glow; hwState.ctx.beginPath(); - hwState.ctx.arc(cx, cy, r * 1.05, 0, Math.PI * 2); + hwState.ctx.arc(cx, cy, r * 2.1, 0, Math.PI * 2); hwState.ctx.fill(); + hwState.ctx.globalAlpha = 0.9 * a; + hwState.ctx.strokeStyle = hexColor; + hwState.ctx.lineWidth = Math.max(1.5, r * 0.16); + hwState.ctx.beginPath(); + hwState.ctx.arc(cx, cy, r * 1.2, 0, Math.PI * 2); + hwState.ctx.stroke(); hwState.ctx.restore(); return; }