Skip to content

Commit debfec9

Browse files
authored
Merge pull request #25 from grega/logo-fun-mobile
Fix logo interaction on touch devices
2 parents 56068c0 + 4f2a169 commit debfec9

File tree

1 file changed

+42
-15
lines changed

1 file changed

+42
-15
lines changed

website/src/components/HdiLogo.astro

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,42 +90,68 @@
9090
// so CSS transitions on color have existing elements to animate between.
9191
function applyClasses() {
9292
logoPre.querySelectorAll<HTMLElement>(".hero-logo-letter").forEach((el) => {
93-
el.classList.toggle("hovered", el.dataset.key === hoveredKey);
93+
const key = el.dataset.key!;
94+
el.classList.toggle("hovered", key === hoveredKey);
95+
el.classList.toggle("pressed", pressedKeys.has(key));
9496
});
9597
}
9698

97-
// Maps a mouse event to a key by dividing the pre into 19 character columns.
99+
// Maps a clientX coordinate to a key by dividing the pre into 19 character columns.
98100
// h: cols 0–5, d: cols 6–11, i: cols 12–17, col 18 is the trailing '|' (ignored).
99-
function keyFromMouse(e: MouseEvent): string | null {
101+
function keyFromPoint(clientX: number): string | null {
100102
const rect = logoPre.getBoundingClientRect();
101-
const col = Math.floor((e.clientX - rect.left) / (rect.width / 19));
103+
const col = Math.floor((clientX - rect.left) / (rect.width / 19));
102104
return col < 6 ? "h" : col < 12 ? "d" : col < 18 ? "i" : null;
103105
}
104106

107+
function releaseAll() {
108+
if (pressedKeys.size === 0) return;
109+
pressedKeys.clear();
110+
render();
111+
}
112+
113+
function pressTouches(e: TouchEvent) {
114+
e.preventDefault();
115+
pressedKeys.clear();
116+
for (const touch of e.touches) {
117+
const key = keyFromPoint(touch.clientX);
118+
if (key) pressedKeys.add(key);
119+
}
120+
render();
121+
}
122+
105123
// Mouse
106124
logoPre.addEventListener("mousemove", (e) => {
107-
const key = keyFromMouse(e);
108-
if (key === hoveredKey) return;
109-
hoveredKey = key;
110-
applyClasses(); // class-only update — preserves spans so transition fires
125+
const key = keyFromPoint(e.clientX);
126+
if (key !== hoveredKey) {
127+
hoveredKey = key;
128+
applyClasses();
129+
}
130+
if (e.buttons === 1) {
131+
pressedKeys.clear();
132+
if (key) pressedKeys.add(key);
133+
render();
134+
}
111135
});
112136
logoPre.addEventListener("mouseleave", () => {
113137
if (hoveredKey === null) return;
114138
hoveredKey = null;
115139
applyClasses();
116140
});
117141
logoPre.addEventListener("mousedown", (e) => {
118-
const key = keyFromMouse(e);
142+
const key = keyFromPoint(e.clientX);
119143
if (key) {
120144
pressedKeys.add(key);
121145
render();
122146
}
123147
});
124-
document.addEventListener("mouseup", () => {
125-
if (pressedKeys.size === 0) return;
126-
pressedKeys.clear();
127-
render();
128-
});
148+
document.addEventListener("mouseup", releaseAll);
149+
150+
// Touch
151+
logoPre.addEventListener("touchstart", pressTouches, { passive: false });
152+
logoPre.addEventListener("touchmove", pressTouches, { passive: false });
153+
document.addEventListener("touchend", releaseAll);
154+
document.addEventListener("touchcancel", releaseAll);
129155

130156
// Keyboard — skip when focus is in a text input to avoid interfering with typing
131157
const KEYS = new Set(["h", "d", "i"]);
@@ -160,7 +186,8 @@
160186
.hero-logo-letter {
161187
color: var(--green);
162188
transition: all 150ms linear;
163-
&.hovered {
189+
&.hovered,
190+
&.pressed {
164191
color: var(--mauve);
165192
}
166193
}

0 commit comments

Comments
 (0)