diff --git a/website/src/components/HdiLogo.astro b/website/src/components/HdiLogo.astro index 75adc14..14f4ee2 100644 --- a/website/src/components/HdiLogo.astro +++ b/website/src/components/HdiLogo.astro @@ -90,24 +90,48 @@ // so CSS transitions on color have existing elements to animate between. function applyClasses() { logoPre.querySelectorAll(".hero-logo-letter").forEach((el) => { - el.classList.toggle("hovered", el.dataset.key === hoveredKey); + const key = el.dataset.key!; + el.classList.toggle("hovered", key === hoveredKey); + el.classList.toggle("pressed", pressedKeys.has(key)); }); } - // Maps a mouse event to a key by dividing the pre into 19 character columns. + // Maps a clientX coordinate to a key by dividing the pre into 19 character columns. // h: cols 0–5, d: cols 6–11, i: cols 12–17, col 18 is the trailing '|' (ignored). - function keyFromMouse(e: MouseEvent): string | null { + function keyFromPoint(clientX: number): string | null { const rect = logoPre.getBoundingClientRect(); - const col = Math.floor((e.clientX - rect.left) / (rect.width / 19)); + const col = Math.floor((clientX - rect.left) / (rect.width / 19)); return col < 6 ? "h" : col < 12 ? "d" : col < 18 ? "i" : null; } + function releaseAll() { + if (pressedKeys.size === 0) return; + pressedKeys.clear(); + render(); + } + + function pressTouches(e: TouchEvent) { + e.preventDefault(); + pressedKeys.clear(); + for (const touch of e.touches) { + const key = keyFromPoint(touch.clientX); + if (key) pressedKeys.add(key); + } + render(); + } + // Mouse logoPre.addEventListener("mousemove", (e) => { - const key = keyFromMouse(e); - if (key === hoveredKey) return; - hoveredKey = key; - applyClasses(); // class-only update — preserves spans so transition fires + const key = keyFromPoint(e.clientX); + if (key !== hoveredKey) { + hoveredKey = key; + applyClasses(); + } + if (e.buttons === 1) { + pressedKeys.clear(); + if (key) pressedKeys.add(key); + render(); + } }); logoPre.addEventListener("mouseleave", () => { if (hoveredKey === null) return; @@ -115,17 +139,19 @@ applyClasses(); }); logoPre.addEventListener("mousedown", (e) => { - const key = keyFromMouse(e); + const key = keyFromPoint(e.clientX); if (key) { pressedKeys.add(key); render(); } }); - document.addEventListener("mouseup", () => { - if (pressedKeys.size === 0) return; - pressedKeys.clear(); - render(); - }); + document.addEventListener("mouseup", releaseAll); + + // Touch + logoPre.addEventListener("touchstart", pressTouches, { passive: false }); + logoPre.addEventListener("touchmove", pressTouches, { passive: false }); + document.addEventListener("touchend", releaseAll); + document.addEventListener("touchcancel", releaseAll); // Keyboard — skip when focus is in a text input to avoid interfering with typing const KEYS = new Set(["h", "d", "i"]); @@ -160,7 +186,8 @@ .hero-logo-letter { color: var(--green); transition: all 150ms linear; - &.hovered { + &.hovered, + &.pressed { color: var(--mauve); } }