Files
hanzo.ai/deck/README.md
zeekayandHanzo Dev d90600177b The deck says why now, and both halves of it are drawn rather than claimed
Two slides between the title and the problem. The pitch opened by answering why
US before it had answered why NOW, so the cost argument on the old slide two was
competing with the thesis instead of following from it. It now follows: if
operations is the bottleneck you will run a great many agents, and everyone
else's bill for an idle one is the thing that stops you.

Slide 2 is the shape of the product, not a sentence about the market. Thirteen
rooms ship under app/(app) and exactly one of them is the code; the other twelve
are the inbox, the board, the calendar, the contacts, the meeting, the files,
the agents doing the work. Every name and every line on those tiles is that
room's own `metadata`, resolved by deck/sync.mjs out of lib/data/rooms.json —
the answer scripts/sync-rooms.mjs already keeps — and a room that describes
itself with nothing stops the sync rather than getting copy invented for it.
/dev is drawn hollow, the way the field's published figures are drawn hollow
elsewhere: it is the part that already got easy, and it is one tile of thirteen.

Slide 3 is our own receipt for the other half. deck/census.mjs walks every git
log under ~/work/{hanzo,lux,zoo}: 188 repositories took a commit in thirty days
and 13,259 commits landed.

It counts a commit ONCE and a repository once, which walking directories does
not: `git worktree` gives one repository several working directories sharing one
object store, so hanzo/universe and its testnet worktree each hand back the same
1,651 commits. Counting paths reported 202 repositories and 16,961 commits where
there are 188 and 13,259 — a 28% overstatement that looks exactly like growth.

And it claims a FLOOR rather than a figure. A commit carries an author name and
an author mailbox and here they disagree: 2,041 commits are `zeekay
<[email protected]>`, a person's name at a service address, and nothing in a git log
says whether a human had the agent's config set or the reverse. Guessing moves
the headline sixteen points. So both classifiers run, only their agreement is
counted, and the doubt is drawn on the slide as its own segment: at least 36.7%
of everything committed was written by an agent, and 52.6% if the disputed
middle is read as agents. A floor cannot be argued down, which is the only kind
of number worth putting in front of an investor. The footnote also declines to
call an identity a person — one of us commits under three names.

No 10x appears anywhere on the deck. It is a good thesis and it is not a number
we measured, and every other figure here re-derives from a URL or a log.

The pitch is renumbered s1–s14 so the series still means position, and the two
layout gates and hyperframes all pass at 31 slides: no overflow, nothing
squeezed, 210 of 210 contrast checks, and the room whose own description ran
long is trimmed in the data with an ellipsis rather than clipped by its box.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 19:34:54 -07:00

4.1 KiB
Raw Permalink Blame History

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.json is a copy of lib/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.json is written out of lib/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.json is the thirteen rooms under app/(app), each named and described by its own page metadata. Which rooms exist is read from lib/data/rooms.json, the answer scripts/sync-rooms.mjs already keeps.
  • assets/estate.json is written by census.mjs, not by sync.mjs: it reads every git log under ~/work/{hanzo,lux,zoo} rather than lib/data, and it takes minutes. Run it when the number needs refreshing.
  • assets/platform.json is written out of lib/data/catalog.json and lib/data/families.json — the two snapshots scripts/sync-catalog.mjs takes of api.hanzo.ai/v1/openapi.json and 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.