ADR-0032 — The hero's system comes alive
The home page's system gains every orbit, a breathing star, planet trails, a lean toward the pointer, a comet and a telemetry line.
- Status: Approved
- Date: 2026-11-01
- Deciders: @puncoz
- Approved by: @puncoz
- Approved on: 2026-11-01
- Supersedes: ADR-0017 decision 2 (in
part: what the hero's system draws), and the One Orbit rule's two-exception list in
DESIGN.md(ADR-0030 decision 3), which gains a third - Superseded by: —
- Reworded by: ADR-0038 (earlier names only)
- Design documents:
../app-flow/living-hero.mdx,../prd/home-page.mdx,../architecture/system.mdx,DESIGN.md
Context
- The home page's hero is the system (ADR-0017 decision 2): a canvas drawn by
src/components/home/orbit-map.tsx. It has the star, an orbit for each ring that has entries (presentRingsinsrc/lib/rings.ts), the planets with their moons, a tooltip on pointing, and the zoom to the Verse (ADR-0029). - Today it draws one star, one orbit and one ringed planet. Only the site's own project is
documented, so
presentRingsreturns the Projects ring alone. The five default orbits appear only when the catalogue is empty. On a 1412px screen the system takes the right half of the hero and is mostly empty canvas. The owner finds it dull. - What stands in the way:
- The Mark Means rule (DESIGN.md): a ring means project, a full moon the latest release, a
crescent an older one, a dashed new moon
dev, a lock private. None is used for decoration, and the legend teaches "dashed = unreleased". - The One Orbit rule (DESIGN.md): the system is the site's only continuous motion. Its exceptions are a diagram's Archify trace (ADR-0022) and the Horizon's sky (ADR-0030).
- The still sky (ADR-0029 decision 3): the page's sky never moves.
- The Places, Not Controls rule (ADR-0029): a space word always stands beside a plain sentence.
- No invented planets. The system shows only documented entries; the invented private names appear only in the Verse view and beyond the belt (ADR-0016, ADR-0017 decision 4).
- The Mark Means rule (DESIGN.md): a ring means project, a full moon the latest release, a
crescent an older one, a dashed new moon
- The owner chose, with options, all eight of: every ecosystem's orbit, a living star, planet trails, pointer depth, an arrival spin-up, a telemetry line, a comet now and then, and a nebula behind the system.
Decision
The hero's system gains eight things. Everything stays on the existing canvas, or in one still layer behind it, and uses only the existing tokens. No dependency is added.
- Every ecosystem's orbit, waiting.
- The system always draws the npm, Composer, PyPI, Go and Projects orbits, the five the subline names. It also draws Cargo and Other when an entry is in them, in the existing ring order.
- An orbit with planets is drawn as now.
- An orbit with none is a thinner, fainter solid line: 0.75px, Line 2 mixed with 35% of its ecosystem colour, at 60% opacity. It is not dashed, because a dashed mark means unreleased.
- Its name (npm, Composer, PyPI, Go, Projects) sits on the orbit's upper left in 11px Data, Steel Faint, and turns with nothing.
- Pointing within 8px of an empty orbit shows the existing tooltip: its name, "No planets yet". It has nothing to open.
- With planets in only some orbits, the documented planets take their own orbits' radii, so the system has the same shape whatever is documented.
- A living star.
- The disc grows from 9px to 14px, with the maker's name under it as now.
- Its glow becomes a corona of three soft layers that breathe together over about 6 seconds (±15% opacity).
- Eight thin rays in the star colour at 25% turn once every 120 seconds.
- This is part of the system's own motion.
- Planet trails. Each planet leaves a trail behind it along its orbit, a sixth of the way round: 2px wide, fading from 35% of its colour to nothing (Frost Ink for projects). Moons leave none. A trail is drawn from the planet's own position each frame and adds no motion of its own.
- Pointer depth.
- On a device with a fine pointer, the system leans toward the pointer as it moves over the hero: the orbits' tilt moves from 0.36 by up to ±0.05, and the centre by up to 8px.
- The nebula shifts up to 12px the other way. Both ease over about 0.6s, and settle back when the pointer leaves.
- The page's still sky does not move (ADR-0029 decision 3).
- Off for touch and under reduced motion. A response to input, so the One Orbit rule holds.
- Arrival: the system spins up.
- On the first view of
/in a session, the star ignites: its disc grows from 40% and its corona from nothing, over 0.6s. - Each planet starts a third of a turn behind its place and sweeps in along its orbit over 1.2s with the site's ease; the orbits fade in over 0.4s. Then the system turns as usual.
- Not again in the same session (remembered in
sessionStorage; if storage is unavailable, it runs each time). Not when/#verseis opened, where the zoom out runs instead, and not under reduced motion. - A single response to arrival.
- On the first view of
- A telemetry line.
- The caption beside Zoom out to the Verse, empty in the system view today, reads in 12.5px
Data, Steel Faint, for example:
1 planet · 4 orbits waiting · 0 moons. - Its counts come from the catalogue: the public entries; the drawn orbits with no planet;
the version lines,
devincluded. Singular and plural follow the counts. - The plain sentence stands with it for screen readers, in place of the data line: "1 project documented; npm, Composer, PyPI and Go have none yet; no versions released." On screen the legend under the search button names the marks.
- In the Verse view the caption reads, as now, The Verse · one system so far.
- The caption beside Zoom out to the Verse, empty in the system view today, reads in 12.5px
Data, Steel Faint, for example:
- A comet now and then: the One Orbit rule's third exception.
- Every 20 to 30 seconds, on a seeded schedule, a comet crosses the upper part of the system: a 2px Frost Ink head with a 90px tail that fades. The tail always points away from the star, the way a comet's does.
- It is in view for about 2.5 seconds.
- It never crosses the text column. On desktop it stays right of 52% of the width; on phones it stays inside the system's 310px.
- It runs only while the hero is on screen, like the system. It doesn't run in the Verse view or under reduced motion.
- It is not a mark, and it carries no meaning.
- A nebula behind the system.
- A still layer behind the canvas: two soft radial washes centred on the star, Teal at 8% and the star colour at 6% in dark, half that in light. They reach 1.4 times the outer orbit.
- It is masked away from the text column (left of 50% on desktop; outside the system's 310px on phones), so nothing glows behind text.
- It moves only with pointer depth, and shrinks with the system in the Verse view.
- Restraint.
- Everything new is quieter than the planets. The only new labels are the empty orbits' names.
- The new continuous motion is the star's breathing and rays, inside the system, and the rare comet.
- Under reduced motion the system is drawn once, as now, with the corona, the trails, the empty orbits and the nebula still. Without scripts there is no canvas and nothing changes.
- Unchanged: the headline, subline, search button, legend, tooltip on planets, clicking a planet, the Verse view and its words, the morph, the still sky, every URL and every Markdown surface.
Options considered
| Question | Option | Pros | Cons | Verdict |
|---|---|---|---|---|
| Objects | Every ecosystem's orbit | Structure today; honest about scope; fills naturally | Five orbits with one planet still reads as early days | Chosen (owner) |
| Objects | A living star | The centre is the maker; costs a few draws | One more motion, inside the system | Chosen (owner) |
| Objects | Planet trails | The turning reads at a glance | Busier with many planets | Chosen (owner) |
| Objects | Pointer depth | Responsive and alive on desktop; no loop | Desktop only | Chosen (owner) |
| Around it | Arrival spin-up | A first-screen moment, once | 1.2s before the system settles | Chosen (owner) |
| Around it | A telemetry line | Honest numbers in the site's technical voice | One more line in the hero | Chosen (owner) |
| Around it | A comet now and then | The most alive | A third loop exception; it means nothing | Chosen (owner) |
| Around it | A nebula | Depth behind the system | Decorative; near the glow cliché | Chosen (owner), kept faint and masked from text |
| Empty orbits | Dashed, as first offered | Reads as "waiting" | The legend says dashed = unreleased: breaks Mark Means | Rejected for a fainter solid line |
| Telemetry | Plain words only ("1 project · 0 packages") | No space words | Less of the system's voice | Rejected; the plain sentence is the screen-reader text |
| Objects | A moon or ring as decoration | Familiar | Breaks Mark Means | Not offered |
Consequences
Positive
- The first screen shows the whole shape of the site today, and grows into it as packages arrive.
- The system has a living centre, visible motion and depth, without a second always-on loop except the rare comet.
Negative / accepted trade-offs
- A third exception to the One Orbit rule: a comet with no meaning crosses the hero every 20 to 30 seconds.
- The canvas code grows (
orbit-map.tsxis already 443 lines); the new geometry moves to a library file so it can be tested. - On first visit the system takes 1.2s to settle; the text and search button are usable at once.
- The nebula is decoration; it is kept faint and away from text.
Content invariants
None is touched. No content is added; the telemetry counts come from the public catalogue.
Per-page Markdown, llms.txt and llms-full.txt are unchanged (§6.5).
Security considerations
None. This change touches no data, no endpoint and no secret. It reads only the public
catalogue the page already has. sessionStorage holds one flag, "the hero has spun up", with
no personal data.
Implementation notes
-
src/lib/hero-sky.ts, plain functions so they can be tested:heroRings(entries): the five orbits always, plus Cargo and Other when present, in ring order;systemFacts(entries): the telemetry counts, the data line and the plain sentence; the sentence ends "no versions documented yet" rather than decision 6's "no versions released", because adevline is a moon the count includes but is not a release;cometStarts(seed, n): when each comet starts, 20 to 30 seconds apart (named for what it returns, in place ofcometSchedule);cometPath(...): its path inside the system region, never left of the text boundary nor above atopline, its tail pointing away from the star. The canvas passestop: 72, so a comet stays below the site header, which the hero's canvas runs under;trailArc(...): a planet's trail as an arc behind it;spinUp(t): the arrival's eased offset and the star's, corona's and orbits' light;- also
corona(t),lean(nx, ny),orbitDistance(...)for the empty orbits' tooltip, andmixHex(...)for their colour.
-
src/lib/rings.ts: the hero usesheroRings.presentRingsandemptyRingswere used only by the hero, so they are removed;ringOrderis exported forheroRings. -
src/components/home/orbit-map.tsx: empty orbits and their labels and tooltip, the star's corona and rays, trails, pointer depth, the arrival, the comet, all in the existing draw loop and its pause, reduced-motion and hidden-tab handling; the caption shows the telemetry line. With five orbits the system's size is capped on desktop so it stays clear of the text, and a planet's label flips to its left where it would leave the canvas. -
src/app/(home)/home.css: the nebula layer, its mask and its pointer shift; the caption. - Tests (ADR-0007, ADR-0021) for
hero-sky.ts(src/lib/hero-sky.test.ts):- the five orbits always, with extra rings only when present;
- the counts, plurals and plain sentence, including an empty catalogue;
- comets 20 to 30 seconds apart, always right of the text boundary and below the header, tails away from the star;
- trails behind the planet and a sixth of a turn long;
- the spin-up ending exactly at zero offset.
- The canvas is a client component and is not unit-tested (ADR-0021). Checked in Chrome
against
bunx next buildand the development server, at about 1412px and 390px, in both themes: the empty orbits and tooltip, the star, trails, pointer depth, the arrival once per session, the comet (sampled from the canvas: nothing drawn under the header), the nebula's mask, the caption, the Verse view; reduced motion in the code. -
bun run test,bun run types:checkandbunx next buildpass;bun run lintis reported while it stays blocked.
Docs to update on implementation
-
../app-flow/living-hero.mdx: Current; the home page and universe theme app flows where they describe the system and its empty state. -
../prd/home-page.mdx: the hero's requirements. -
DESIGN.mdand.impeccable/design.json: the system (hero canvas), Motion, the One Orbit rule's third exception. -
AGENTS.md§1, §5 and §10, and../architecture/system.mdx. - ADR-0017's "Superseded by" line, and
./index.mdx: index row and status.
ADR-0031 — The sidebar sets without dimming
As the Horizon enters, the sidebar and the trajectory set by dropping into its dusk edge and stay at full opacity.
ADR-0033 — The footer says you've reached the horizon
The footer's closing line becomes "You've reached the horizon. The Verse goes on.", and The Verse links to the Verse view.