The root layout handed every page the plan catalog (lib/rooms.ts configure with lib/data/plans.json), and /pricing, /calculator, /customers and the benchmark pages bundled the 202 KB price list. lib/api is now the one reader: plans() GET /v1/billing/plans, pricing() /v1/pricing, summary(), served() /v1/models, templates(), blueprints() /v1/blueprint/sbom, one promise a path a page load, each drawn as loading, ready or "api.hanzo.ai is unreachable" (components/api) and never an older number. plans.json, pricing.json, models.json, templates.json, blueprints.json and sync-pricing, sync-models, sync-templates, sync-blueprints are gone. What a static export must know before anyone asks, which model pages exist and their titles, is read by the build itself (lib/api/build, Next's fetch cache keyed by the build's stamp: one answer per build) and renders no rate. lib/moved reads the retired model pages against the export, and the 153 pages the old snapshot kept for models the gateway dropped are in retired.json. Kai's price beside Jev's, Enso's tier rates, a model page's rate and the GPU table read the price list in the browser; the site's plan math no longer imports @hanzo/rooms/lib/plans, whose bundled @hanzo/plans catalog rode the home, /pricing and the picker. Home first load 452.7 -> 437.4 KB gzip (budget 440); pages over perf.budget.json 32 -> 9, the rest over for settings code, connectors and the commerce catalog. The gates serve invented answers (e2e/gates/api) and block them; e2e/gates/read.spec.ts holds that no script carries a plan, price or model table.
Hanzo AI — investor deck
Thirty-one slides — fourteen in the pitch, six of measurement appendix, eleven of
platform appendix — as one hyperframes composition: index.html, the two Zen
faces, GSAP, and the screenshots, all under assets/. The marks are paths in
the page, one per lab, each painting a single colour that is theirs. Nothing
here loads from a network.
Read it
Open index.html in a browser, or serve the directory and open it there:
python3 -m http.server 8090
Arrow keys, space, click or swipe move between slides. Every number on a slide is a measurement or a count established in the repository this was cut from; none is typed in twice.
Present, render or measure it
npx hyperframes present . # presenter mode with an audience display (press P)
npx hyperframes render . # one MP4, seven seconds a slide
npx hyperframes check . # lint, runtime, layout and contrast
node deck/shoot.mjs # photograph every slide, and measure it
node deck/census.mjs # re-count the estate (slow: every repo's git log)
shoot.mjs runs from the repository root, serves this directory, and pages the
deck at 1280, 1600 and 2560. It seeks each slide to its own still frame before
measuring — the paused timeline holds every part it has not reached yet 22px
down, and measuring through that reads a phantom offset on a dozen boxes. Then it
checks two things a screenshot cannot tell you: whether any child leaves the
1920×1080 stage, and whether any flex or grid box has been shrunk below its own
content, which is how a caption comes to print through the line under it.
Where the numbers come from
node sync.mjs, run inside hanzoai/hanzo.ai, writes everything under
assets/ that a slide reads and fetches nothing itself — each of those files
already has an owner that probes for it, and a second probe here is how the deck
and the site come to state different numbers.
assets/bench.jsonis a copy oflib/data/bench.json, the same file the landing page's "Measured" band reads. Slides 8 and 9 and A1–A3 render from it.assets/models.jsonis written out oflib/data/pricing.json: the lab wall ranked by how many of each lab's models we carry, the catalog counts, and the output price beside each GPQA score.assets/rooms.jsonis the thirteen rooms underapp/(app), each named and described by its own page metadata. Which rooms exist is read fromlib/data/rooms.json, the answerscripts/sync-rooms.mjsalready keeps.assets/estate.jsonis written bycensus.mjs, not bysync.mjs: it reads every git log under~/work/{hanzo,lux,zoo}rather thanlib/data, and it takes minutes. Run it when the number needs refreshing.assets/platform.jsonis written out oflib/data/catalog.jsonandlib/data/families.json— the two snapshotsscripts/sync-catalog.mjstakes ofapi.hanzo.ai/v1/openapi.jsonand the commerce catalog, behind its own ratchets. It carries the whole surface (every family and the operations under each), the date those snapshots were taken, the ten layers and their product counts, the twelve-call tour, and one resolved record per product the appendix walks.
sync.mjs also stamps every slide's data-start from its position, so inserting
one renumbers the rest — and it puts every path the deck prints to the API
before writing it, so a slide cannot carry a call that answers nothing. An
unreachable API keeps the last certified answer; it never writes an unchecked
one.
Editing
One file. Each slide is a <section class="slide"> with data-composition-id;
the GSAP timeline at the bottom animates every slide from its start, and the
pager seeks that timeline. Add a slide by adding a section and a note in the JSON
island, then run node sync.mjs to time it. Keep the stage at 1920×1080 and
nothing past its edge.
A platform page declares one thing — data-family on its section — and sync.mjs
resolves the name, the sentence, the base path, the operation count, the page and
the repository behind it. A family the document stops carrying stops the sync
rather than shipping a slide that has quietly gone stale.