Skip to content

Commit f29011b

Browse files
seanhancaseanhancaclaude
authored
site(math): Life in Glyph — gallery of four Claude × Glyph showcases (#72)
## Summary Adds **three new "Draw Me ___" pages** plus a **gallery index** that ties them together with the existing Orion page. Each new page follows the same prompt-as-product template: the English prompt → hero animation → three-act narrative → JSON spec + Glyph-rendered SVG → four "your turn" prompt templates. Together the four pages exercise four of Glyph's six data shapes, turning the series into a tour of the grammar: | Page | Glyph shape | Math | |------|-------------|------| | 🚀 [Orion](https://seanhanca.github.io/glyph/math/draw-me.html) | `function` (parametric) | Three-phase ternary x(t), y(t) | | ❤️ [Heartbeat](https://seanhanca.github.io/glyph/math/draw-me-heartbeat.html) | `trajectory` (ODE) | FitzHugh-Nagumo 1961 | | 🐆 [Leopard](https://seanhanca.github.io/glyph/math/draw-me-leopard.html) | `pde-solve` (RD) | Gray-Scott 1952/1993 | | 🌻 [Sunflower](https://seanhanca.github.io/glyph/math/draw-me-sunflower.html) | `recurrence` | Vogel 1979 phyllotaxis | ## New pages - `site/math/draw-me-heartbeat.html` — beating heart icon + ECG trace - `site/math/draw-me-leopard.html` — leopard silhouette + 30 spots emerging in stagger over 8 seconds - `site/math/draw-me-sunflower.html` — sunflower silhouette + 200 seeds blooming at the golden angle (generated in-page from the same formula the Glyph spec compiles) - `site/math/life-in-glyph.html` — gallery index with 2×2 grid of cards (each shows the prompt + thumbnail + grammar tag) plus a grammar-tour table ## New byte-locked fixtures Locked in CI across all 6 matrix cells: - `heartbeat.json` — `trajectory` ODE, 2000 samples - `leopard-spots.json` — `pde-solve` RD, 32×32 grid, 400 steps - `sunflower-seeds.json` — `recurrence`, 800 seeds at golden angle ## README New "Life in Glyph" section between "See it in action" and "How it works" with a 2×2 table of all four pages, thumbnails, prompt previews, and Glyph-shape tags. ## Cross-page nav - `joy.html` hero CTA now points at the gallery (was: just Orion) - `draw-me.html` CTA updated to link to the gallery + the three new pages - Each new page has a breadcrumb back to `life-in-glyph.html` and a bottom nav linking the three siblings ## Test plan - [x] 3 new fixture tests pass (snapshots locked) - [x] Full vitest pass: 662/662 (was 659) - [x] Lint clean (only the pre-existing mcp/server.test.ts warning) - [x] HTML/SVG tag balance verified for all 4 new pages - [x] Playground bundle already current (no schema change) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: seanhanca <infraservice@livepeer.org> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d0db860 commit f29011b

19 files changed

Lines changed: 1849 additions & 5 deletions

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,55 @@ Every image below is a real fixture in this repo, locked at byte-identity in CI.
163163
164164
---
165165

166+
## Life in Glyph
167+
168+
Four hand-crafted showcases of what one English prompt + an AI agent + Glyph produce together. Each page picks a subject anyone can recognize, walks the reader through **prompt → JSON spec → byte-locked SVG → animated page**, and ends with prompt templates you can adapt for your own ideas.
169+
170+
**Gallery:** [`seanhanca.github.io/glyph/math/life-in-glyph.html`](https://seanhanca.github.io/glyph/math/life-in-glyph.html)
171+
172+
<table>
173+
<tr>
174+
<td width="50%" valign="top">
175+
<a href="https://seanhanca.github.io/glyph/math/draw-me.html">
176+
<img alt="Orion's journey — Earth, Moon, spacecraft path" src="https://raw.githubusercontent.com/seanhanca/glyph/main/packages/core/__fixtures__/math/orion-journey.svg" width="100%">
177+
</a>
178+
<h4>🚀 <a href="https://seanhanca.github.io/glyph/math/draw-me.html">Orion's journey to the Moon</a></h4>
179+
<sub><code>data.shape: "function"</code> · NASA Artemis I, 25 days, three phases</sub><br><br>
180+
<sub><b>Prompt:</b> "Draw me NASA's Artemis I Orion spacecraft journey to the Moon and back. Show the outbound transit, a wide retrograde orbit, and the return arc. Beautiful for a 5-year-old, meaningful for a 70-year-old."</sub>
181+
</td>
182+
<td width="50%" valign="top">
183+
<a href="https://seanhanca.github.io/glyph/math/draw-me-heartbeat.html">
184+
<img alt="Heartbeat — FitzHugh-Nagumo trace" src="https://raw.githubusercontent.com/seanhanca/glyph/main/packages/core/__fixtures__/math/heartbeat.svg" width="100%">
185+
</a>
186+
<h4>❤️ <a href="https://seanhanca.github.io/glyph/math/draw-me-heartbeat.html">A heartbeat at rest</a></h4>
187+
<sub><code>data.shape: "trajectory"</code> · FitzHugh-Nagumo relaxation oscillator (1961)</sub><br><br>
188+
<sub><b>Prompt:</b> "Draw me a heartbeat — a resting human heart at 60 BPM. Use FitzHugh-Nagumo so the trace has the real spike-and-recover rhythm. Beautiful for a child, meaningful for a grandparent."</sub>
189+
</td>
190+
</tr>
191+
<tr>
192+
<td width="50%" valign="top">
193+
<a href="https://seanhanca.github.io/glyph/math/draw-me-leopard.html">
194+
<img alt="Leopard's spots — Gray-Scott reaction-diffusion" src="https://raw.githubusercontent.com/seanhanca/glyph/main/packages/core/__fixtures__/math/leopard-spots.svg" width="100%">
195+
</a>
196+
<h4>🐆 <a href="https://seanhanca.github.io/glyph/math/draw-me-leopard.html">How the leopard got his spots</a></h4>
197+
<sub><code>data.shape: "pde-solve"</code> · Gray-Scott reaction-diffusion · Turing 1952, Kipling 1902</sub><br><br>
198+
<sub><b>Prompt:</b> "Draw me how the leopard got his spots. Use Gray-Scott reaction-diffusion at F = k = 0.062. Start from a single seed, run 400 steps, let the pattern emerge."</sub>
199+
</td>
200+
<td width="50%" valign="top">
201+
<a href="https://seanhanca.github.io/glyph/math/draw-me-sunflower.html">
202+
<img alt="Sunflower seeds — golden-angle phyllotaxis" src="https://raw.githubusercontent.com/seanhanca/glyph/main/packages/core/__fixtures__/math/sunflower-seeds.svg" width="100%">
203+
</a>
204+
<h4>🌻 <a href="https://seanhanca.github.io/glyph/math/draw-me-sunflower.html">A sunflower's secret math</a></h4>
205+
<sub><code>data.shape: "recurrence"</code> · Vogel's golden-angle phyllotaxis (1979)</sub><br><br>
206+
<sub><b>Prompt:</b> "Draw me how a sunflower packs its seeds. Each seed n at radius √n, angle n × 137.5° (the golden angle). 200 seeds. Show why this angle and only this angle works."</sub>
207+
</td>
208+
</tr>
209+
</table>
210+
211+
> Each page ends with four prompt templates ("your turn — prompts to try") across different domains, so you can adapt the pattern to your own subject — a zebra's stripes, a pine cone's spirals, a different mission, a different oscillator.
212+
213+
---
214+
166215
## How it works
167216

168217
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"version": "glyph/0.1",
3+
"title": "Heartbeat — FitzHugh-Nagumo relaxation oscillator (life-in-glyph)",
4+
"data": {
5+
"trajectory": {
6+
"shape": "trajectory",
7+
"dxdt": "x - x*x*x/3 - y + 0.5",
8+
"dydt": "0.08*(x + 0.7 - 0.8*y)",
9+
"initial": { "x": -1.2, "y": -0.6 },
10+
"time": { "min": 0, "max": 200, "samples": 2000 }
11+
}
12+
},
13+
"layers": [
14+
{
15+
"mark": "line",
16+
"encoding": {
17+
"x": {
18+
"field": "t",
19+
"type": "quantitative",
20+
"scale": { "domain": [0, 200] }
21+
},
22+
"y": {
23+
"field": "x",
24+
"type": "quantitative",
25+
"scale": { "domain": [-2.5, 2.5] }
26+
}
27+
}
28+
}
29+
]
30+
}

packages/core/__fixtures__/math/heartbeat.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Glyph spec for a resting heartbeat — FitzHugh-Nagumo relaxation
3+
* oscillator (Richard FitzHugh, 1961). Powers the "Life in Glyph"
4+
* gallery's `draw-me-heartbeat.html` showcase.
5+
*
6+
* The 2D ODE:
7+
* dV/dt = V - V³/3 - W + I (membrane potential, fast variable)
8+
* dW/dt = ε(V + a - bW) (recovery variable, slow)
9+
*
10+
* with I=0.5, a=0.7, b=0.8, ε=0.08 — the classic FHN parameters
11+
* that produce the spike-and-recover pattern. We map V → trajectory.x
12+
* and W → trajectory.y. The plot shows V(t), the membrane potential,
13+
* which traces an ECG-like rhythm.
14+
*
15+
* Determinism gate: byte-identical SVG. RK4 + clampSamplerPrecision
16+
* keep the cross-platform libm drift below the SVG layer.
17+
*/
18+
import { readFileSync } from "node:fs";
19+
import { fileURLToPath } from "node:url";
20+
import { describe, expect, it } from "vitest";
21+
import { type CompileFieldInfo, compileSpec } from "../../src/compiler/compile.js";
22+
import { renderSvg } from "../../src/render/svg.js";
23+
import { parseSpec } from "../../src/spec/parse.js";
24+
25+
const fixtureUrl = new URL("./heartbeat.json", import.meta.url);
26+
const fixturePath = fileURLToPath(fixtureUrl);
27+
28+
const schema: CompileFieldInfo[] = [
29+
{ name: "x", type: "DOUBLE" },
30+
{ name: "y", type: "DOUBLE" },
31+
];
32+
33+
function buildAnchorRows(): ReadonlyArray<ReadonlyArray<number>> {
34+
return [
35+
[0, -2.5],
36+
[200, 2.5],
37+
];
38+
}
39+
40+
describe("math examples — heartbeat (life-in-glyph)", () => {
41+
it("renders the FitzHugh-Nagumo membrane-potential trace deterministically", async () => {
42+
const raw = JSON.parse(readFileSync(fixturePath, "utf8"));
43+
const spec = parseSpec(raw);
44+
const rows = buildAnchorRows();
45+
const svg = renderSvg(compileSpec({ spec, rows, schema }));
46+
const svg2 = renderSvg(compileSpec({ spec, rows, schema }));
47+
expect(svg2).toBe(svg);
48+
const pathCount = (svg.match(/<path /g) ?? []).length;
49+
expect(pathCount).toBeGreaterThanOrEqual(1);
50+
await expect(svg).toMatchFileSnapshot("./heartbeat.svg");
51+
});
52+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"version": "glyph/0.1",
3+
"title": "Leopard spots — Gray-Scott reaction-diffusion (life-in-glyph)",
4+
"data": {
5+
"pde_solve": {
6+
"shape": "pde-solve",
7+
"kind": "reaction-diffusion",
8+
"domain": { "x": [-1, 1], "y": [-1, 1] },
9+
"grid": { "rows": 32, "cols": 32 },
10+
"initial": "0",
11+
"initial_U": "1",
12+
"initial_V": "exp(-25*(x*x + y*y))*0.4",
13+
"params": { "Du": 1.0, "Dv": 0.5, "F": 0.062, "k": 0.062 },
14+
"boundary": "periodic",
15+
"steps": 400,
16+
"dt": 0.0008
17+
}
18+
},
19+
"layers": [
20+
{
21+
"mark": "heatmap",
22+
"encoding": {
23+
"x": {
24+
"field": "x",
25+
"type": "quantitative",
26+
"scale": { "domain": [-1, 1] }
27+
},
28+
"y": {
29+
"field": "y",
30+
"type": "quantitative",
31+
"scale": { "domain": [-1, 1] }
32+
},
33+
"color": {
34+
"field": "u",
35+
"type": "quantitative"
36+
}
37+
}
38+
}
39+
]
40+
}

packages/core/__fixtures__/math/leopard-spots.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Glyph spec for "How the Leopard Got His Spots" — Gray-Scott
3+
* reaction-diffusion (Alan Turing, *The Chemical Basis of
4+
* Morphogenesis*, 1952; Pearson, *Complex patterns in a simple
5+
* system*, 1993). Powers the "Life in Glyph" gallery's
6+
* `draw-me-leopard.html` showcase.
7+
*
8+
* The leopard-spots regime sits at F = k = 0.062 in the (F, k)
9+
* parameter space — small isolated nuclei grow and then divide,
10+
* locking into a hexagonal spot pattern. Compared with the v2
11+
* `rd-spots` fixture this uses a 32×32 grid (more spot detail),
12+
* 400 steps (well into the pattern-locked regime), and a slightly
13+
* tighter Gaussian seed.
14+
*
15+
* Determinism gate: byte-identical SVG. The per-cell precision
16+
* clamp in pde-solve keeps the libm boundary closed.
17+
*/
18+
import { readFileSync } from "node:fs";
19+
import { fileURLToPath } from "node:url";
20+
import { describe, expect, it } from "vitest";
21+
import { type CompileFieldInfo, compileSpec } from "../../src/compiler/compile.js";
22+
import { renderSvg } from "../../src/render/svg.js";
23+
import { parseSpec } from "../../src/spec/parse.js";
24+
25+
const fixtureUrl = new URL("./leopard-spots.json", import.meta.url);
26+
const fixturePath = fileURLToPath(fixtureUrl);
27+
28+
const schema: CompileFieldInfo[] = [
29+
{ name: "x", type: "DOUBLE" },
30+
{ name: "y", type: "DOUBLE" },
31+
];
32+
33+
function buildAnchorRows(): ReadonlyArray<ReadonlyArray<number>> {
34+
return [
35+
[-1, -1],
36+
[1, 1],
37+
];
38+
}
39+
40+
describe("math examples — leopard-spots (life-in-glyph)", () => {
41+
it("renders the Gray-Scott spot pattern deterministically", async () => {
42+
const raw = JSON.parse(readFileSync(fixturePath, "utf8"));
43+
const spec = parseSpec(raw);
44+
const rows = buildAnchorRows();
45+
const svg = renderSvg(compileSpec({ spec, rows, schema }));
46+
const svg2 = renderSvg(compileSpec({ spec, rows, schema }));
47+
expect(svg2).toBe(svg);
48+
const rectCount = (svg.match(/<rect /g) ?? []).length;
49+
// 32×32 grid = 1024 cells. Some may be deduped at the renderer
50+
// level if they share styles, but we expect close to 1024.
51+
expect(rectCount).toBeGreaterThanOrEqual(1024);
52+
expect(rectCount).toBeLessThanOrEqual(1100);
53+
await expect(svg).toMatchFileSnapshot("./leopard-spots.svg");
54+
});
55+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"version": "glyph/0.1",
3+
"title": "Sunflower seeds — golden-angle phyllotaxis (life-in-glyph)",
4+
"data": {
5+
"recurrence": {
6+
"shape": "recurrence",
7+
"state": ["x", "y"],
8+
"initial": { "x": 0, "y": 0 },
9+
"step": {
10+
"x": "sqrt(n + 1) * cos((n + 1) * 2.39996322972865332)",
11+
"y": "sqrt(n + 1) * sin((n + 1) * 2.39996322972865332)"
12+
},
13+
"steps": 800
14+
}
15+
},
16+
"layers": [
17+
{
18+
"mark": "point",
19+
"encoding": {
20+
"x": {
21+
"field": "x",
22+
"type": "quantitative",
23+
"scale": { "domain": [-32, 32] }
24+
},
25+
"y": {
26+
"field": "y",
27+
"type": "quantitative",
28+
"scale": { "domain": [-32, 32] }
29+
}
30+
}
31+
}
32+
]
33+
}

packages/core/__fixtures__/math/sunflower-seeds.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Glyph spec for sunflower phyllotaxis — Helmut Vogel's 1979 model
3+
* for the golden-angle seed packing observed in *Helianthus annuus*,
4+
* pine cones, pineapples, and aloe rosettes. Powers the "Life in
5+
* Glyph" gallery's `draw-me-sunflower.html` showcase.
6+
*
7+
* Each seed n is placed at:
8+
* r(n) = sqrt(n)
9+
* θ(n) = n · golden_angle where golden_angle = π · (3 − √5)
10+
* ≈ 137.50776° ≈ 2.39996 rad
11+
*
12+
* This is the angle that maximises packing density — any rational
13+
* multiple of π gives radial stripes; only the irrational golden
14+
* angle fills the disc evenly. Expressed as a Glyph `recurrence`
15+
* with `n` driving each step.
16+
*
17+
* Determinism gate: byte-identical SVG. cos/sin precision clamps
18+
* in the recurrence shape keep cross-platform libm drift below the
19+
* SVG layer.
20+
*/
21+
import { readFileSync } from "node:fs";
22+
import { fileURLToPath } from "node:url";
23+
import { describe, expect, it } from "vitest";
24+
import { type CompileFieldInfo, compileSpec } from "../../src/compiler/compile.js";
25+
import { renderSvg } from "../../src/render/svg.js";
26+
import { parseSpec } from "../../src/spec/parse.js";
27+
28+
const fixtureUrl = new URL("./sunflower-seeds.json", import.meta.url);
29+
const fixturePath = fileURLToPath(fixtureUrl);
30+
31+
const schema: CompileFieldInfo[] = [
32+
{ name: "x", type: "DOUBLE" },
33+
{ name: "y", type: "DOUBLE" },
34+
];
35+
36+
function buildAnchorRows(): ReadonlyArray<ReadonlyArray<number>> {
37+
return [
38+
[-32, -32],
39+
[32, 32],
40+
];
41+
}
42+
43+
describe("math examples — sunflower-seeds (life-in-glyph)", () => {
44+
it("renders the golden-angle phyllotaxis pattern deterministically", async () => {
45+
const raw = JSON.parse(readFileSync(fixturePath, "utf8"));
46+
const spec = parseSpec(raw);
47+
const rows = buildAnchorRows();
48+
const svg = renderSvg(compileSpec({ spec, rows, schema }));
49+
const svg2 = renderSvg(compileSpec({ spec, rows, schema }));
50+
expect(svg2).toBe(svg);
51+
// 800 steps + the initial (0,0) seed = 801 rows. The point mark
52+
// emits one <circle> per row; some at the origin may collapse
53+
// but the bulk should be unique.
54+
const circleCount = (svg.match(/<circle /g) ?? []).length;
55+
expect(circleCount).toBeGreaterThanOrEqual(700);
56+
await expect(svg).toMatchFileSnapshot("./sunflower-seeds.svg");
57+
});
58+
});

0 commit comments

Comments
 (0)