Files
hanzo.ai/deck/index.html

1208 lines
112 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Hanzo AI — the open AI cloud</title>
<script src="./assets/gsap.min.js"></script>
<style>
/* ZEN, FROM @hanzo/font — the one place the faces are authored. `block`
rather than `swap`: a held word is a moment, the wrong typeface is the
brand. The tail below is only ever reached if the face fails to arrive. */
@font-face { font-family: "Zen"; src: url("./assets/Zen-Variable.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: block; }
@font-face { font-family: "Zen Mono"; src: url("./assets/ZenMono-Variable.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: block; }
:root { --sans: "Zen", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mono: "Zen Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
/* NO HUE. Hanzo is monochrome — the site paints in #ffffff, #a1a1aa and
black, and this deck reads from the same ladder. The one distinction the
numbers need, ours against the field's, is carried by LUMINANCE AND FILL
instead of by colour: what we measured is white and solid, what the field
published is grey and hollow. Colour appears exactly once, on other
people's logos, where it belongs to them. #8a8a8a is 5.9:1 on black. */
:root { --ink: #ffffff; --mid: #a1a1aa; --dim: #8a8a8a; --line: rgba(255,255,255,0.14); --raised: rgba(255,255,255,0.055); }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
body { font-family: "Zen", -apple-system, "Segoe UI", Roboto, Arial, sans-serif; color: var(--ink); -webkit-font-smoothing: antialiased; }
#deck { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; overflow: hidden; transform-origin: 0 0; }
.bg { position: absolute; inset: 0; background: #000; }
.lamp { position: absolute; border-radius: 9999px; filter: blur(180px); }
.lamp.a { left: 0; top: 0; width: 900px; height: 460px; background: rgba(255,255,255,0.055); }
.lamp.b { right: 0; bottom: 0; width: 900px; height: 460px; background: rgba(255,255,255,0.04); }
.slide { position: absolute; inset: 0; padding: 118px 150px 108px; display: flex; flex-direction: column; opacity: 0; }
.eyebrow { font-family: var(--mono); font-size: 21px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--mid); }
.head { color: var(--ink); font-size: 88px; line-height: 1.03; letter-spacing: -0.035em; font-weight: 500; margin-top: 22px; max-width: 1520px; }
.head.small { font-size: 68px; }
.sub { font-size: 30px; line-height: 1.42; color: var(--mid); margin-top: 22px; max-width: 1260px; font-weight: 350; }
.rule { height: 1px; background: var(--line); margin-top: 36px; }
.spacer { flex: 1; }
.foot { font-family: var(--mono); font-size: 19px; letter-spacing: 0.12em; color: var(--dim); max-width: 1440px; line-height: 1.5; }
.mark { position: absolute; right: 150px; bottom: 46px; width: 56px; opacity: 0.9; }
/* A split: words on the left, the thing itself on the right. */
.split { display: grid; grid-template-columns: 640px minmax(0, 1fr); column-gap: 60px; align-items: start; flex: 1; min-height: 0; margin-top: 24px; }
.split .sub { margin-top: 0; font-size: 28px; }
.split .lines { margin-top: 30px; }
.frame { display: block; width: 100%; max-width: 100%; height: auto; max-height: 100%; object-fit: contain; object-position: top left; border-radius: 14px; border: 1px solid var(--line); box-shadow: 0 40px 90px rgba(0,0,0,0.75); }
.framebox { min-height: 0; height: 100%; display: flex; align-items: center; }
.framebox .frame { width: 100%; height: auto; max-height: 100%; object-position: center; }
.pills { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 24px; }
.pill { font-family: var(--mono); font-size: 18px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--mid); border: 1px solid rgba(255,255,255,0.22); border-radius: 999px; padding: 11px 20px; }
.pill.ours { color: var(--ink); border-color: rgba(255,255,255,0.55); }
.band { display: flex; gap: 76px; max-width: 1500px; }
.stat .num { font-family: var(--mono); font-size: 62px; line-height: 1; letter-spacing: -0.045em; color: var(--ink); font-variant-numeric: tabular-nums; }
.stat.field .num { color: var(--mid); }
.stat .lbl { font-family: var(--mono); font-size: 17px; letter-spacing: 0.19em; text-transform: uppercase; color: var(--dim); margin-top: 14px; }
.lbl code, .pill code { font: inherit; letter-spacing: inherit; text-transform: none; }
.lines { display: flex; flex-direction: column; gap: 20px; max-width: 1500px; }
.line { display: flex; align-items: baseline; gap: 24px; font-size: 28px; color: var(--mid); font-weight: 350; }
.line b { font-family: var(--mono); font-size: 18px; letter-spacing: 0.17em; text-transform: uppercase; color: var(--ink); font-weight: 400; width: 150px; flex: none; }
table { border-collapse: collapse; width: 100%; max-width: 1620px; margin-top: 18px; }
th, td { text-align: left; padding: 12px 18px 12px 0; border-bottom: 1px solid var(--line); }
th { font-family: var(--mono); font-size: 16px; letter-spacing: 0.19em; text-transform: uppercase; color: var(--dim); font-weight: 400; }
th.ours { color: var(--ink); } th.theirs { color: var(--dim); }
td { font-size: 25px; color: var(--mid); font-weight: 350; }
td.ours { font-family: var(--mono); color: var(--ink); font-weight: 500; }
td.theirs { font-family: var(--mono); color: var(--mid); font-weight: 350; }
td.mult { font-family: var(--mono); color: var(--ink); text-align: right; padding-right: 0; }
td.grp { font-family: var(--mono); font-size: 15px; letter-spacing: 0.19em; text-transform: uppercase; color: var(--dim); padding-top: 18px; }
table.tight td { font-size: 22px; padding: 9px 14px 9px 0; }
table.tight th { font-size: 14px; }
table.tight td.theirs, table.tight td.ours, table.tight td.mult { white-space: nowrap; }
.method { font-family: var(--mono); font-size: 18px; letter-spacing: 0.08em; color: var(--dim); margin-top: 22px; line-height: 1.6; max-width: 1440px; }
.method b { color: var(--ink); font-weight: 450; }
/* The head-to-head, drawn from the numbers by the script. */
.bars { display: flex; flex-direction: column; gap: 12px; margin-top: 24px; max-width: 1600px; }
.bar { display: grid; grid-template-columns: 300px minmax(0, 1fr) 110px 150px; align-items: center; gap: 22px; }
.bar .nm { font-size: 26px; font-weight: 400; }
.bar .nm small { font-family: var(--mono); font-size: 15px; letter-spacing: 0.14em; color: var(--dim); margin-left: 10px; text-transform: uppercase; }
/* Two bars a row, from the same catalog: what it scored, and what it costs
to run — so "the best score at a fraction of the price" is a shape and
not an adjective. The score bar is long where we win; the price bar is
SHORT where we win, and the same fill rule reads both. */
.bar .stack { display: flex; flex-direction: column; gap: 9px; }
.bar .track { height: 30px; background: rgba(255,255,255,0.07); border-radius: 6px; overflow: hidden; }
.bar .track.thin { height: 11px; border-radius: 3px; }
.bar .fill { height: 100%; border-radius: 6px; background: rgba(255,255,255,0.15); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.34); transform-origin: left center; }
.bar .track.thin .fill { border-radius: 3px; }
.bar.ours .fill { background: var(--ink); box-shadow: none; }
.bar.ours .nm, .bar.ours .v { color: var(--ink); font-weight: 500; }
.bar .v { font-family: var(--mono); font-size: 27px; text-align: right; font-variant-numeric: tabular-nums; }
.bar .pr { font-family: var(--mono); font-size: 24px; text-align: right; color: var(--mid); font-weight: 350; font-variant-numeric: tabular-nums; }
.bar.ours .pr { color: var(--ink); font-weight: 500; }
.barhead { display: grid; grid-template-columns: 300px minmax(0, 1fr) 110px 150px; gap: 22px; font-family: var(--mono); font-size: 15px; letter-spacing: 0.19em; text-transform: uppercase; color: var(--dim); margin-top: 26px; max-width: 1600px; }
.barhead span + span { text-align: right; }
.axis { display: flex; justify-content: space-between; font-family: var(--mono); font-size: 15px; letter-spacing: 0.14em; color: var(--dim); margin-top: 8px; padding-left: 322px; padding-right: 304px; max-width: 1600px; }
.logos { display: flex; flex-wrap: wrap; align-items: center; gap: 24px 40px; max-width: 1600px; }
/* The one place colour appears. Every mark is a single path painting
currentColor, so the brand is one declaration on the span. */
.logo { display: inline-flex; align-items: center; height: 42px; }
.logo svg { height: 100%; width: auto; display: block; }
.logo.tall { height: 54px; } .logo.wide { height: 62px; }
/* THE WALL: who we carry, ranked by how many of their models we
carry, both read from the pricing catalog at sync. */
.wall.five { grid-template-columns: repeat(5, 1fr); margin-top: 26px; }
.badges { display: flex; gap: 96px; margin-top: 46px; }
.badge { display: flex; align-items: center; gap: 26px; }
.badge .nm { font-size: 30px; font-weight: 450; }
.logo.big { height: 84px; }
.wall { display: grid; grid-template-columns: repeat(6, 1fr); gap: 54px 40px; margin-top: 50px; max-width: 1620px; }
.wall .cell { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.wall .logo { height: 66px; }
/* The name, where we hold no mark: the same 66px the artwork occupies, so
a row of cells still sits on one line, and set in our own type because
it is the company's name and not its logo. */
.word { height: 66px; display: flex; align-items: center; font-size: 32px; font-weight: 500; letter-spacing: -0.03em; color: var(--ink); }
.wall .nm { font-size: 24px; font-weight: 400; line-height: 1.25; }
.ct { font-family: var(--mono); font-size: 15px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--dim); margin-top: 6px; }
.logos .name { font-family: var(--mono); font-size: 21px; color: var(--mid); letter-spacing: 0.06em; }
/* THE TEN LAYERS, each carrying the number of products the catalog sells
in it — counted at sync, so the ladder is a measurement and the bar
beside it cannot disagree with the number at its end. */
.layers { display: flex; flex-direction: column; }
.layer { display: grid; grid-template-columns: 28px 176px minmax(0, 1fr) 140px 34px; column-gap: 16px; align-items: center; padding: 11px 0; border-bottom: 1px solid var(--line); }
.layer .n { font-family: var(--mono); font-size: 16px; color: var(--dim); }
.layer .nm { font-size: 25px; font-weight: 450; }
.layer .of { font-family: var(--mono); font-size: 15px; color: var(--dim); }
.layer .track { display: block; height: 12px; background: rgba(255,255,255,0.07); border-radius: 3px; overflow: hidden; }
.layer .fill { display: block; height: 100%; background: var(--ink); border-radius: 3px; transform-origin: left center; }
.layer .ct { font-family: var(--mono); font-size: 19px; letter-spacing: 0; text-transform: none; color: var(--ink); text-align: right; margin-top: 0; }
.calls { display: flex; flex-direction: column; gap: 16px; align-items: flex-start; }
.call { font-family: var(--mono); font-size: 27px; background: var(--raised); border: 1px solid var(--line); border-radius: 12px; padding: 18px 26px; }
.call b { color: var(--dim); font-weight: 400; }
/* The four cards reach the floor of the slide, and their prose sits on
that floor: the price at the top, what you get at the bottom, and the
air between them rather than under all four. */
.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; max-width: 1620px; margin-top: 30px; flex: 1; min-height: 0; }
.tile { border: 1px solid var(--line); border-radius: 16px; padding: 34px 30px 30px; background: var(--raised); display: flex; flex-direction: column; }
.tile .k { font-family: var(--mono); font-size: 16px; letter-spacing: 0.19em; text-transform: uppercase; color: var(--dim); }
.tile .p { font-family: var(--mono); font-size: 54px; letter-spacing: -0.04em; margin-top: 12px; color: var(--mid); font-variant-numeric: tabular-nums; font-weight: 350; }
.tile.ours .p { color: var(--ink); font-weight: 500; }
.tile .d { font-size: 22px; line-height: 1.4; color: var(--mid); margin-top: 12px; font-weight: 350; }
.tile .p + .d { margin-top: auto; padding-top: 32px; }
.tile .d + .d { margin-top: 10px; }
/* THE SEVEN BILLS: what an assembled stack costs before a line of product. */
.bills { display: grid; grid-template-columns: repeat(7, 1fr); gap: 16px; margin-top: 48px; max-width: 1620px; }
.bill { border: 1px solid var(--line); border-radius: 12px; padding: 26px 20px 24px; background: var(--raised); transform-origin: bottom center; min-height: 208px; display: flex; flex-direction: column; }
.bill .who { font-size: 25px; line-height: 1.25; flex: 1; }
.bill .amt { font-family: var(--mono); font-size: 15px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); margin-top: 14px; border-top: 1px dashed rgba(255,255,255,0.2); padding-top: 12px; }
.bill .amt b { color: var(--ink); font-weight: 400; display: block; font-size: 22px; letter-spacing: 0; text-transform: none; margin-top: 4px; }
/* THE SIZE OF A DORMANT AGENT, at true scale: sides in the ratio of the
square roots, so the areas are in the ratio of the bytes. Ours sits
INSIDE theirs, in the corner, because the comparison is containment —
two squares side by side make the reader do the nesting in their head. */
.sizes { display: flex; align-items: flex-end; gap: 44px; margin-top: 14px; }
.sq { border-radius: 6px; transform-origin: bottom left; }
.sq.theirs { position: relative; width: 430px; height: 430px; background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.38); }
.sq.ours { position: absolute; left: 12px; bottom: 12px; width: 8px; height: 8px; background: var(--ink); box-shadow: 0 0 0 5px rgba(255,255,255,0.22); }
.sqlbl { font-family: var(--mono); font-size: 15px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim); margin-top: 10px; }
.sqlbl b { display: block; font-size: 22px; letter-spacing: 0; text-transform: none; font-weight: 400; margin-top: 3px; }
.sqlbl.ours b { color: var(--ink); } .sqlbl.theirs b { color: var(--mid); font-weight: 350; }
.starts { margin-top: 26px; display: flex; flex-direction: column; gap: 9px; }
.start { display: grid; grid-template-columns: 150px minmax(0, 1fr) 84px; gap: 12px; align-items: center; font-family: var(--mono); font-size: 15px; color: var(--mid); }
.start .track { height: 14px; background: rgba(255,255,255,0.07); border-radius: 4px; overflow: hidden; }
.start .fill { height: 100%; background: rgba(255,255,255,0.15); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.34); transform-origin: left center; border-radius: 4px; }
.start.ours .fill { background: var(--ink); box-shadow: none; } .start.ours { color: var(--ink); font-weight: 500; }
.start .v { text-align: right; }
/* THE LEDGER: four lines that add up, in the order they were measured. */
.ledger { font-family: var(--mono); margin-top: 30px; max-width: 1620px; border-top: 1px solid var(--line); }
.row { display: grid; grid-template-columns: 340px 420px minmax(0, 1fr); gap: 24px; align-items: baseline; padding: 20px 0; border-bottom: 1px solid var(--line); font-size: 26px; }
.row .k { color: var(--dim); font-size: 18px; letter-spacing: 0.16em; text-transform: uppercase; }
.row .a { color: var(--ink); font-variant-numeric: tabular-nums; }
.row .b { color: var(--mid); font-weight: 350; font-variant-numeric: tabular-nums; }
.row.ours .a, .row.ours .b { color: var(--ink); font-weight: 500; }
.row.sum { font-size: 34px; } .row.sum .b { color: var(--ink); font-weight: 500; }
.title-wrap .head { font-size: 124px; margin-top: 18px; letter-spacing: -0.045em; }
.title-wrap .head { color: var(--mid); }
.title-wrap .head em { font-style: normal; color: var(--ink); }
.title-wrap .sub { max-width: 1400px; font-size: 29px; margin-top: 16px; }
/* A shot is centred in the room it is given. Anchoring it top-left leaves
the leftover width as a black column beside it, which reads as a
cropping accident rather than a frame. */
.hero { margin-top: 26px; min-height: 0; flex: 1; display: flex; align-items: center; justify-content: center; }
.hero .frame { width: auto; height: 100%; max-width: 100%; object-position: center; }
/* THE SURFACE: every family the served document declares, drawn as a
rectangle whose AREA is in the ratio of the operations under it.
Squarified, so a family is a patch and not a sliver. On a product slide
the family being described is the lit one — the same luminance-and-fill
rule the numbers use, applied to a map instead of a bar. */
.surface { position: relative; min-height: 0; align-self: stretch; }
.product .surface { margin-bottom: 62px; }
.area { position: absolute; border-radius: 5px; background: var(--raised); box-shadow: inset 0 0 0 1px var(--line); overflow: hidden; padding: 8px 10px; }
.area b { display: block; font-family: var(--mono); font-size: 14px; letter-spacing: 0.05em; color: var(--mid); font-weight: 400; white-space: nowrap; }
.area i { font-style: normal; font-family: var(--mono); font-size: 13px; color: var(--dim); }
.area.ours { background: var(--ink); box-shadow: 0 0 0 5px rgba(255,255,255,0.16); padding: 4px 6px; }
.area.ours b { font-size: 12px; letter-spacing: 0; }
.area.ours b, .area.ours i { color: #000; }
/* THE TOUR: twelve operations in the order somebody calls them. The verb,
the path and the sentence are the document's; the line is ours. */
.beats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 70px; margin-top: 20px; }
.beat { display: grid; align-content: start; grid-template-columns: 36px 74px minmax(0, 1fr); column-gap: 16px; row-gap: 5px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.beat .n, .beat .m { font-family: var(--mono); font-size: 15px; letter-spacing: 0.12em; color: var(--dim); }
.beat .m { color: var(--ink); }
.beat .p { font-family: var(--mono); font-size: 20px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.beat .d { grid-column: 2 / -1; font-size: 20px; line-height: 1.35; color: var(--mid); font-weight: 350; }
/* THE GAP, on a log axis: what a call costs to serve, what we sell it for,
and what the category charges for the same call. Five decades apart, so
a linear axis would stack all three marks on one pixel. The decade rules
are the background's, because a gridline is not a fact worth an element. */
.scale { position: relative; height: 196px; max-width: 1620px; margin-top: 30px;
background: repeating-linear-gradient(90deg, rgba(255,255,255,0.075) 0 1px, transparent 1px 14.2857%); }
.tick { position: absolute; top: 0; bottom: 0; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; }
.tick i { flex: 1; width: 1px; background: rgba(255,255,255,0.34); }
.tick.ours i { width: 2px; background: var(--ink); }
.tick b { font-family: var(--mono); font-size: 26px; color: var(--mid); font-weight: 400; white-space: nowrap; font-variant-numeric: tabular-nums; }
.tick.ours b { color: var(--ink); font-weight: 500; }
.tick span { font-family: var(--mono); font-size: 15px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--dim); margin: 8px 0 14px; white-space: nowrap; }
/* The distance between the first mark and the last IS the multiple the
ledger states below, so it is drawn rather than restated. */
.reach { position: absolute; height: 1px; background: rgba(255,255,255,0.3); }
.reach b { position: absolute; left: 50%; bottom: 9px; transform: translateX(-50%); font-family: var(--mono); font-size: 20px; letter-spacing: 0.1em; color: var(--ink); font-weight: 400; white-space: nowrap; }
/* A REFERENCE SHEET reads at arm's length, so its rows are twice the
height of the receipt's on slide 10, which is a dense comparison. */
.sheet table.tight td { font-size: 25px; padding: 19px 16px 19px 0; }
.sheet table.tight th { font-size: 15px; }
.sheet .method { margin-top: 34px; }
/* A short sheet keeps its footnote with its content. Pushed to the floor
it reads as a stranded line under half a page of nothing. */
.sheet .spacer { display: none; }
.sheet .foot { margin-top: 40px; }
.sheet .starts { gap: 14px; margin-top: 20px; }
.sheet .start { grid-template-columns: 230px minmax(0, 1fr) 110px; font-size: 21px; }
.sheet .start .track { height: 26px; border-radius: 6px; }
.sheet .start .fill { border-radius: 6px; }
/* THE THIRTEEN ROOMS, and the whole argument is the count. Twelve of them
are the company — the inbox, the board, the calendar, the meeting — and
one is the code. The one that is the code is drawn hollow, the way the
field's published numbers are drawn hollow elsewhere: it is the part
that already got easy, and it is one tile of thirteen. */
.rooms { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 15px; margin-top: 28px; }
.room { border: 1px solid var(--line); border-radius: 14px; padding: 20px 18px 16px; background: var(--raised); display: flex; flex-direction: column; min-height: 150px; }
.room .nm { font-size: 25px; font-weight: 500; letter-spacing: -0.02em; color: var(--ink); }
.room .of { font-size: 16px; line-height: 1.32; font-weight: 350; color: var(--mid); margin-top: 9px; }
.room .rt { font-family: var(--mono); font-size: 13px; letter-spacing: 0.1em; color: var(--dim); margin-top: auto; padding-top: 12px; }
.room.code { background: transparent; border-style: dashed; }
.room.code .nm, .room.code .of { color: var(--dim); }
/* WHO WROTE IT. One bar, three segments, and the middle one is the honest
part: the commits whose author name and mailbox disagree about whether
a person or an agent made them. The claim is the left segment alone. */
.who { display: flex; height: 92px; margin-top: 32px; max-width: 1620px; border-radius: 10px; overflow: hidden; }
.who > div { display: flex; flex-direction: column; justify-content: center; padding: 0 24px; min-width: 0; }
.who b { font-family: var(--mono); font-size: 30px; font-weight: 500; letter-spacing: -0.02em; }
.who span { font-family: var(--mono); font-size: 14px; letter-spacing: 0.16em; text-transform: uppercase; margin-top: 5px; white-space: nowrap; }
.who .agent { background: var(--ink); color: #000; }
.who .agent span { color: rgba(0,0,0,0.62); }
.who .split { background: rgba(255,255,255,0.26); color: var(--ink); }
.who .split span { color: var(--mid); }
.who .person { background: rgba(255,255,255,0.06); box-shadow: inset 0 0 0 1px var(--line); color: var(--mid); }
.who .person b { font-weight: 350; }
.who .person span { color: var(--dim); }
#pager { position: fixed; right: 26px; bottom: 20px; font-family: var(--mono); font-size: 15px; letter-spacing: 0.16em; color: var(--dim); z-index: 10; }
#pager.off { display: none; }
</style>
</head>
<body>
<div id="deck" data-composition-id="deck" data-width="1920" data-height="1080">
<div class="bg"></div>
<div class="lamp a"></div>
<div class="lamp b"></div>
<img class="mark" src="./assets/mark.svg" alt="Hanzo" />
<!-- 1 · Title -->
<section class="slide title-wrap" id="s1" data-composition-id="s1" data-start="0" data-duration="7">
<div class="eyebrow">Hanzo AI · Open AI Cloud</div>
<h1 class="head">Build <em>what’s next.</em></h1>
<div class="sub">The open AI cloud for agents, models and applications — the field’s best-scoring model and the lightest, cheapest place to run an agent, both measured.</div>
<div class="pills"><div class="pill">NVIDIA Inception member</div><div class="pill">Techstars 2017</div><div class="pill ours">Open source · 682 repos</div></div>
<div class="hero"><img class="frame" src="./assets/live/hero.jpg" alt="hanzo.ai, the fold" /></div>
</section>
<!-- 2 · Why now: the bottleneck moved -->
<section class="slide" id="s2" data-composition-id="s2" data-start="7" data-duration="7">
<div class="eyebrow">Why now</div>
<h2 class="head small">Writing the software stopped being the hard part.</h2>
<div class="sub" style="max-width:1560px">Everything else a company does is still done by hand — the inbox, the board, the calendar, the contacts, the meeting, the money. That is the bottleneck now, and it is what Hanzo automates. The product's own shape says so before we do.</div>
<div class="rooms" id="rooms"></div>
<div class="spacer"></div>
<div class="foot" id="rooms-foot"></div>
</section>
<!-- 3 · Why now: our own receipt -->
<section class="slide" id="s3" data-composition-id="s3" data-start="14" data-duration="7">
<div class="eyebrow">Why now · our own receipt</div>
<h2 class="head small">More than a third of what we shipped last month, an agent wrote.</h2>
<div class="sub" style="max-width:1560px">Not a forecast about the industry — a census of our own git logs. Every repository this company keeps, every commit in thirty days, sorted by who signed it.</div>
<div class="who" id="who"></div>
<div class="band" id="estate" style="margin-top:44px"></div>
<div class="spacer"></div>
<div class="foot" id="estate-foot"></div>
</section>
<!-- 4 · Problem -->
<section class="slide" id="s4" data-composition-id="s4" data-start="21" data-duration="7">
<div class="eyebrow">Problem</div>
<h2 class="head small">An agent costs more to keep than it costs to think.</h2>
<div class="sub">Everyone selling agent infrastructure assembled it. Nobody owns the layer under the layer, so every one of them forwards somebody else’s bill for a machine that is sitting still.</div>
<div class="bills">
<div class="bill"><div class="who">Model vendor</div><div class="amt">bill<b>per token</b></div></div>
<div class="bill"><div class="who">Sandbox vendor</div><div class="amt">bill<b>per second</b></div></div>
<div class="bill"><div class="who">Vector store</div><div class="amt">bill<b>per GB</b></div></div>
<div class="bill"><div class="who">Identity provider</div><div class="amt">bill<b>per seat</b></div></div>
<div class="bill"><div class="who">Queue</div><div class="amt">bill<b>per message</b></div></div>
<div class="bill"><div class="who">Object store</div><div class="amt">bill<b>per request</b></div></div>
<div class="bill"><div class="who">Observability</div><div class="amt">bill<b>per event</b></div></div>
</div>
<div class="rule" style="margin-top:46px"></div>
<div class="band" style="margin-top:44px">
<div class="stat field"><div class="num" data-count="7">0</div><div class="lbl">Contracts before a line of product</div></div>
<div class="stat field"><div class="num">~1 MB</div><div class="lbl">What a dormant agent is assumed to weigh</div></div>
<div class="stat field"><div class="num">$0.05</div><div class="lbl">A credit, priced against nothing</div></div>
</div>
<div class="spacer"></div>
<div class="foot">Seven contracts, seven keys, seven invoices. The megabyte and the five-cent credit are the category’s own published figures; slide 10 measures ours against both.</div>
</section>
<!-- 5 · Solution -->
<section class="slide" id="s5" data-composition-id="s5" data-start="28" data-duration="7">
<div class="eyebrow">Solution</div>
<h2 class="head small">We own every layer.</h2>
<div class="split" style="grid-template-columns: 760px minmax(0,1fr); column-gap: 60px">
<div>
<div class="sub">One host answers for the whole platform and one credential opens it. Models, sandboxes, data, identity, secrets and storage are ours, in one binary — so what an idle agent costs is a number we set rather than a bill we pass on.</div>
<div class="band" style="margin-top:40px; gap:52px">
<div class="stat"><div class="num" data-count="0" data-platform="operations">0</div><div class="lbl">Operations</div></div>
<div class="stat"><div class="num" data-count="0" data-platform="products">0</div><div class="lbl">Products</div></div>
</div>
<div class="calls" style="margin-top:40px">
<div class="call"><b>$</b> curl https://api.hanzo.ai/v1/&lt;anything&gt;</div>
<div class="call"><b>&nbsp;&nbsp;</b> -H "Authorization: Bearer $HANZO_KEY"</div>
</div>
<div class="foot" style="margin-top:26px">One host. One key. The appendix walks the ten layers behind it, one product each.</div>
</div>
<div class="framebox"><img class="frame" src="./assets/live/connected.jpg" alt="Everything connected: the rooms an organization shares" /></div>
</div>
</section>
<!-- 6 · Product -->
<section class="slide" id="s6" data-composition-id="s6" data-start="35" data-duration="7">
<div class="eyebrow">Product</div>
<h2 class="head small">Agents that keep working.</h2>
<div class="sub" style="max-width:1100px;font-size:28px">One workspace: the agents an organization runs, what each did while nobody was watching, and one place to answer.</div>
<div class="hero"><img class="frame" src="./assets/product.png" alt="The Hanzo workspace: five agents and a finished run" /></div>
</section>
<!-- 7 · Enso leads -->
<section class="slide" id="s7" data-composition-id="s7" data-start="42" data-duration="7">
<div class="eyebrow">Enso · our frontier model</div>
<h2 class="head small">The best score in the field, and the cheapest model near the top of it.</h2>
<div class="sub" style="margin-top:14px">GPQA-Diamond, 198 graduate-level science questions, the full pool. Hanzo-measured, cross-checked against the leaderboard snapshot.</div>
<div class="barhead"><span></span><span>GPQA-Diamond</span><span>score</span><span>$ / M out</span></div>
<div class="bars" id="gpqa"></div>
<div class="axis"><span>85</span><span>90</span><span>95</span><span>100</span></div>
<div class="spacer"></div>
<div class="foot">Enso Ultra answered 194 of 198, the best score on the board · the score axis runs from 85 and the thin bar under it is that model's output price per million tokens, log-scaled to $200 · we are not cheapest at every rung and the column says so: Gemini 3.1 Pro is $12 against Ultra's $25. The claim is the two ends — best score outright, and Enso Flash within half a point of GPT-5.2 Pro at a forty-second of its price.</div>
</section>
<!-- 8 · The models -->
<section class="slide" id="s8" data-composition-id="s8" data-start="49" data-duration="7">
<div class="eyebrow">Models</div>
<h2 class="head small">Every major lab, one route.</h2>
<div class="sub">Enso is ours. Zen is the open-weight family, made with the Zoo Labs Foundation. Everyone else’s models sit on the same routing table, at the same endpoint, on one key.</div>
<div class="wall" id="wall"></div>
<div class="spacer"></div>
<div class="band">
<div class="stat"><div class="num" data-count="0" data-catalog="models">0</div><div class="lbl">Models in the catalog</div></div>
<div class="stat"><div class="num" data-count="0" data-catalog="free">0</div><div class="lbl">Free to call</div></div>
<div class="stat"><div class="num" data-count="0" data-catalog="providers">0</div><div class="lbl">Providers</div></div>
<div class="stat"><div class="num">1</div><div class="lbl">Endpoint</div></div>
</div>
</section>
<!-- 9 · Vertical integration -->
<section class="slide" id="s9" data-composition-id="s9" data-start="56" data-duration="7">
<div class="eyebrow">Vertical integration</div>
<h2 class="head small">Ten layers, one contract. Your laptop is the cluster.</h2>
<div class="split" style="grid-template-columns: 720px minmax(0,1fr); column-gap: 56px;">
<div>
<div class="layers">
<div class="layer"><span class="n">01</span><span class="nm">AI</span><span class="of">models, agents</span></div>
<div class="layer"><span class="n">02</span><span class="nm">Compute</span><span class="of">sandboxes, functions</span></div>
<div class="layer"><span class="n">03</span><span class="nm">Data</span><span class="of">tables, vectors, files</span></div>
<div class="layer"><span class="n">04</span><span class="nm">Network</span><span class="of">routing, edge</span></div>
<div class="layer"><span class="n">05</span><span class="nm">Security</span><span class="of">identity, secrets</span></div>
<div class="layer"><span class="n">06</span><span class="nm">Dev</span><span class="of">build, ship</span></div>
<div class="layer"><span class="n">07</span><span class="nm">Infrastructure</span><span class="of">clusters, apps</span></div>
<div class="layer"><span class="n">08</span><span class="nm">Observe</span><span class="of">traces, cost</span></div>
<div class="layer"><span class="n">09</span><span class="nm">Web3</span><span class="of">chains, wallets</span></div>
<div class="layer"><span class="n">10</span><span class="nm">Apps</span><span class="of">the ones people open</span></div>
</div>
<div class="foot" style="margin-top:24px">The bar is how many products the catalog sells in that layer · the same binaries on a laptop, in your cluster, or in ours · deploy targets: AWS · Google Cloud · Azure · DigitalOcean · Cloudflare</div>
</div>
<div class="framebox"><img class="frame" src="./assets/live/laptop.jpg" alt="hanzo up: a Kubernetes control plane in a microVM, 7 seconds from cold" /></div>
</div>
</section>
<!-- 10 · Proof -->
<section class="slide" id="s10" data-composition-id="s10" data-start="63" data-duration="7">
<div class="eyebrow">Proof · the fastest, lightest agentic cloud</div>
<h2 class="head small">Measured on a laptop, by scripts that re-run.</h2>
<div class="split" style="grid-template-columns: 1000px minmax(0,1fr); column-gap: 70px; margin-top: 6px">
<table class="tight" id="proof"></table>
<div>
<div class="sqlbl" style="margin-top:0">One dormant agent, to scale</div>
<div class="sizes"><div class="sq theirs"><div class="sq ours"></div></div></div>
<div class="band" style="margin-top:26px; gap:70px">
<div class="sqlbl theirs">published<b id="sq-theirs"></b></div>
<div class="sqlbl ours">measured<b id="sq-ours"></b></div>
</div>
<div class="method">Sides in the ratio of the square roots, so the areas are in the ratio of the bytes.</div>
</div>
</div>
</section>
<!-- 11 · Unit economics -->
<section class="slide" id="s11" data-composition-id="s11" data-start="70" data-duration="7">
<div class="eyebrow">Unit economics</div>
<h2 class="head small">Ninety percent under them, and it still clears.</h2>
<div class="scale" id="scale"></div>
<div class="ledger" id="ledger"></div>
<div class="spacer"></div>
<div class="foot" id="ledger-foot"></div>
</section>
<!-- 12 · Traction & credibility -->
<section class="slide" id="s12" data-composition-id="s12" data-start="77" data-duration="7">
<div class="eyebrow">Traction &amp; credibility</div>
<h2 class="head small">Ten years of building the tools we needed, then giving them away.</h2>
<div class="badges" id="badges"></div>
<div class="ct" style="margin-top:52px">Deploy targets · the same binaries, wherever they run</div>
<div class="wall five" id="clouds"></div>
<div class="spacer"></div>
<div class="band">
<div class="stat"><div class="num">$1B+</div><div class="lbl">Client revenue, via Sensei</div></div>
<div class="stat"><div class="num" data-count="100" data-suffix="+">0</div><div class="lbl">Products to market</div></div>
<div class="stat"><div class="num" data-count="682">0</div><div class="lbl">Public repos · 6 orgs</div></div>
<div class="stat"><div class="num">2016</div><div class="lbl">Incorporated</div></div>
</div>
<div class="rule"></div>
<div class="foot" style="margin-top:22px">hanzoai 345 · luxfi 140 · hanzo-apps 99 · zenlm 87 · hanzo-js 7 · zoo-labs 4 &nbsp;·&nbsp; SSO · SCIM · per-org encryption · audit · GDPR · CCPA · HIPAA BAA</div>
</section>
<!-- 13 · Business model -->
<section class="slide" id="s13" data-composition-id="s13" data-start="84" data-duration="7">
<div class="eyebrow">Business model</div>
<h2 class="head small">Free where it should be. Paid where it works.</h2>
<div class="tiles">
<div class="tile"><div class="k">Free, forever</div><div class="p">$0</div><div class="d">hanzo.chat, with no account.</div><div class="d">Local private AI on your own machine.</div><div class="d">The open Zen weights.</div></div>
<div class="tile ours"><div class="k">The hanzo.ai app</div><div class="p"><span data-count="19" data-prefix="$">0</span><span style="font-size:24px;color:var(--dim)">/mo</span></div><div class="d">An organization or a personal account.</div><div class="d">Usage metered past the included credit.</div><div class="d">No per-seat fee.</div></div>
<div class="tile ours"><div class="k">API · pay as you go</div><div class="p"><span data-count="5" data-prefix="+" data-suffix="%">0</span></div><div class="d">Model cost plus a five percent platform fee.</div><div class="d">No minimum. <span data-catalog="models"></span> models, one key.</div><div class="d">Enso has its own free and paid plans.</div></div>
<div class="tile"><div class="k">Enterprise</div><div class="p">Talk</div><div class="d">SSO / SAML, contractual SLAs.</div><div class="d">Invoicing and PO terms.</div><div class="d">Bring your own keys.</div></div>
</div>
<div class="foot" style="margin-top:30px">Nothing at all for a machine that is sitting still. Quoted before it runs, booked at what it cost.</div>
</section>
<!-- 14 · The ask -->
<section class="slide" id="s14" data-composition-id="s14" data-start="91" data-duration="7">
<div class="eyebrow">Join us</div>
<h2 class="head small">Own a piece of the open AI cloud.</h2>
<div class="split">
<div>
<div class="sub">The round is open to everyone on Republic — the same people who clone the repos, run the weights, and ask Enso. Two lines to try it first.</div>
<div class="calls" style="margin-top:34px">
<div class="call"><b>$</b> curl -fsSL https://hanzo.sh | sh</div>
<div class="call"><b>$</b> claude mcp add hanzo https://api.hanzo.ai/v1/mcp</div>
</div>
<div class="foot" style="margin-top:30px">hanzo.ai · api.hanzo.ai · republic.com</div>
</div>
<div class="framebox"><img class="frame" src="./assets/live/start.jpg" alt="Say what you want: a sentence opens a chat, a coding agent, or the cloud" /></div>
</div>
</section>
<!-- A1 · Fleet residency -->
<section class="slide sheet" id="a1" data-composition-id="a1" data-start="98" data-duration="7">
<div class="eyebrow">Appendix · A1 · fleet residency</div>
<h2 class="head small">What a dormant agent weighs.</h2>
<table class="tight" id="a1-table" data-row="residency"></table>
<div class="method" id="a1-method"></div>
<div class="spacer"></div>
<div class="foot">A suite that only contains wins is a brochure; see A4.</div>
</section>
<!-- A2 · Sandbox cold start -->
<section class="slide sheet" id="a2" data-composition-id="a2" data-start="105" data-duration="7">
<div class="eyebrow">Appendix · A2 · sandbox cold start</div>
<h2 class="head small">How long a sandbox takes to answer.</h2>
<table class="tight" id="a2-table" data-row="cold"></table>
<div class="ct" style="margin-top:40px">Every figure the row states, over the slowest of them</div>
<div class="starts" id="starts"></div>
<div class="method" id="a2-method"></div>
<div class="spacer"></div>
<div class="foot">Two primitives, not one: a V8 context runs JavaScript and cannot run pytest, pip, cargo or a shell. The rows are compared per workload, not per millisecond.</div>
</section>
<!-- A3 · Unit economics -->
<section class="slide sheet" id="a3" data-composition-id="a3" data-start="112" data-duration="7">
<div class="eyebrow">Appendix · A3 · unit economics</div>
<h2 class="head small">What a call costs, and what it sells for.</h2>
<table class="tight" id="a3-table" data-row="call"></table>
<div class="method" id="a3-method"></div>
<div class="spacer"></div>
<div class="foot">Excluded, and it dominates: model tokens. A turn that thinks costs 100–1,000× the infrastructure under it, so a per-call price sits beside token billing rather than pretending to include it.</div>
</section>
<!-- A4 · Recall -->
<section class="slide" id="a4" data-composition-id="a4" data-start="119" data-duration="7">
<div class="eyebrow">Appendix · A4 · recall on LoCoMo</div>
<h2 class="head small">The row we are working on.</h2>
<table class="tight">
<tr><th>recall@20, every gold turn retrieved</th><th class="ours">single-hop (841 Q)</th><th class="ours">multi-hop (282 Q)</th><th style="text-align:right">&nbsp;</th></tr>
<tr><td>one vector search</td><td class="ours">78.5%</td><td class="ours">20.9%</td><td class="mult">any: 78.7%</td></tr>
<tr><td>pseudo-relevance feedback</td><td class="ours">75.4%</td><td class="ours">19.5%</td><td class="mult">−1.4</td></tr>
<tr><td>search again from the top hits</td><td class="ours">78.5%</td><td class="ours">20.9%</td><td class="mult">+0.0</td></tr>
<tr><td>chained, two hops deep</td><td class="ours">78.5%</td><td class="ours">20.9%</td><td class="mult">+0.0</td></tr>
<tr><td>published claim</td><td class="theirs">91%</td><td class="theirs">57%</td><td class="mult">k unstated</td></tr>
</table>
<div class="barhead" style="margin-top:24px"><span>the distance left to close</span><span>recall</span><span></span></div>
<div class="bars" id="gap"></div>
<div class="method"><b>method</b> qwen3-embedding via api.hanzo.ai · brute-force cosine, 4.0 ms per conversation, no index · <b>node brain.mjs locomo10.json</b> then <b>node hops.mjs</b> in cloud/bench/brain · LoCoMo categories 4 and 1 (Maharana et al.)</div>
<div class="spacer"></div>
<div class="foot">Any is 78.7% and all is 20.9%: the store finds a relevant turn nearly every time and every relevant turn nearly never. More vector search does not close that gap. The published 91/57 states neither its k nor all-versus-any, so it is a target rather than a like-for-like.</div>
</section>
<!-- A5 · GPQA-Diamond -->
<section class="slide sheet" id="a5" data-composition-id="a5" data-start="126" data-duration="7">
<div class="eyebrow">Appendix · A5 · GPQA-Diamond</div>
<h2 class="head small">The full table.</h2>
<div class="split" style="grid-template-columns: 1fr 1fr; column-gap: 80px; margin-top: 4px">
<table class="tight" id="gpqaA"></table>
<table class="tight" id="gpqaB"></table>
</div>
<div class="method"><b>method</b> hanzo-measured · 198 questions, the full pool · Enso Ultra 194 of 198 correct · snapshot in lib/data/leaderboard.json, top twelve by score</div>
</section>
<!-- A6 · The surface, drawn -->
<section class="slide" id="a6" data-composition-id="a6" data-start="133" data-duration="7">
<div class="eyebrow">Appendix · A6 · the platform, drawn</div>
<h2 class="head small">The whole surface, at one glance.</h2>
<div class="sub" style="margin-top:14px; max-width:1560px; font-size:27px">One rectangle per API family, its area in the ratio of the operations under it: <span data-platform="operations"></span> operations across <span data-platform="products"></span> products and ten layers. Read from the document the API serves about itself; the ten lit on the pages that follow are one product from each layer.</div>
<div class="surface" id="wide" style="flex:1; margin-top:26px"></div>
<div class="foot" style="margin-top:22px">api.hanzo.ai/v1/openapi.json, as the document declared itself on <span data-platform="taken"></span> · the commerce catalog · <span data-catalog="models"></span> models · <span data-catalog="free"></span> free to call · <span data-catalog="providers"></span> providers · 682 public repositories across 6 organizations · the surface, the ten layers and the catalog counts are written at sync from those two snapshots, never typed.</div>
</section>
<!-- P0 · The tour: twelve real operations, in the order somebody calls them -->
<section class="slide" id="p0" data-composition-id="p0" data-start="140" data-duration="7">
<div class="eyebrow">Appendix · the platform · one afternoon</div>
<h2 class="head small"><span id="beats"></span>, and the shop is open.</h2>
<div class="sub" id="story" style="margin-top:12px; font-size:27px"></div>
<div class="beats" id="tour"></div>
<div class="spacer"></div>
<div class="foot">The verb, the path and the sentence beside each line are the document's own; only the line is ours. Every path here is put to the API at sync and dropped if it no longer answers, so this page is as long as the story the platform still serves — six of the twelve it told last month were retired when the API singularised its segments.</div>
</section>
<!-- P1–P10 · one product per layer. The family named here is the only thing
declared: deck/sync.mjs resolves its name, sentence, path, operation
count, page and repository, and stops if the document has dropped it. -->
<section class="slide product" id="p1" data-composition-id="p1" data-start="147" data-duration="7" data-family="agent"></section>
<section class="slide product" id="p2" data-composition-id="p2" data-start="154" data-duration="7" data-family="function"></section>
<section class="slide product" id="p3" data-composition-id="p3" data-start="161" data-duration="7" data-family="s3"></section>
<section class="slide product" id="p4" data-composition-id="p4" data-start="168" data-duration="7" data-family="dns"></section>
<section class="slide product" id="p5" data-composition-id="p5" data-start="175" data-duration="7" data-family="iam"></section>
<section class="slide product" id="p6" data-composition-id="p6" data-start="182" data-duration="7" data-family="integration"></section>
<section class="slide product" id="p7" data-composition-id="p7" data-start="189" data-duration="7" data-family="registry"></section>
<section class="slide product" id="p8" data-composition-id="p8" data-start="196" data-duration="7" data-family="o11y"></section>
<section class="slide product" id="p9" data-composition-id="p9" data-start="203" data-duration="7" data-family="wallet"></section>
<section class="slide product" id="p10" data-composition-id="p10" data-start="210" data-duration="7" data-family="bot"></section>
</div>
<div id="pager">← →</div>
<!--rail--><!-- THE RAIL — one line per slide, down the right edge.
Both decks carry it, so it is written once. What differs between them is how
they MOVE: the friends deck scrolls, the investor deck pages a paused GSAP
timeline. So the rail knows neither. It is given the titles and a way to
pick one, and hands back a way to say which is current.
Lines rather than dots because a deck is a sequence, and a row of dots reads
as a set of choices while a stack of rules reads as a spine. The current one
is longer and solid; a hover lengthens the line and puts the slide's own
name beside it, so a reader finds a slide by what it says rather than by
counting. -->
<style>
.rail {
position: fixed;
right: 14px;
top: 50%;
transform: translateY(-50%);
z-index: 40;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 9px;
}
.rail a {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding: 3px 0;
cursor: pointer;
text-decoration: none;
}
.rail i {
display: block;
width: 16px;
height: 1px;
background: rgba(255, 255, 255, 0.26);
transition: width 0.22s ease, height 0.22s ease, background 0.22s ease;
}
.rail a:hover i,
.rail a:focus-visible i {
width: 26px;
background: rgba(255, 255, 255, 0.62);
}
.rail a.on i {
width: 30px;
height: 2px;
background: #ffffff;
}
/* The name is in the page the whole time and simply not shown, so a reader
tabbing through gets it too and nothing has to be built on hover. */
.rail em {
font-style: normal;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.82);
white-space: nowrap;
opacity: 0;
transform: translateX(6px);
transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none;
text-shadow: 0 1px 10px rgba(0, 0, 0, 0.8);
}
.rail a:hover em,
.rail a:focus-visible em {
opacity: 1;
transform: none;
}
/* A rail needs height to be a rail. Below that it is a stack of lines with no
room between them, so it goes. */
@media (max-height: 560px), (max-width: 520px) {
.rail { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.rail i, .rail em { transition: none; }
}
</style>
<script>
/**
* Rail(titles, pick) -> { at(i) }
*
* `titles` names each slide, `pick(i)` is called when one is chosen, and
* `at(i)` marks which is current. Nothing here knows what a slide is.
*/
window.Rail = function (titles, pick) {
var rail = document.createElement('nav');
rail.className = 'rail';
rail.setAttribute('aria-label', 'Slides');
var links = titles.map(function (title, i) {
var a = document.createElement('a');
a.href = '#';
a.setAttribute('aria-label', title || 'Slide ' + (i + 1));
var em = document.createElement('em');
em.textContent = title || String(i + 1);
var line = document.createElement('i');
a.appendChild(em);
a.appendChild(line);
// stopPropagation, not just preventDefault: the investor deck advances on
// any click on the page, so picking a slide from the rail was also paging
// one forward and landing a reader one past what they asked for.
a.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
pick(i);
});
rail.appendChild(a);
return a;
});
document.body.appendChild(rail);
return {
at: function (n) {
for (var i = 0; i < links.length; i++) links[i].classList.toggle('on', i === n);
},
};
};
/**
* What a slide is called, taken from the slide.
*
* The eyebrow is the deck's own name for the section and both decks have one.
* It reads "02 · The shift", "Why now · our own receipt", "Appendix · A1 ·
* fleet residency" — general to specific, left to right — so the LAST part is
* what tells two slides apart. Taking the first gave seventeen lines all
* reading "Appendix" and two reading "Why now". Failing an eyebrow, the
* heading; failing that, the number.
*/
window.railTitle = function (slide, i) {
// The closing footer is a slide in the sequence but carries no eyebrow, and
// a lone number there reads as a title that failed rather than as the end.
if (slide.tagName === 'FOOTER') return 'End';
var el = slide.querySelector('.eyebrow') || slide.querySelector('h1, h2, .head, .hero');
var t = (el ? el.textContent : '').replace(/\s+/g, ' ').trim();
var parts = t.split(/\s*[·•]\s*/).filter(Boolean);
t = (parts[parts.length - 1] || '').replace(/^\s*\d+\s*$/, '').trim() || parts[0] || '';
return t.length > 30 ? t.slice(0, 29).trim() + '…' : t || String(i + 1);
};
</script>
<!--/rail--><script>
/* THE MARKS, AND THE ONE RULE FOR ADDING ONE.
A company shows its MARK where we hold the real artwork, and its NAME
set in Zen where we do not. There is no third option: a shape drawn to
stand in for a logo is a company's identity invented on its behalf, and
on a slide headed "Every major lab, one route" it is a claim about
somebody else that we made up. Five entries here were exactly that —
Hugging Face and Qwen were both the generic Material circle, byte for
byte, sitting on the wall as the second and third largest labs on it.
Their brand hexes were invented too: Qwen was #615CED against a real
#6950EF, DeepSeek #4D6BFE against #5786FE.
WHERE THE ARTWORK COMES FROM: node_modules/simple-icons, which the site
already vendors for scripts/gen-marks.mjs — take the path and the hex
from the package rather than transcribing either. If simple-icons has
no entry, do not go looking for a lookalike elsewhere: pass the mark as
null and `cells` will set the name instead. Every mark below is one
path painting currentColor except AWS and Techstars, which are three
and which simple-icons has dropped for trademark reasons, so a brand is
one hex beside its artwork and nothing else in the deck knows a colour.
Where a lab's own mark is black — OpenAI, xAI, Techstars — it is white
here, which is what those labs do on a dark ground themselves. */
const MARK = {
openai: ['#ffffff', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.8956zm16.0993 3.8558L12.6 8.3829l2.02-1.1638a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"></path> </svg>'],
anthropic: ['#D97757', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <path d="M17.304 3.541h-3.672l6.696 16.918h3.672l-6.696-16.918zM6.696 3.541 0 20.459h3.744l1.32-3.456h6.624l1.296 3.456h3.744L10.08 3.541H6.696zm.576 10.632 2.136-5.616 2.112 5.616H7.272z"></path> </svg>'],
google: ['#4285F4', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"></path></svg>'],
xai: ['#ffffff', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.253 5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path> </svg>'],
deepseek: ['#5786FE', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45"></path></svg>'],
mistral: ['#FA520F', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"></path></svg>'],
qwen: ['#6950EF', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z"></path></svg>'],
huggingface: ['#FFD21E', '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M12.025 1.13c-5.77 0-10.449 4.647-10.449 10.378 0 1.112.178 2.181.503 3.185.064-.222.203-.444.416-.577a.96.96 0 0 1 .524-.15c.293 0 .584.124.84.284.278.173.48.408.71.694.226.282.458.611.684.951v-.014c.017-.324.106-.622.264-.874s.403-.487.762-.543c.3-.047.596.06.787.203s.31.313.4.467c.15.257.212.468.233.542.01.026.653 1.552 1.657 2.54.616.605 1.01 1.223 1.082 1.912.055.537-.096 1.059-.38 1.572.637.121 1.294.187 1.967.187.657 0 1.298-.063 1.921-.178-.287-.517-.44-1.041-.384-1.581.07-.69.465-1.307 1.081-1.913 1.004-.987 1.647-2.513 1.657-2.539.021-.074.083-.285.233-.542.09-.154.208-.323.4-.467a1.08 1.08 0 0 1 .787-.203c.359.056.604.29.762.543s.247.55.265.874v.015c.225-.34.457-.67.683-.952.23-.286.432-.52.71-.694.257-.16.547-.284.84-.285a.97.97 0 0 1 .524.151c.228.143.373.388.43.625l.006.04a10.3 10.3 0 0 0 .534-3.273c0-5.731-4.678-10.378-10.449-10.378M8.327 6.583a1.5 1.5 0 0 1 .713.174 1.487 1.487 0 0 1 .617 2.013c-.183.343-.762-.214-1.102-.094-.38.134-.532.914-.917.71a1.487 1.487 0 0 1 .69-2.803m7.486 0a1.487 1.487 0 0 1 .689 2.803c-.385.204-.536-.576-.916-.71-.34-.12-.92.437-1.103.094a1.487 1.487 0 0 1 .617-2.013 1.5 1.5 0 0 1 .713-.174m-10.68 1.55a.96.96 0 1 1 0 1.921.96.96 0 0 1 0-1.92m13.838 0a.96.96 0 1 1 0 1.92.96.96 0 0 1 0-1.92M8.489 11.458c.588.01 1.965 1.157 3.572 1.164 1.607-.007 2.984-1.155 3.572-1.164.196-.003.305.12.305.454 0 .886-.424 2.328-1.563 3.202-.22-.756-1.396-1.366-1.63-1.32q-.011.001-.02.006l-.044.026-.01.008-.03.024q-.018.017-.035.036l-.032.04a1 1 0 0 0-.058.09l-.014.025q-.049.088-.11.19a1 1 0 0 1-.083.116 1.2 1.2 0 0 1-.173.18q-.035.029-.075.058a1.3 1.3 0 0 1-.251-.243 1 1 0 0 1-.076-.107c-.124-.193-.177-.363-.337-.444-.034-.016-.104-.008-.2.022q-.094.03-.216.087-.06.028-.125.063l-.13.074q-.067.04-.136.086a3 3 0 0 0-.135.096 3 3 0 0 0-.26.219 2 2 0 0 0-.12.121 2 2 0 0 0-.106.128l-.002.002a2 2 0 0 0-.09.132l-.001.001a1.2 1.2 0 0 0-.105.212q-.013.036-.024.073c-1.139-.875-1.563-2.317-1.563-3.203 0-.334.109-.457.305-.454m.836 10.354c.824-1.19.766-2.082-.365-3.194-1.13-1.112-1.789-2.738-1.789-2.738s-.246-.945-.806-.858-.97 1.499.202 2.362c1.173.864-.233 1.45-.685.64-.45-.812-1.683-2.896-2.322-3.295s-1.089-.175-.938.647 2.822 2.813 2.562 3.244-1.176-.506-1.176-.506-2.866-2.567-3.49-1.898.473 1.23 2.037 2.16c1.564.932 1.686 1.178 1.464 1.53s-3.675-2.511-4-1.297c-.323 1.214 3.524 1.567 3.287 2.405-.238.839-2.71-1.587-3.216-.642-.506.946 3.49 2.056 3.522 2.064 1.29.33 4.568 1.028 5.713-.624m5.349 0c-.824-1.19-.766-2.082.365-3.194 1.13-1.112 1.789-2.738 1.789-2.738s.246-.945.806-.858.97 1.499-.202 2.362c-1.173.864.233 1.45.685.64.451-.812 1.683-2.896 2.322-3.295s1.089-.175.938.647-2.822 2.813-2.562 3.244 1.176-.506 1.176-.506 2.866-2.567 3.49-1.898-.473 1.23-2.037 2.16c-1.564.932-1.686 1.178-1.464 1.53s3.675-2.511 4-1.297c.323 1.214-3.524 1.567-3.287 2.405.238.839 2.71-1.587 3.216-.642.506.946-3.49 2.056-3.522 2.064-1.29.33-4.568 1.028-5.713-.624"></path></svg>'],
meta: ['#0467DF', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"/></svg>'],
perplexity: ['#1FB8CD', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z"/></svg>'],
nvidia: ['#76B900', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z"/></svg>'],
aws: ['#FF9900', '<svg fill="currentColor" viewBox="0 -30 150 150" xmlns="http://www.w3.org/2000/svg"> <path d="M122.714 62.703c5.28-.643 16.924-2.027 18.997.643 2.072 2.621-2.319 13.649-4.293 18.544-.592 1.484.691 2.077 2.023.94 8.684-7.319 10.954-22.6 9.178-24.825-1.777-2.175-17.023-4.055-26.3 2.473-1.431 1.038-1.184 2.423.395 2.225z" fill="currentColor"></path> <path d="M74.852 89.456c20.28 0 43.865-6.38 60.099-18.396 2.664-1.978.345-4.994-2.369-3.758-18.207 7.714-37.993 11.473-56.003 11.473-26.694 0-52.5-7.368-73.42-19.533-1.827-1.088-3.208.791-1.679 2.176 19.343 17.505 44.951 28.038 73.372 28.038z" fill="currentColor"></path> <path d="M42.632 32.835c0 1.83.197 3.313.542 4.401a26.505 26.505 0 001.58 3.56c.246.396.345.792.345 1.138 0 .495-.296.99-.938 1.484l-3.108 2.077c-.444.296-.889.445-1.283.445-.494 0-.987-.248-1.48-.693a15.29 15.29 0 01-1.777-2.324 38.28 38.28 0 01-1.53-2.918c-3.848 4.55-8.684 6.825-14.506 6.825-4.145 0-7.45-1.187-9.868-3.56-2.418-2.374-3.652-5.54-3.652-9.495 0-4.204 1.48-7.616 4.49-10.187 3.01-2.572 7.007-3.857 12.09-3.857 1.677 0 3.404.148 5.23.395 1.825.248 3.7.643 5.674 1.088v-3.61c0-3.758-.79-6.379-2.32-7.912-1.578-1.533-4.243-2.274-8.042-2.274-1.727 0-3.503.197-5.329.642a39.247 39.247 0 00-5.329 1.682 14.14 14.14 0 01-1.727.643c-.345.098-.592.148-.79.148-.69 0-1.036-.494-1.036-1.533V6.577c0-.791.1-1.385.346-1.73.246-.347.69-.693 1.381-1.04 1.727-.89 3.8-1.631 6.217-2.225C20.23.94 22.797.642 25.51.642c5.872 0 10.164 1.336 12.927 4.006 2.714 2.67 4.096 6.726 4.096 12.165v16.022h.099zm-20.033 7.517c1.628 0 3.306-.297 5.082-.89 1.776-.594 3.355-1.682 4.687-3.165.79-.94 1.382-1.978 1.678-3.165.296-1.187.493-2.621.493-4.302v-2.077a41.135 41.135 0 00-4.539-.84 37.099 37.099 0 00-4.638-.298c-3.306 0-5.724.643-7.352 1.978-1.628 1.336-2.418 3.215-2.418 5.687 0 2.324.592 4.055 1.826 5.242 1.184 1.236 2.911 1.83 5.18 1.83zm39.621 5.34c-.888 0-1.48-.148-1.875-.494-.394-.297-.74-.99-1.036-1.929L47.714 5.044c-.296-.99-.444-1.632-.444-1.978 0-.791.394-1.236 1.184-1.236h4.835c.938 0 1.58.148 1.925.494.395.297.69.99.987 1.929l8.29 32.736 7.697-32.736c.246-.99.542-1.632.937-1.929.395-.297 1.085-.494 1.974-.494h3.947c.938 0 1.579.148 1.974.494.395.297.74.99.937 1.929l7.796 33.132L98.29 4.253c.295-.99.64-1.632.986-1.929.395-.297 1.036-.494 1.925-.494h4.588c.79 0 1.234.395 1.234 1.236 0 .247-.049.494-.099.791a7.041 7.041 0 01-.345 1.236L94.688 43.32c-.297.989-.642 1.631-1.037 1.928-.394.297-1.036.495-1.875.495h-4.243c-.938 0-1.58-.149-1.974-.495-.394-.346-.74-.989-.937-1.978l-7.648-31.895-7.599 31.846c-.247.989-.543 1.632-.938 1.978-.394.346-1.085.494-1.973.494H62.22zm63.405 1.336c-2.566 0-5.132-.297-7.599-.89-2.467-.594-4.391-1.237-5.674-1.979-.79-.445-1.332-.94-1.53-1.384a3.503 3.503 0 01-.296-1.385v-2.522c0-1.038.395-1.533 1.135-1.533.296 0 .592.05.888.148.296.1.74.297 1.234.495a26.791 26.791 0 005.428 1.73c1.973.396 3.898.594 5.871.594 3.109 0 5.527-.544 7.204-1.632 1.678-1.088 2.566-2.67 2.566-4.698 0-1.384-.444-2.521-1.332-3.461-.888-.94-2.566-1.78-4.984-2.571l-7.154-2.226c-3.602-1.137-6.267-2.818-7.895-5.044-1.628-2.176-2.467-4.599-2.467-7.17 0-2.077.444-3.907 1.332-5.489.888-1.582 2.072-2.967 3.553-4.055 1.48-1.137 3.157-1.978 5.131-2.571 1.974-.594 4.046-.841 6.217-.841 1.086 0 2.221.05 3.306.198 1.135.148 2.171.346 3.207.544.987.247 1.925.494 2.813.79.888.298 1.579.594 2.072.891.691.396 1.185.791 1.481 1.236.296.396.444.94.444 1.632V8.16c0 1.039-.395 1.583-1.135 1.583-.395 0-1.036-.198-1.875-.594-2.813-1.285-5.971-1.928-9.474-1.928-2.812 0-5.033.445-6.562 1.384-1.53.94-2.319 2.374-2.319 4.402 0 1.384.493 2.57 1.48 3.51s2.812 1.88 5.427 2.72l7.007 2.226c3.553 1.137 6.118 2.72 7.648 4.747 1.53 2.027 2.27 4.351 2.27 6.923 0 2.126-.444 4.055-1.283 5.736-.888 1.681-2.072 3.165-3.602 4.352-1.53 1.236-3.355 2.126-5.477 2.769-2.22.692-4.54 1.039-7.056 1.039z" fill="currentColor"></path> </svg>'],
techstars: ['#ffffff', '<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60" fill="currentColor"><path d="M19.63 38.552l2.097-6.73-4.377-3.358L6.026 39.56h12.16l1.43-.982zm14.028-10.087l-4.377 3.358 2.1 6.753 1.42.982H44.98z"></path><path d="M31.62 24.634L27 24.622l-1.49-4.425-1.503 4.425-4.62.012 3.686 2.85-1.346 4.353L25.5 29.24l3.77 2.595-1.358-4.353zm-6.1 9.942l-.012-.012-7.323 4.995h14.622z"></path><path d="M46.812 30.15v-.982h1.66v-2.134l1.212-.533v2.667h2.097v.982h-2.097v6.668c0 1.382.145 1.7 1.128 1.7.324.005.65-.015.97-.06v1.103a10.84 10.84 0 0 1-1.212.061c-1.66 0-2.158-.74-2.158-2.013V30.2h-1.66zm7.602 4.462v.8c0 1.443.388 3.346 2.34 3.346a2.12 2.12 0 0 0 2.279-2.425h1.297a3.14 3.14 0 0 1-3.637 3.371c-1.794 0-3.637-.715-3.637-3.868V33.3c0-3.007 1.212-4.377 3.637-4.377 3.637 0 3.637 2.57 3.637 5.65h-5.917zm4.692-.982c0-2.304-.376-3.637-2.34-3.637s-2.34 1.382-2.34 3.637h4.692zm8.1-1.48c.06-1.297-.412-2.194-1.83-2.194a1.71 1.71 0 0 0-1.661 1.212 14.15 14.15 0 0 0-.291 3.274c0 1.867 0 4.365 1.916 4.365 1.625 0 1.83-1.03 1.89-2.28h1.212c.156.83-.075 1.685-.628 2.322s-1.366.988-2.2.95c-3.516 0-3.516-2.776-3.516-5.347a13.75 13.75 0 0 1 .303-3.431c.45-1.316 1.745-2.15 3.128-2.013a2.69 2.69 0 0 1 2.898 3.189h-1.212zm3.383 7.42V24.706H71.8v5.735c.526-.892 1.474-1.45 2.5-1.48 2.57 0 2.837 1.916 2.837 3.274V39.5h-1.212v-7.275c0-1.212-.473-2.34-1.916-2.34-1.115 0-2.243.764-2.243 3.08v6.596h-1.212zm11.882.182c-2.243 0-3.128-1.09-3.104-3.286h1.212c0 1.42.327 2.425 1.916 2.425.495.054.987-.124 1.333-.482s.507-.856.437-1.35c0-2.716-4.668-2.013-4.668-5.444 0-1.916 1.322-2.595 3.128-2.595 2.06 0 2.716 1.42 2.716 3.007h-1.212c-.06-1.322-.412-2.013-1.807-2.013a1.48 1.48 0 0 0-1.588 1.503c0 2.595 4.668 1.89 4.668 5.37.053.805-.256 1.59-.842 2.144s-1.39.816-2.19.717zm4.316-9.602v-.982h1.66v-2.134l1.212-.533v2.667h2.097v.982H89.66v6.668c0 1.382.145 1.7 1.128 1.7.324.005.65-.015.97-.06v1.103a10.91 10.91 0 0 1-1.212.061c-1.66 0-2.158-.74-2.158-2.013V30.2h-1.66zM98.8 38.274c-.653.978-1.772 1.54-2.946 1.48a2.75 2.75 0 0 1-2.861-3.067c0-3.637 3.637-3.274 5.76-3.43v-.86c0-1.528-.45-2.425-2.097-2.425a1.78 1.78 0 0 0-2.037 1.794h-1.322c.097-2 1.65-2.776 3.455-2.776 1.043 0 3.225.097 3.225 2.752v5.226c-.01.87.03 1.74.12 2.607H98.8v-1.273zm-.085-4.025c-1.77.06-4.437-.145-4.437 2.425a1.88 1.88 0 0 0 1.867 2.134 2.67 2.67 0 0 0 2.57-2.631v-1.89zm5.165-3.686a3.15 3.15 0 0 1 2.861-1.6v1.212c-.762-.157-1.554.06-2.127.587s-.86 1.295-.77 2.068v6.74h-1.212v-10.4h1.212v1.394zm7.056 9.2c-2.243 0-3.128-1.09-3.104-3.286h1.212c0 1.42.327 2.425 1.916 2.425.495.054.987-.124 1.333-.482s.507-.856.437-1.35c0-2.716-4.668-2.013-4.668-5.444 0-1.916 1.322-2.595 3.128-2.595 2.06 0 2.716 1.42 2.716 3.007h-1.212c-.06-1.322-.412-2.013-1.807-2.013a1.48 1.48 0 0 0-1.588 1.503c0 2.595 4.668 1.89 4.668 5.37.053.805-.256 1.59-.842 2.144s-1.39.816-2.19.717z"></path></svg>'],
gcloud: ['#4285F4', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0-3.875 2.551-3.922 8.11-.247 10.941l.006-.007-.007.03a6.717 6.717 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.365 9.365 0 0 0-2.821-4.552l-.043.043.006-.05A9.344 9.344 0 0 0 12.19 2.38zm-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.186 5.186 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.59 2.59 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.747 6.747 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.186 5.186 0 0 1 3.67-1.69z"/></svg>'],
azure: ['#0078D4', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.379 23.343a1.62 1.62 0 0 0 1.536-2.14v.002L17.35 1.76A1.62 1.62 0 0 0 15.816.657H8.184A1.62 1.62 0 0 0 6.65 1.76L.086 21.204a1.62 1.62 0 0 0 1.536 2.139h4.741a1.62 1.62 0 0 0 1.535-1.103l.977-2.892 4.947 3.675c.28.208.618.32.966.32m-3.084-12.531 3.624 10.739a.54.54 0 0 1-.51.713v-.001h-.03a.54.54 0 0 1-.322-.106l-9.287-6.9h4.853m6.313 7.006c.116-.326.13-.694.007-1.058L9.79 1.76a1.722 1.722 0 0 0-.007-.02h6.034a.54.54 0 0 1 .512.366l6.562 19.445a.54.54 0 0 1-.338.684"/></svg>'],
digitalocean: ['#0080FF', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z"/></svg>'],
cloudflare: ['#F38020', '<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727"/></svg>'],
};
/* A cell wears the mark or the name, never both and never neither — the
name would otherwise print twice, once as the wordmark and again as the
label under it. */
const cells = (list) => list.map(([mark, name, note]) =>
`<div class="cell">${MARK[mark] ? logo(mark) : `<div class="word">${name}</div>`}` +
`<div>${MARK[mark] ? `<div class="nm">${name}</div>` : ""}${note ? `<div class="ct">${note}</div>` : ""}</div></div>`).join("");
/* Refuses rather than renders nothing: a badge or a deploy target whose
mark went missing would otherwise leave a labelled hole on the slide. */
const logo = (n, cls) => {
const m = MARK[n];
if (!m) throw new Error(`no mark for ${n} — a company without one wears its name, which is cells()'s job`);
return `<span class="logo${cls ? ' ' + cls : ''}" style="color:${m[0]}" role="img" aria-label="${n}">${m[1]}</span>`;
};
/* THE HEAD-TO-HEAD, drawn from the scores so a bar can never disagree
with its label. Hanzo-measured, 198 questions, the full pool. */
const GPQA = [["Enso Ultra","Hanzo",98.0,true],["Enso Pro","Hanzo",96.0,true],["Gemini 3.1 Pro","Google",94.3,false],["GPT-5.5","OpenAI",93.6,false],["GPT-5.2 Pro","OpenAI",93.2,false],["Enso Flash","Hanzo",92.9,true]];
const FLOOR = 85, CEIL = 100;
document.getElementById("gpqa").innerHTML = GPQA.map(([n, v, s, ours]) =>
`<div class="bar${ours ? " ours" : ""}" data-model="${n}"><div class="nm">${n}<small>${v}</small></div>` +
`<div class="stack"><div class="track"><div class="fill" style="width:${((s - FLOOR) / (CEIL - FLOOR) * 100).toFixed(1)}%"></div></div>` +
`<div class="track thin"><div class="fill"></div></div></div>` +
`<div class="v" data-count="${s}" data-decimals="1">0</div></div>`).join("");
/* THE GAP ON A4, read off the table beside it rather than from a second
copy of the same five numbers: the first measured row against the
published claim, on one axis, so the distance left to close is a length. */
const pct = (el) => Number(el.textContent.replace(/[^0-9.]/g, ""));
const measured = [...document.querySelectorAll("#a4 td.ours")].slice(0, 2).map(pct);
const claimed = [...document.querySelectorAll("#a4 td.theirs")].map(pct);
document.getElementById("gap").innerHTML = ["single-hop", "multi-hop"]
.flatMap((k, i) => [[`${k} · measured`, measured[i], true], [`${k} · published`, claimed[i], false]])
.map(([n, v, ours]) => `<div class="bar${ours ? " ours" : ""}"><div class="nm">${n}</div>` +
`<div class="track"><div class="fill" style="width:${v}%"></div></div><div class="v">${v}%</div></div>`).join("");
/* The full table, top twelve from the leaderboard snapshot. */
const TABLE = [["enso-ultra","Hanzo",98.0],["enso","Hanzo",96.0],["gemini-3.1-pro","Google",94.3],["gpt-5.5","OpenAI",93.6],["gpt-5.2-pro","OpenAI",93.2],["enso-flash","Hanzo",92.9],["gpt-5.6-sol","OpenAI",92.9],["gpt-5.2","OpenAI",91.7],["gpt-5.4","OpenAI",91.7],["kimi-k2.6","Moonshot",89.1],["qwen3.5-397b-a17b","Alibaba",88.4],["gpt-5.6-terra","OpenAI",87.9]];
const row = (r, i) => `<tr><td class="mult" style="text-align:left;color:var(--dim)">${i + 1}</td><td>${r[0]}</td><td style="color:var(--dim)">${r[1]}</td><td class="${r[1] === "Hanzo" ? "ours" : "theirs"}" style="text-align:right;padding-right:0">${r[2].toFixed(1)}</td></tr>`;
const th = `<tr><th>#</th><th>model</th><th>lab</th><th style="text-align:right">GPQA-Diamond</th></tr>`;
document.getElementById("gpqaA").innerHTML = th + TABLE.slice(0, 6).map((r, i) => row(r, i)).join("");
document.getElementById("gpqaB").innerHTML = th + TABLE.slice(6).map((r, i) => row(r, i + 6)).join("");
/* THE CATALOG IS READ, NOT TYPED — deck/assets/models.json is written by
deck/sync.mjs out of lib/data/pricing.json, the same snapshot every
price on the site comes from. The wall, the counts and the price beside
each score all come from it, so none of them can drift from the others. */
function catalog(m) {
document.getElementById("wall").innerHTML = cells(m.labs.map((l) => [l.mark, l.name, `${l.models} models`]));
document.getElementById("clouds").innerHTML = cells([
["aws", "AWS"], ["gcloud", "Google Cloud"], ["azure", "Azure"],
["digitalocean", "DigitalOcean"], ["cloudflare", "Cloudflare"]]);
document.getElementById("badges").innerHTML =
`<div class="badge">${logo("nvidia", "big")}<div><div class="nm">NVIDIA Inception</div><div class="ct">Member · the programme for AI companies</div></div></div>` +
`<div class="badge">${logo("techstars", "big")}<div><div class="nm">Techstars</div><div class="ct">Kansas City · inaugural cohort · 2017</div></div></div>`;
document.querySelectorAll("[data-catalog]").forEach((el) => {
const v = m[el.dataset.catalog];
if (el.dataset.count !== undefined) el.dataset.count = v; else el.textContent = v.toLocaleString("en-US");
});
// The price rides the same row as the score, as a second bar: log to
// $200, because the field runs $4 to $168 and a linear axis would put
// five of the six models on one tick. A long white bar is a win on the
// score; a short white bar is a win on the price. One fill rule, both.
document.querySelectorAll("#gpqa .bar").forEach((b) => {
const p = m.price[b.dataset.model];
b.querySelector(".thin .fill").style.width = (Math.log(p) / Math.log(200) * 100).toFixed(1) + "%";
b.insertAdjacentHTML("beforeend", `<div class="pr">$${p}</div>`);
});
}
/* THE SURFACE, SQUARIFIED. Rectangles whose areas are in the ratio of
their values, laid out in rows chosen to keep each one as square as it
can be (Bruls, Huizing & van Wijk). The input arrives ranked, so this is
a fold over a sorted list and nothing here sorts anything. */
function tile(items, x, y, w, h, out = []) {
if (!items.length) return out;
const total = items.reduce((n, i) => n + i.v, 0);
const wide = w >= h, side = wide ? h : w, span = wide ? w : h;
const row = [];
let sum = 0, best = Infinity, k = 0;
for (; k < items.length; k++) {
const s = sum + items[k].v, len = (s / total) * span;
const worst = Math.max(...[...row, items[k]].map((i) => {
const t = (i.v / s) * side;
return Math.max(len / t, t / len);
}));
if (row.length && worst > best) break;
row.push(items[k]); sum = s; best = worst;
}
const len = (sum / total) * span;
let off = 0;
for (const i of row) {
const t = (i.v / sum) * side;
out.push(wide ? { ...i, x, y: y + off, w: len, h: t } : { ...i, x: x + off, y, w: t, h: len });
off += t;
}
return wide ? tile(items.slice(k), x + len, y, w - len, h, out)
: tile(items.slice(k), x, y + len, w, h - len, out);
}
/* The map, drawn into whatever room it is given. Measured with clientWidth
rather than a rect, because the stage is transform-scaled and a rect
reports painted pixels; the layout is in stage pixels. A name is printed
only where the whole of it fits — a family clipped mid-word reads as a
rendering fault rather than as a small family. */
function surface(el, ranked, lit) {
const W = el.clientWidth, H = el.clientHeight;
if (!W || !H) return;
el.innerHTML = tile(ranked.map(([k, v]) => ({ k, v })), 0, 0, W, H).map((a) => {
const on = a.k === lit;
const named = a.h >= (on ? 22 : 30) && a.w - (on ? 12 : 20) >= a.k.length * (on ? 7.3 : 8.6);
return `<div class="area${on ? " ours" : ""}" style="left:${a.x}px;top:${a.y}px;width:${a.w - 3}px;height:${a.h - 3}px">` +
(named ? `<b>${a.k}</b>${a.h >= 54 ? `<i>${a.v}</i>` : ""}` : "") + "</div>";
}).join("");
}
/* THE PLATFORM, READ. deck/assets/platform.json is written by
deck/sync.mjs out of lib/data/{catalog,families}.json — the two
snapshots scripts/sync-catalog.mjs writes from the served document
behind its own ratchets. One probe for the estate, one committed
answer, and this is its third reader after the mega-menu and the skill
manifest, so the deck cannot disagree with the site about the size of
the platform. Every product page below is built from one record. */
function platform(p) {
document.querySelectorAll("[data-platform]").forEach((el) => {
const v = p[el.dataset.platform];
if (el.dataset.count !== undefined) el.dataset.count = v; else el.textContent = v.toLocaleString("en-US");
});
// s7 — how many products the catalog sells in each of the ten layers.
const most = Math.max(...p.layers.map((l) => l.products));
for (const row of document.querySelectorAll("#s9 .layer")) {
const l = p.layers.find((x) => x.label === row.querySelector(".nm").textContent);
if (!l) throw new Error("no layer called " + row.querySelector(".nm").textContent);
row.insertAdjacentHTML("beforeend",
`<span class="track"><span class="fill" style="width:${(l.products / most * 100).toFixed(1)}%"></span></span><span class="ct">${l.products}</span>`);
}
// A6 — the whole surface, nothing lit.
surface(document.getElementById("wide"), p.surface, null);
// P0 — the tour the hero tells, twelve operations the document serves.
// Spelled, and counted from what survived the check rather than typed:
// the tour is exactly as long as the operations the API still serves.
const TALLY = ["No calls", "One call", "Two calls", "Three calls", "Four calls", "Five calls",
"Six calls", "Seven calls", "Eight calls", "Nine calls", "Ten calls", "Eleven calls", "Twelve calls"];
document.getElementById("beats").textContent = TALLY[p.tour.beats.length] ?? `${p.tour.beats.length} calls`;
document.getElementById("story").textContent = p.tour.story;
document.getElementById("tour").innerHTML = p.tour.beats.map((b, i) =>
`<div class="beat"><span class="n">${String(i + 1).padStart(2, "0")}</span><span class="m">${b.method}</span>` +
`<span class="p">${b.path}</span><span class="d">${b.line} <span style="color:var(--dim)">${b.summary}</span></span></div>`).join("");
// P1–P10 — one product from each layer, the same map with its own cell
// lit. The sections are collected once and matched on their own
// dataset: a selector built from a template literal is a string the
// bundler's CSS parser has to read, and it cannot.
const pages = [...document.querySelectorAll("[data-family]")];
p.core.forEach((c) => {
const el = pages.find((s) => s.dataset.family === c.family);
const n = p.layers.findIndex((l) => l.label === c.layer) + 1;
el.innerHTML =
`<div class="eyebrow">Appendix · P${n} · ${c.layer}</div>` +
`<h2 class="head small">${c.name}</h2>` +
`<div class="split" style="grid-template-columns: 760px minmax(0,1fr); column-gap: 60px">` +
`<div style="display:flex; flex-direction:column; min-height:0; align-self:stretch">` +
`<div class="sub">${c.summary}</div>` +
`<div class="calls" style="margin-top:36px">` +
`<div class="call"><b>$</b> curl https://${p.host}${c.path}</div>` +
`<div class="call"><b>&nbsp;&nbsp;</b> -H "Authorization: Bearer $HANZO_KEY"</div>` +
`</div>` +
`<div class="band" style="margin-top:44px">` +
`<div class="stat"><div class="num" data-count="${c.operations}">0</div><div class="lbl">Operations under <code>${c.path}</code></div></div>` +
`</div>` +
`<div class="pills" style="margin-top:36px">` +
`<div class="pill">Layer ${String(n).padStart(2, "0")} · ${c.layer}</div>` +
`<div class="pill ours"><code>hanzo.ai${c.page}</code></div>` +
(c.repo ? `<div class="pill"><code>github.com/${c.repo}</code></div>` : "") +
`</div>` +
`<div class="spacer"></div>` +
`<div class="foot">${c.operations} of ${p.operations.toLocaleString("en-US")} operations · the lit rectangle is this family, in the surface drawn whole on A6</div>` +
`</div>` +
`<div class="surface"></div>` +
`</div>`;
surface(el.querySelector(".surface"), p.surface, c.family);
});
}
/* THE THIRTEEN ROOMS, read off the tree that ships them. Every name and
every line is the room's own `metadata` — deck/sync.mjs resolves them
out of app/(app) and refuses a room that describes itself with nothing,
because a tile with copy invented for it is the one thing this argument
cannot afford. `dev` is drawn hollow: it is the room that is the code,
and it is one of thirteen. */
function rooms(list) {
document.getElementById("rooms").innerHTML = list.map((r) =>
`<div class="room${r.slug === "dev" ? " code" : ""}"><div class="nm">${r.name}</div>` +
`<div class="of">${r.line}</div><div class="rt">/${r.slug}</div></div>`).join("");
document.getElementById("rooms-foot").textContent =
`${list.length} rooms under app/(app), each named and described by its own page metadata. ` +
`${list.length - 1} of them are the company; the hollow one is the code.`;
}
/* THE ESTATE, and the reason the headline is a floor. A commit carries an
author name and an author mailbox, and here they disagree — a person's
name at a service address, or the reverse — on a sixth of everything.
Nothing in a git log settles which, so both classifiers run and only
their agreement is claimed: the segment on the left is the number, the
middle segment is the doubt, and it is drawn rather than dropped. */
function estate(e) {
const most = e.contested[0];
document.getElementById("who").innerHTML = [
["agent", e.share.agent, "written by an agent"],
["split", e.share.disputed, "disputed"],
["person", e.share.person, "written by a person"],
].map(([k, pct, label]) =>
`<div class="${k}" style="flex:0 0 ${pct}%"><b>${pct}%</b><span>${label}</span></div>`).join("");
document.getElementById("estate").innerHTML =
`<div class="stat"><div class="num" data-count="${e.commits}">0</div><div class="lbl">Commits in ${e.window}</div></div>` +
`<div class="stat"><div class="num" data-count="${e.repos}">0</div><div class="lbl">Repositories that took one</div></div>` +
`<div class="stat field"><div class="num" data-count="${e.identities.all}">0</div><div class="lbl">Author identities</div></div>`;
const ceiling = (((e.agent + e.disputed) / e.commits) * 100).toFixed(1);
document.getElementById("estate-foot").textContent =
`${e.source}, measured ${e.measured} — ${e.orgs.map((o) => `${o.name} ${o.commits.toLocaleString("en-US")}`).join(" · ")}. ` +
`A commit count is a crude proxy for work and the split is cruder: git records whatever name and mailbox were configured, and the two disagree on ` +
`${e.disputed.toLocaleString("en-US")} commits — ${most.commits.toLocaleString("en-US")} of them as ${most.who} alone. Those are counted as neither, ` +
`which is why ${e.share.agent}% is a floor and not a figure: read the middle segment as agents and it is ${ceiling}%. ` +
`An identity is not a person — one of us commits under three names — and a checkout that is behind under-reports, while none can over-report.`;
}
/* COUNTERS tick up to the number in their own attribute — never to a
typed intermediate. Formatting is one function so 2,441 and $3.7M and
79,614× all come out of the same place. */
const fmt = (v, el) => {
const d = Number(el.dataset.decimals ?? 0);
const s = d ? v.toFixed(d) : Math.round(v).toLocaleString("en-US");
return (el.dataset.prefix ?? "") + s + (el.dataset.suffix ?? "");
};
/* THE BENCH ROWS ARE READ, NOT TYPED. deck/assets/bench.json is a copy of
lib/data/bench.json made by deck/sync.mjs — the same file the landing
page's "Measured" band reads — so the site and the deck cannot disagree
about a number. Everything below that shows one is rendered from it. */
const num = (t) => Number(String(t).replace(/[^0-9.]/g, ""));
const ms = (t) => { const m = /([\d.]+)\s*(ms|s)/.exec(t); return m ? Number(m[1]) * (m[2] === "s" ? 1000 : 1) : NaN; };
const cell = (t, cls) => `<td class="${cls}">${t}</td>`;
const sentence = (t) => t.charAt(0).toUpperCase() + t.slice(1);
function render(bench) {
const by = Object.fromEntries(bench.rows.map((r) => [r.id, r]));
const R = by.residency, C = by.cold, K = by.call;
// s8 — the receipt, with each row's detail beneath it.
const th = `<tr><th>&nbsp;</th><th class="ours">Hanzo, measured</th><th class="theirs">Published</th><th style="text-align:right">&nbsp;</th></tr>`;
document.getElementById("proof").innerHTML = th + [R, C, K].map((r) =>
`<tr><td class="grp" colspan="4">${r.claim} · <span style="text-transform:none">${r.command}</span></td></tr>` +
`<tr>${cell(r.claim, "")}${cell(r.ours, "ours")}${cell(r.published, "theirs")}${cell(r.multiple, "mult")}</tr>` +
`<tr><td colspan="4" style="font-size:17px;color:var(--dim);font-family:var(--mono);letter-spacing:0.04em">${r.detail}</td></tr>`).join("");
document.getElementById("sq-theirs").textContent = R.published;
document.getElementById("sq-ours").textContent = R.ours;
// The squares: sides in the ratio of the square roots of the bytes.
const ratio = num(R.multiple);
const side = 430 / Math.sqrt(ratio);
const o = document.querySelector(".sq.ours"); o.style.width = o.style.height = Math.max(6, side).toFixed(1) + "px";
// Cold start: ours, then every published figure the file states, over the slowest.
const pub = C.published.split(" · ").map((t) => ({ label: t, ms: ms(t.replace(/^[<~]/, "").replace(/^(\d+)–(\d+)/, "$2")) }));
const slowest = Math.max(ms(C.ours), ...pub.map((p) => p.ms));
document.getElementById("starts").innerHTML =
`<div class="start ours"><span>Hanzo, cold</span><div class="track"><div class="fill" style="width:${(ms(C.ours) / slowest * 100).toFixed(1)}%"></div></div><span class="v">${C.ours}</span></div>` +
pub.map((p) => `<div class="start"><span>published</span><div class="track"><div class="fill" style="width:${(p.ms / slowest * 100).toFixed(1)}%"></div></div><span class="v">${p.label}</span></div>`).join("");
// s9 — the ledger. Every figure is parsed from the call row.
const credit = num(K.published), under = Math.round((1 - credit / 10 / credit) * 100), ret = /\$([\d.]+)M/.exec(K.detail)?.[1] ?? "";
const compute = /≈?\s*(\d+)\s*ms of compute/.exec(K.detail)?.[1] ?? "";
// The same three figures on a log axis, seven decades of it. On a linear
// one the cost and the price land on the same pixel and the slide says
// nothing; here the distance IS the multiple the ledger states below.
const mark = (v) => ((Math.log10(v) + 7) / 7 * 100).toFixed(2);
document.getElementById("scale").innerHTML = [
[K.ours, num(K.ours), "one call, marginal", true],
[`$${(credit / 10).toFixed(3)}`, credit / 10, "our price", true],
[`$${credit}`, credit, "their credit", false],
].map(([t, v, k, ours]) =>
`<div class="tick${ours ? " ours" : ""}" style="left:${mark(v)}%"><b>${t}</b><span>${k}</span><i></i></div>`).join("") +
`<div class="reach" style="left:${mark(num(K.ours))}%; right:${(100 - mark(credit))}%; top:132px"><b>${K.multiple}</b></div>`;
document.getElementById("ledger").innerHTML =
`<div class="row ours"><span class="k">one call</span><span class="a">≈ ${compute} ms of compute</span><span class="b">${K.ours} marginal</span></div>` +
`<div class="row"><span class="k">their credit</span><span class="a">$${credit}</span><span class="b"><span data-count="${num(K.multiple)}" data-suffix="×">0</span> marginal</span></div>` +
`<div class="row ours"><span class="k">our price</span><span class="a">$${(credit / 10).toFixed(3)}</span><span class="b"><span data-count="${under}" data-suffix="% under theirs">0</span></span></div>` +
`<div class="row sum"><span class="k">1B calls a month</span><span class="a">returns</span><span class="b"><span data-count="${ret}" data-decimals="1" data-prefix="$" data-suffix="M">0</span></span></div>`;
document.getElementById("ledger-foot").textContent = "The axis above runs seven decades, $0.0000001 to $1, and every faint rule on it is ten times the one before. " + sentence(K.detail.split(" · ").slice(1).join(" · ")) + ". Model tokens are excluded and dominate: a turn that thinks costs 100–1,000× the infrastructure under it, and is billed beside this, never inside it.";
// A1–A3 — the row, its detail split into rows where it states pairs, the command.
for (const t of document.querySelectorAll("table[data-row]")) {
const r = by[t.dataset.row];
const subs = r.detail.split(" · ").map((d) => {
// A LABEL IS WHAT PRECEDES A COLON. `.*?` matched the empty string
// ahead of an optional colon, so every detail row fell back to the
// claim and the sheet printed "A dormant agent" three times.
const m = /^(?:(.+?):\s*)?(.+?)\s+vs\s+(.+)$/.exec(d);
return m ? `<tr>${cell(m[1] ?? "", "")}${cell(m[2], "ours")}${cell(m[3], "theirs")}${cell("", "mult")}</tr>` : `<tr><td colspan="4" style="font-size:19px;color:var(--mid)">${d}</td></tr>`;
}).join("");
t.innerHTML = th + `<tr>${cell(r.claim, "")}${cell(r.ours, "ours")}${cell(r.published, "theirs")}${cell(r.multiple, "mult")}</tr>` + subs;
document.getElementById(t.id.replace("-table", "-method")).innerHTML = `<b>method</b> ${bench.machine} · <b>${r.command}</b> in cloud/bench · ${bench.source}`;
}
}
/* One timeline for the deck, at absolute time. Everything a slide does
is finished by +1.4s, so the still frame at +3.5s is the finished
slide — which is what an export and a reduced-motion viewer see. */
const HOLD = 7;
const slides = [...document.querySelectorAll(".slide")];
const tl = gsap.timeline({ paused: true });
async function main() {
render(await (await fetch("./assets/bench.json")).json());
catalog(await (await fetch("./assets/models.json")).json());
platform(await (await fetch("./assets/platform.json")).json());
rooms(await (await fetch("./assets/rooms.json")).json());
estate(await (await fetch("./assets/estate.json")).json());
slides.forEach((slide, i) => {
const at = i * HOLD;
tl.set(slide, { opacity: 1 }, at);
const parts = slide.querySelectorAll(".eyebrow, .head, .sub, .rule, .band .stat, .layer, .line, .call, .foot, .method, tr, .pill, .logos > *, .wall .cell, .badge, .tile, .bar, .axis, .row, .sqlbl, .start, .beat, .tick, .room, .who");
tl.fromTo(parts, { opacity: 0, y: 22 }, { opacity: 1, y: 0, duration: 0.55, stagger: 0.028, ease: "power2.out" }, at);
// The map comes up as a field rather than as a queue: the stagger is a
// total, so a hundred and eighty rectangles finish in the same breath
// as three, and every slide is settled well before the still is taken.
const areas = slide.querySelectorAll(".area");
if (areas.length) tl.fromTo(areas, { opacity: 0, scale: 0.88 }, { opacity: 1, scale: 1, duration: 0.45, stagger: { amount: 0.65 }, ease: "power2.out" }, at + 0.2);
// A frame rises a little further, a little later, like a window opening.
const frames = slide.querySelectorAll(".frame");
if (frames.length) tl.fromTo(frames, { opacity: 0, y: 40, scale: 0.985 }, { opacity: 1, y: 0, scale: 1, duration: 0.8, ease: "power3.out" }, at + 0.25);
// Bills stand up from the desk.
const bills = slide.querySelectorAll(".bill");
if (bills.length) tl.fromTo(bills, { opacity: 0, scaleY: 0.6 }, { opacity: 1, scaleY: 1, duration: 0.5, stagger: 0.07, ease: "power2.out" }, at + 0.3);
// Bars grow from the left; the two squares scale from their corner.
const fills = slide.querySelectorAll(".bar .fill, .start .fill, .layer .fill");
if (fills.length) tl.fromTo(fills, { scaleX: 0 }, { scaleX: 1, duration: 0.9, stagger: 0.08, ease: "power2.out" }, at + 0.3);
const sq = slide.querySelectorAll(".sq");
if (sq.length) tl.fromTo(sq, { scale: 0 }, { scale: 1, duration: 0.8, stagger: 0.15, ease: "power2.out" }, at + 0.35);
// Logos settle in with a soft float, once.
const logos = slide.querySelectorAll(".logo");
if (logos.length) tl.fromTo(logos, { y: 10 }, { y: 0, duration: 0.9, stagger: 0.05, ease: "sine.out" }, at + 0.2);
// Counters.
// THE FINAL NUMBER IS IN THE DOM BEFORE ANYTHING MOVES. A seek suppresses
// callbacks — GSAP's pause(time) and a renderer's scrub both do — so a
// counter that only wrote on update stayed at zero in every still. The
// text is set to its value here; the tween merely counts over it while
// the deck is actually playing.
slide.querySelectorAll("[data-count]").forEach((el, j) => {
const target = Number(el.dataset.count);
el.textContent = fmt(target, el);
const o = { v: 0 };
tl.to(o, { v: target, duration: 0.9, ease: "power2.out", onUpdate: () => { el.textContent = fmt(o.v, el); }, onComplete: () => { el.textContent = fmt(target, el); } }, at + 0.35 + j * 0.05);
});
tl.to(slide, { opacity: 0, duration: 0.4 }, at + HOLD - 0.4);
});
window.__timelines = window.__timelines || {};
window.__timelines["deck"] = tl;
const stage = document.getElementById("deck");
const fit = () => { const k = Math.min(innerWidth / 1920, innerHeight / 1080); stage.style.transform = "translate(" + (innerWidth - 1920 * k) / 2 + "px," + (innerHeight - 1080 * k) / 2 + "px) scale(" + k + ")"; };
addEventListener("resize", fit); fit();
const pager = document.getElementById("pager");
setTimeout(function () {
if (window.__hyperframes) { pager.classList.add("off"); return; }
var at = 0;
var rail = window.Rail([].map.call(slides, window.railTitle), function (i) { at = i; show(); });
var show = function () { tl.seek(at * HOLD + HOLD / 2, false); tl.pause(); pager.textContent = (at + 1) + " / " + slides.length + " · ← →"; rail.at(at); };
var go = function (d) { at = Math.min(slides.length - 1, Math.max(0, at + d)); show(); };
addEventListener("keydown", function (e) {
if (["ArrowRight", "ArrowDown", " ", "PageDown", "Enter"].indexOf(e.key) >= 0) { e.preventDefault(); go(1); }
else if (["ArrowLeft", "ArrowUp", "PageUp", "Backspace"].indexOf(e.key) >= 0) { e.preventDefault(); go(-1); }
else if (e.key === "Home") { at = 0; show(); } else if (e.key === "End") { at = slides.length - 1; show(); }
});
addEventListener("click", function (e) { go(e.clientX < innerWidth / 4 ? -1 : 1); });
var x0 = null;
addEventListener("touchstart", function (e) { x0 = e.touches[0].clientX; }, { passive: true });
addEventListener("touchend", function (e) { if (x0 === null) return; var dx = e.changedTouches[0].clientX - x0; if (Math.abs(dx) > 40) go(dx < 0 ? 1 : -1); x0 = null; });
show();
}, 60);
}
main();
</script>
<script type="application/hyperframes-slideshow+json">
{
"slides": [
{ "sceneId": "s1", "notes": "Hanzo AI, the open AI cloud. Two claims, both measured: the field's best-scoring model, and the lightest, cheapest place to run an agent. NVIDIA Inception member, Techstars 2017, 682 open repos. This is hanzo.ai as it is served today." },
{ "sceneId": "s2", "notes": "Why now. Building software got easy; running the company did not. The bottleneck moved to operations — the inbox, the board, the calendar, the contacts, the money — and that is what Hanzo automates. The product's own shape is the argument: thirteen rooms ship in the app and exactly one of them, the hollow tile, is the code." },
{ "sceneId": "s3", "notes": "And the first half of that is our own receipt, not a market claim. Across every repository this company keeps, 188 of them with a commit in the last thirty days, 13,259 commits: at least 36.7% were written by an agent. It is a floor rather than a figure — a sixth of the commits have an author name and a mailbox that disagree, and those are counted as neither." },
{ "sceneId": "s4", "notes": "The problem. Seven contracts before a line of product. Everyone assembled their stack, so they forward somebody else's bill — a dormant agent billed as a megabyte, a credit priced against nothing." },
{ "sceneId": "s5", "notes": "We own every layer. One host, one credential, 2,441 operations across 79 products — and one shared context across every room an organization keeps." },
{ "sceneId": "s6", "notes": "The product. One workspace: the agents an organization runs, what each did while nobody was watching, and one place to answer. The shipped interface." },
{ "sceneId": "s7", "notes": "Enso leads on score. GPQA-Diamond, the full 198-question pool, Hanzo-measured: Enso Ultra 98.0, Enso Pro 96.0, then Gemini 3.1 Pro at 94.3 and GPT-5.5 at 93.6. We are not cheapest at every rung — Gemini is $12 against Ultra's $25 — and the claim is the two ends: best score outright, and Enso Flash within half a point of GPT-5.2 Pro at a forty-second of its price." },
{ "sceneId": "s8", "notes": "536 models from 57 providers behind one endpoint and one key — every major lab, ranked by how many of each we carry. 74 free to call. Enso is ours; Zen is open weights with the Zoo Labs Foundation." },
{ "sceneId": "s9", "notes": "Ten layers, one contract, and your laptop is the cluster: one line installs the CLI, the next boots a real Kubernetes control plane in a microVM — seven seconds from cold, the microVM in a third of a second." },
{ "sceneId": "s10", "notes": "The receipt. A dormant agent is 477 bytes against a published megabyte — the two squares are to scale. A cold container in 149.6 milliseconds against every published number. Every row re-runs from a script." },
{ "sceneId": "s11", "notes": "A call is fifty-one milliseconds of compute, six ten-millionths of a dollar. The category sells it as a five cent credit — 79,614 times cost. At a tenth of their price, a billion calls a month clears 3.7 million." },
{ "sceneId": "s12", "notes": "Hanzo AI since 2016, Techstars Kansas City's inaugural cohort in 2017, NVIDIA Inception. Over a billion dollars of client revenue through Sensei, a hundred products to market, 682 public repositories." },
{ "sceneId": "s13", "notes": "The model. hanzo.chat is free with no account; local AI and the Zen weights are free forever. The app is $20 a month per organization or personal account. The API is model cost plus five percent, no minimum." },
{ "sceneId": "s14", "notes": "The round is open on Republic to the same people who use it. Two lines to try it first." },
{ "sceneId": "a1", "notes": "Appendix. Fleet residency, cloud/bench/fleet, M-series laptop, September 2026." },
{ "sceneId": "a2", "notes": "Sandbox cold start. Two primitives, compared per workload." },
{ "sceneId": "a3", "notes": "Unit economics. Tokens excluded, and they dominate." },
{ "sceneId": "a4", "notes": "Recall on LoCoMo — the row we are working on, not a win. More vector search does not close the gap." },
{ "sceneId": "a5", "notes": "GPQA-Diamond, the full top twelve." },
{ "sceneId": "a6", "notes": "The whole surface, drawn: one rectangle per API family, its area in the ratio of the operations under it. 2,441 operations across 79 products and ten layers, as the document declared itself when the snapshot was taken. The ten lit on the pages after this are one product from each layer." },
{ "sceneId": "p0", "notes": "One org, one afternoon, in the operations the API still serves — from listing the models to reading what the day cost. Every path here was put to the API at sync; six of the twelve this told last month were dropped because they no longer answer, which is the check working rather than the story shrinking." },
{ "sceneId": "p1", "notes": "AI. Agents: define them, run them, keep every run. Thirty-two operations under /v1/agents, and the run history is the product — an agent nobody watched still has to be answerable for what it did." },
{ "sceneId": "p2", "notes": "Compute. Functions: publish serverless code, call it over HTTP, and see every run and what it cost. Eleven operations under /v1/functions, on the same sandbox primitive the cold-start numbers were measured on." },
{ "sceneId": "p3", "notes": "Data. Object storage: buckets, the files in them, and signed URLs for upload and download. Eight operations under /v1/s3, S3-compatible, so existing clients need a new endpoint and nothing else." },
{ "sceneId": "p4", "notes": "Network. DNS: the zones and records behind every name pointed at Hanzo. Five operations under /v1/dns — small on purpose, because a name is one record and the interesting part is that it is in the same account as everything else." },
{ "sceneId": "p5", "notes": "Security. IAM: users, organizations, applications and the OIDC and OAuth2 endpoints. Two hundred and three operations under /v1/iam — the second largest surface here, and the one nobody else on this slide would build themselves." },
{ "sceneId": "p6", "notes": "Dev. Integrations: how an org connects a third-party account and revokes it. Forty operations under /v1/integrations, and the revoke half is the half that matters at audit." },
{ "sceneId": "p7", "notes": "Infrastructure. Registry: push images, pull them back, and see what you store. Six operations under /v1/registry, in the same account as the cluster that will run them." },
{ "sceneId": "p8", "notes": "Observe. Traces: ship logs, metrics and traces in, query them, chart them. Three hundred and sixty-five operations under /v1/o11y — the largest family on the platform, which is what it costs to own observability rather than forward a bill for it." },
{ "sceneId": "p9", "notes": "Web3. Wallets: create them, rotate their keys, sign with them. Eight operations under /v1/wallets, key custody in the same account as the agents that will spend from it." },
{ "sceneId": "p10", "notes": "Apps. Bot: your own machines, connected and ready to take a command. Nine operations under /v1/bot — the last of the ten layers, and the one that puts an agent in a channel where people already are." }
]
}
</script>
</body>
</html>