Site · Logo: the letter E in iso blocks, in the header, the favicon, the touch icon and the OG image

One geometry in src/iso/mark.ts, drawn by the engine the miniatures use. The favicon follows the system's mode; scripts/favicon.mjs takes its colors from styles.css. Below 768px the header shows the mark alone, its word kept as the link's name.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-04 22:19:04 -04:00
1 parent faf0987cf8
commit f80721abed
18 files changed
+158 -6

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
import { mark, points } from "./iso/mark";
const { width, height, polygons } = mark(0.12);
/** The E in blocks, in the page's iso colors. */
export function Mark() {
return (
<svg className="mark" viewBox={`0 0 ${width} ${height}`} aria-hidden="true">
{polygons.map((p, i) => (
<polygon key={i} className={p.accent ? `${p.face} accent` : p.face} points={points(p.points)} />
))}
</svg>
);
}
+3 -1
View File
@@ -2,6 +2,7 @@ import { Link, Outlet, useRouter } from "@tanstack/react-router";
import { useEffect } from "react";
import { REPO } from "./data";
import { Icon } from "./icons";
import { Mark } from "./Mark";
import { useStill } from "./iso/Iso";
import { smooth } from "./scroll";
import { setSound, useSound } from "./iso/marimba";
@@ -14,7 +15,8 @@ function Header() {
return (
<header className="bar">
<Link to="/" className="brand">
Ely
<Mark />
<span>Ely</span>
</Link>
<nav aria-label="Site">
<Link to="/components/" className="link" activeProps={{ "aria-current": "page" }}>
+24
View File
@@ -0,0 +1,24 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { mark, points } from "./mark.ts";
test("the mark draws three faces of each of its four blocks, the middle arm's in the accent", () => {
const { polygons } = mark(0);
assert.equal(polygons.length, 12);
assert.equal(polygons.filter((p) => p.accent).length, 3);
assert.deepEqual(polygons.slice(0, 3).map((p) => p.face), ["right", "front", "top"]);
});
test("the mark fills its box less the padding", () => {
const pad = 0.5;
const { width, height, polygons } = mark(pad);
const all = polygons.flatMap((p) => p.points);
const [xs, ys] = [all.map(([x]) => x), all.map(([, y]) => y)];
assert.ok(Math.abs(Math.min(...xs) - pad) < 1e-9 && Math.abs(Math.max(...xs) - (width - pad)) < 1e-9);
assert.ok(Math.abs(Math.min(...ys) - pad) < 1e-9 && Math.abs(Math.max(...ys) - (height - pad)) < 1e-9);
assert.ok(height > width, "the E stands taller than wide");
});
test("points print as an SVG list", () => {
assert.equal(points([[1, 2.5], [0.12345, 3]]), "1,2.5 0.123,3");
});
+27
View File
@@ -0,0 +1,27 @@
import { bounds, faces, painted, type Block, type Point } from "./engine.ts";
const b = (x: number, z: number, w: number, d: number, h: number, y = 0, accent = false): Block => ({ x, z, w, d, h, y, accent, beat: 0 });
/** The letter E in blocks, its middle arm in the accent. */
const BLOCKS = [b(0, 0, 1, 1, 5), b(1, 0, 2.4, 1, 1), b(1, 0, 1.8, 1, 1, 2, true), b(1, 0, 2.4, 1, 1, 4)];
export type Face = "top" | "front" | "right";
export interface Polygon {
points: Point[];
face: Face;
accent: boolean;
}
/** The mark's faces back to front, in a box `pad` larger than the mark each side. */
export function mark(pad: number): { width: number; height: number; polygons: Polygon[] } {
const [x0, y0, x1, y1] = bounds(BLOCKS);
const [ox, oy] = [pad - x0, pad - y0];
const polygons = painted(BLOCKS).flatMap((k) => {
const f = faces(k, k.h);
return (["right", "front", "top"] as const).map((face) => ({ points: f[face].map(([x, y]): Point => [x + ox, y + oy]), face, accent: !!k.accent }));
});
return { width: x1 - x0 + pad * 2, height: y1 - y0 + pad * 2, polygons };
}
export const points = (p: Point[]) => p.map(([x, y]) => `${+x.toFixed(3)},${+y.toFixed(3)}`).join(" ");
+7 -1
View File
@@ -63,7 +63,12 @@ button { font: inherit; color: inherit; background: none; border: 0; padding: 0;
/* Header */
.bar { position: fixed; inset: 0 0 auto; z-index: 40; height: 64px; display: flex; align-items: center; gap: 32px; padding: 0 32px; background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(18px); }
.brand { font-weight: 800; font-size: 20px; letter-spacing: -0.03em; }
.brand { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 20px; letter-spacing: -0.03em; }
.mark { display: block; height: 30px; width: auto; flex: none; stroke: var(--iso-line); stroke-width: 0.17; stroke-linejoin: round; }
.mark .top { fill: var(--bg); }
.mark .front { fill: var(--iso-side); }
.mark .right { fill: var(--iso-shade); }
.mark .accent { stroke: var(--accent); }
.bar nav { display: flex; gap: 24px; }
.link { font-size: 14px; font-weight: 400; color: var(--muted); transition: color 160ms ease-out; }
.link:hover, .link[aria-current="page"] { color: var(--fg); }
@@ -260,6 +265,7 @@ img.shot { display: block; max-width: 100%; height: auto; }
@media (max-width: 767px) {
.bar { padding: 0 16px; gap: 12px; }
.bar nav { gap: 16px; }
.brand span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.bar .end { gap: 0; }
.hero, .chapters, .missing, .showcase, .app-page { padding-left: 20px; padding-right: 20px; }
.showcase, .app-page { padding-top: 112px; }