Skip to content
Merged
Show file tree
Hide file tree
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
168 changes: 168 additions & 0 deletions website/src/components/HdiLogo.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<pre
class="hero-logo"
id="hero-logo-pre"
role="img"
aria-label="hdi">
_____ _____ _____
||<span class="hero-logo-letter" data-key="h">h</span> |||<span class="hero-logo-letter" data-key="d">d</span> |||<span class="hero-logo-letter" data-key="i">i</span> ||
||___|||___|||___||
|/___\|/___\|/___\|</pre>

<script>
// Animates the ASCII art logo so h, d, i keys visually depress when pressed
// (via keyboard or mouse click). Letters also highlight on hover.
//
// Each key is a 6-char-wide column rendered across 4 rows:
// row 0 — top cap: ' _____'
// row 1 — letter: '||h |'
// row 2 — bottom cap: '||___|'
// row 3 — shadow: '|/___\'
//
// The "pressed" state shifts the column down one row, replacing the top cap
// with spaces and dropping the shadow — giving the illusion of a key sinking.
//
// The static <pre> above is the no-JS fallback; render() rewrites it with
// interactive <span> elements once JS loads.

// prettier-ignore
const KEY_COLS: Record<string, { normal: string[]; pressed: string[] }> = {
// row0 row1 row2 row3
h: {
normal: [' _____', '||h |', '||___|', '|/___\\'],
pressed: [' ', ' _____', '||h |', '||___|' ],
},
d: {
// Leading '|' on pressed row1 closes h's right wall when h is up
normal: [' _____', '||d |', '||___|', '|/___\\'],
pressed: [' ', '|_____', '||d |', '||___|' ],
},
i: {
// Same as d — leading '|' closes d's right wall when d is up
normal: [' _____', '||i |', '||___|', '|/___\\'],
pressed: [' ', '|_____', '||i |', '||___|' ],
},
};

// Trailing character appended after the 3 key columns, one entry per row.
// Row 1's trailing '|' closes i's right wall — replaced with ' ' when i is pressed.
const ROW_SUFFIX = [" ", "|", "|", "|"];

// Maps each key to its left neighbour, used to detect adjacent pressed keys.
const KEY_LEFT: Record<string, string> = { d: "h", i: "d" };

const pressedKeys = new Set<string>();
let hoveredKey: string | null = null;
const logoPre = document.getElementById("hero-logo-pre")!;

function render() {
const keys = ["h", "d", "i"] as const;
const rows = [0, 1, 2, 3].map((row) => {
// When i is pressed its trailing wall disappears (key shifted down)
const suffix = row === 1 && pressedKeys.has("i") ? " " : ROW_SUFFIX[row];
return (
keys
.map((k) => {
let seg = (
pressedKeys.has(k) ? KEY_COLS[k].pressed : KEY_COLS[k].normal
)[row];
// On row 1, a pressed key's leading '|' closes the left neighbour's key wall.
// If the left neighbour is also pressed that wall is gone — drop the '|'.
if (
row === 1 &&
pressedKeys.has(k) &&
pressedKeys.has(KEY_LEFT[k])
) {
seg = " " + seg.slice(1);
}
// Position 2 of each segment is always the letter — wrap it in a span
return /[a-z]/.test(seg[2])
? `${seg.slice(0, 2)}<span class="hero-logo-letter" data-key="${k}">${seg[2]}</span>${seg.slice(3)}`
: seg;
})
.join("") + suffix
);
});
logoPre.innerHTML = rows.join("\n");
applyClasses();
}

// Apply hover class without touching the DOM structure,
// 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);
});
}

// Maps a mouse event 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 {
const rect = logoPre.getBoundingClientRect();
const col = Math.floor((e.clientX - rect.left) / (rect.width / 19));
return col < 6 ? "h" : col < 12 ? "d" : col < 18 ? "i" : null;
}

// Mouse
logoPre.addEventListener("mousemove", (e) => {
const key = keyFromMouse(e);
if (key === hoveredKey) return;
hoveredKey = key;
applyClasses(); // class-only update — preserves spans so transition fires
});
logoPre.addEventListener("mouseleave", () => {
if (hoveredKey === null) return;
hoveredKey = null;
applyClasses();
});
logoPre.addEventListener("mousedown", (e) => {
const key = keyFromMouse(e);
if (key) {
pressedKeys.add(key);
render();
}
});
document.addEventListener("mouseup", () => {
if (pressedKeys.size === 0) return;
pressedKeys.clear();
render();
});

// Keyboard — skip when focus is in a text input to avoid interfering with typing
const KEYS = new Set(["h", "d", "i"]);
const isTextField = (t: EventTarget | null) =>
t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement;
document.addEventListener("keydown", (e) => {
if (KEYS.has(e.key) && !isTextField(e.target)) {
pressedKeys.add(e.key);
render();
}
});
document.addEventListener("keyup", (e) => {
if (KEYS.has(e.key)) {
pressedKeys.delete(e.key);
render();
}
});

render();
</script>

<style>
.hero-logo {
font-size: 1.5rem;
line-height: initial;
cursor: default;
user-select: none;
@media (scripting: enabled) {
cursor: pointer;
}

.hero-logo-letter {
color: var(--green);
transition: all 150ms linear;
&.hovered {
color: var(--mauve);
}
}
}
</style>
22 changes: 3 additions & 19 deletions website/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ import Layout from "../layouts/Layout.astro";
import DemoLatte from "../assets/demo-latte.gif";
import DemoMocha from "../assets/demo-mocha.gif";
import { VERSION } from "../data/data";
import HdiLogo from "../components/HdiLogo.astro";
---

<Layout title="Extract install/run/test commands from project READMEs">
<main class="about stack" id="about">
<section class="hero stack">
<header class="hero-header stack">
<pre
class="hero-logo"
role="img"
aria-label="hdi">
_____ _____ _____
||<span class="hero-logo-letter ">h</span> |||<span class="hero-logo-letter ">d</span> |||<span class="hero-logo-letter ">i</span> ||
||___|||___|||___||
|/___\|/___\|/___\|
</pre>
<HdiLogo />
<a
href="https://github.com/grega/hdi"
class="gh-link"
Expand Down Expand Up @@ -228,16 +221,7 @@ $ hdi

.hero-header {
align-items: center;
--space: 0;
}

.hero-logo {
font-size: 1.5rem;
line-height: initial;

.hero-logo-letter {
color: var(--green);
}
--space: 1.5rem;
}

.hero h1 {
Expand Down