Files
hanzo.ai/e2e/gates/ground.spec.ts
zeekayandHanzo Dev d58d99ae86 benchmarks: five products, wins only, monochrome, one icon set
/benchmarks now reads as one story in five products: Enso (intelligence
per dollar), Kai (decisions), Zen 6 (speed on Hanzo hardware), the agent
harness and the agent platform. Each section opens with one headline and
one number, then its chart. Methods and the searchable shelf follow.

It publishes wins and the product's own measurements, each a recorded run:
- Kai leads Jev on 11 suites, listed by name. The Jailbreak suite and the
  typed HarmBench constants are gone, and so is "Decision Index v2", whose
  12th row was not a HarmBench run.
- No jailbreak-board figure is published. The Jev run on its unseen rows
  (hanzo-inc/kai 1671eca) has Jev ahead.
- Enso shows the frontiers it holds: GPQA-Diamond and LiveCodeBench v6.
- Zen 6 shows its 2026-09-23 readings.
- The platform section reads the cloud bench (lib/data/bench.json) and the
  runtime passes.
- The ledger keeps wins and standalone measurements; the leaderboard lists
  the suites Kai leads on the metric drawn.

Monochrome: Hanzo is in the brightest ink and the field in the mid grey;
standing is a glyph and a word. Icons come from gui's lucide set, and
e2e/gates/glyphs.spec.ts fails on an icon at 0×0, an <svg> with no shapes,
an icon outside 12–24 px, or an arrow typed as a character. Both schemes
are covered at 375 and 1440 px, as is the contrast gate.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-09 10:50:29 -07:00

96 lines
4.9 KiB
TypeScript

// The benchmark pages read in the visitor's scheme, ground and type alike.
//
// Their ground was once forced black while their type followed the visitor, so a
// light scheme drew dark headings on black. Ground and ink now come from the same
// theme; this holds them to it as WCAG contrast, ink against the background it
// actually sits on (the nearest opaque ancestor, translucent layers composited),
// for the hero's every line, the eyebrow badge and the coral figures, each
// chapter's heading and the chapter nav, in both schemes, on a phone and a laptop.
import { test, expect, type Page } from '@playwright/test'
import { serveExport } from './export'
import { api } from './revision'
import snapshot from '../../lib/data/benchmarks-kai.json' with { type: 'json' }
let site: Awaited<ReturnType<typeof serveExport>>
test.beforeAll(async () => {
site = await serveExport()
})
test.afterAll(async () => {
await site.close()
})
/** Every element `selector` matches that draws text, with its contrast against what it is drawn on. */
const contrasts = (page: Page, selector: string) =>
page.evaluate((sel) => {
type C = [number, number, number, number]
const parse = (s: string): C | null => {
const rgb = /rgba?\(([^)]+)\)/.exec(s)
if (rgb) {
const [r, g, b, a = 1] = rgb[1].split(/[\s,/]+/).filter(Boolean).map(Number)
return [r, g, b, a]
}
const srgb = /color\(srgb ([^)]+)\)/.exec(s)
if (srgb) {
const [r, g, b, a = 1] = srgb[1].split(/[\s/]+/).filter(Boolean).map(Number)
return [r * 255, g * 255, b * 255, a]
}
return null
}
const over = (top: C, base: [number, number, number]): [number, number, number] =>
[0, 1, 2].map((i) => top[i] * top[3] + base[i] * (1 - top[3])) as [number, number, number]
const ground = (el: Element): [number, number, number] => {
const layers: C[] = []
for (let e: Element | null = el; e; e = e.parentElement) {
const c = parse(getComputedStyle(e).backgroundColor)
if (c && c[3] > 0) {
layers.push(c)
if (c[3] >= 1) break
}
}
return layers.reverse().reduce<[number, number, number]>((base, c) => over(c, base), [255, 255, 255])
}
const lum = ([r, g, b]: number[]) =>
[r, g, b].map((v) => { const c = v / 255; return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4 }).reduce((a, c, i) => a + c * [0.2126, 0.7152, 0.0722][i], 0)
return [...document.querySelectorAll(sel)]
.filter((el) => [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent!.trim()) && (el as HTMLElement).offsetParent !== null)
.map((el) => {
const bg = ground(el)
const ink = over(parse(getComputedStyle(el).color) ?? [0, 0, 0, 1], bg)
const [a, b] = [lum(ink), lum(bg)].sort((x, y) => y - x)
return { text: (el.textContent ?? '').trim().slice(0, 48), ratio: Math.round(((a + 0.05) / (b + 0.05)) * 100) / 100 }
})
}, selector)
for (const scheme of ['light', 'dark'] as const) {
for (const width of [375, 1440]) {
test(`/benchmarks reads at body-text contrast under a ${scheme} scheme at ${width}px`, async ({ page }) => {
// Settled colours, not a transition's midpoint: the design system zeroes durations under reduced motion.
await page.emulateMedia({ colorScheme: scheme, reducedMotion: 'reduce' })
await page.setViewportSize({ width, height: 900 })
await page.route(api, (r) => r.fulfill({ json: { data: snapshot.data, total: snapshot.data.length } }))
await page.goto(`${site.url}/benchmarks`)
await page.evaluate(() => document.fonts.ready)
// The hero, its eyebrow badge included: every line at body-text contrast.
const hero = await contrasts(page, '#lead *')
expect(hero.length).toBeGreaterThan(3)
for (const c of hero) expect(c.ratio, `${scheme} ${width}px hero: "${c.text}"`).toBeGreaterThanOrEqual(4.5)
// Every chapter's name and heading, and the nav that names them.
const rest = await contrasts(page, 'main section[aria-labelledby] > *, nav[aria-label="Benchmark chapters"] *')
expect(rest.length).toBeGreaterThan(8)
for (const c of rest) expect(c.ratio, `${scheme} ${width}px: "${c.text}"`).toBeGreaterThanOrEqual(4.5)
})
test(`a benchmark's own page reads at body-text contrast under a ${scheme} scheme at ${width}px`, async ({ page }) => {
// Settled colours, not a transition's midpoint: the design system zeroes durations under reduced motion.
await page.emulateMedia({ colorScheme: scheme, reducedMotion: 'reduce' })
await page.setViewportSize({ width, height: 900 })
await page.goto(`${site.url}/benchmarks/kai-banking77`)
await page.evaluate(() => document.fonts.ready)
const all = await contrasts(page, 'main h1, main h2, main p')
expect(all.length).toBeGreaterThan(5)
for (const c of all) expect(c.ratio, `${scheme} ${width}px: "${c.text}"`).toBeGreaterThanOrEqual(4.5)
})
}
}