Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions website/src/components/HdiLogo.astro
Original file line number Diff line number Diff line change
Expand Up @@ -90,42 +90,68 @@
// so CSS transitions on color have existing elements to animate between.
function applyClasses() {
logoPre.querySelectorAll<HTMLElement>(".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;
hoveredKey = null;
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"]);
Expand Down Expand Up @@ -160,7 +186,8 @@
.hero-logo-letter {
color: var(--green);
transition: all 150ms linear;
&.hovered {
&.hovered,
&.pressed {
color: var(--mauve);
}
}
Expand Down