Files
hanzo.ai/app/layout.tsx
zeekay cb6db9e659 design: every page and component outside the landing and beta paths is at zero lint
Inline style objects became gui props (Box for a div, View or Text rendering the
element for the rest, since Box styles only a div), raw colours and sizes became
the design's tokens, the brand swatches read @hanzo/design's own values, and the
project and creation hues moved to palette.json, which is data. The newsletter's
subscribe button is the standard filled Action. connect-steps.tsx, which nothing
imports, is deleted with the brand constants nobody read.

Pages compared before and after at 1440x900 and 390x844: blockchain, brand,
directory, projects, settings, unsubscribe identical; founder 0.03% of pixels;
buying specialist's title snaps from 28 to the ramp's 26px and the newsletter's
button is now filled.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 03:49:42 -07:00

414 lines
29 KiB
TypeScript

import type { Metadata, Viewport } from 'next'
import { colors } from '@hanzo/design'
import { GuiProvider } from '@/components/GuiProvider'
import { Providers } from './providers'
import { ONE_SLASH, STALE } from '@/lib/head'
import { SEAL } from '@hanzo/app/lib/share'
import { Toaster } from '@/components/shell/later'
// NO STYLESHEET OF OUR OWN. Every sheet below is a package's, in cascade order;
// a page styles itself with gui props and @hanzo/ui components.
//
// @hanzo/design's tokens, one published group at a time: the faces, the
// ladder, type, spacing, radii, elevation, motion and z rungs every Hanzo
// surface reads.
import '@hanzo/design/tokens/fonts.css'
import '@hanzo/design/tokens/colors.css'
import '@hanzo/design/tokens/typography.css'
import '@hanzo/design/tokens/spacing.css'
import '@hanzo/design/tokens/radius.css'
import '@hanzo/design/tokens/elevation.css'
import '@hanzo/design/tokens/motion.css'
import '@hanzo/design/tokens/z.css'
// Its element defaults. The sheet declares its own layer (`base`), so it is
// imported plain: Next compiles `@import … layer(x)` into `@media layer(x)`, an
// unknown media feature, and the browser drops the block whole.
import '@hanzo/design/tokens/base.css'
// @hanzo/ui's elevation slice: the lit edge and drop shadow `sheet(n)` and
// `glass(n)` surfaces and every dialog, popover and menu wear. Unlayered, so the
// lift outranks the utilities. Not `@hanzo/ui/theme.css`, which inlines design's
// base unlayered and lets its element selectors beat every layered rule.
import '@hanzo/ui/glass.css'
// @hanzo/ui's data palette: the hues a record wears, keyed per theme (`.light`).
// Avatar draws an initial in its record's own hue from these and nothing else,
// so without them an organization's monogram has no colour.
import '@hanzo/tokens/css/data'
// The @hanzo/gui atomic sheet, as a real stylesheet Next can fingerprint and the
// browser can cache. Generated from lib/gui.ts by `prebuild`; see
// scripts/gen-gui-css.mjs for why it is not injected at render time.
import './gui.css'
// @hanzo/font's faces, italics and pixel cuts. After design's own two faces, so
// its `font-display: swap` is the declaration that holds for Zen and Zen Mono.
import '@hanzo/font/css'
// THE KEYFRAMES THE COMPONENTS NAME. @hanzo/ui's components style themselves
// through gui props and say how they move with `data-motion` (Pulse, Caret,
// Skeleton, FadeIn); this sheet holds the keyframes and the reduced-motion
// switch, which no prop can say. Every selector in it is a data attribute.
import '@hanzo/ui/styles/motion.css'
// A long document's type: markdown arrives as bare tags, so `Document` styles
// it by tag from its own slot. Data attributes again, nothing to collide with.
import '@hanzo/ui/styles/document.css'
// Right-to-left, from @hanzo/i18n: under `dir="rtl"` code, URLs, email fields
// and `[data-i18n-ltr]` keep their own writing order, and a `[data-i18n-mirror]`
// mark (an arrow, a chevron that points along the line) turns to face the line.
// Zero specificity, so a component's own rule still wins.
import '@hanzo/i18n/rtl.css'
import { ogImages, twitterImages } from '@/lib/constants/og'
import mark from '@/lib/data/mark.json'
import Handoff from '@/components/shell/composer'
import { Route } from '@/components/shell/route'
import { bootScript } from '@hanzo/appearance/state'
import * as language from '@hanzo/i18n/boot'
import { INSTALL } from '@/lib/appearance'
/* viewport-fit=cover: draw under the notch and home indicator instead of
letterboxing; @hanzo/design's base pays the safe-area insets back where chrome
and content need them. */
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
/* Names the UA's own ground before a stylesheet exists, so form controls,
scrollbars and the canvas behind a slow paint are dark rather than white.
`dark light` — dark FIRST, because that is this site's default; the second
value keeps a light-preferring reader's controls legible once the theme
resolves. Composes with the inline ground rule on <body>: that one paints
the page, this one paints everything the page does not own. */
colorScheme: 'dark light',
themeColor: [
// A meta tag takes a colour, not a custom property: the design's own values.
{ media: '(prefers-color-scheme: dark)', color: colors['neutral-950'] },
{ media: '(prefers-color-scheme: light)', color: colors['neutral-50'] },
],
}
/**
* On a touch screen every text field renders at @hanzo/design's control size
* (`--text-control`, 1rem times the reader's type scale there) and never under
* 16px: iOS Safari zooms the page in when a focused field computes below 16px,
* and stays zoomed. design's base asks for the size from a zero-specificity
* layer rule, which every field that states its own size outranks: the search
* palette's inline 14px, and @hanzo/ui's Input, Picker and Composer and
* @hanzo/composer's Field at 13 to 14px. Only an important declaration outranks
* an inline one. A fine pointer keeps every field as drawn, and pinch-zoom is
* untouched.
*/
const FIELDS =
'@media (pointer:coarse){input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file],[type=button],[type=submit],[type=reset],[type=image],[type=hidden]),textarea,select,[contenteditable]:not([contenteditable=false]){font-size:max(16px,var(--text-control,16px))!important}}'
/**
* An icon's URL carries a hash of its own bytes.
*
* These are served from the edge with a 24h TTL, and asked for by bare path the
* cache key never moved when the bytes did — the corrected mark shipped and
* every visitor kept the old one for a day. `scripts/sync-mark.mjs` derives the
* hash from each file in `prebuild`, so it cannot be forgotten the way a
* hand-bumped constant is, and an icon nobody edited keeps its URL and stays
* cached. Unstamped if the file is unknown to the map: a bare path is the
* behaviour we already had, and it beats requesting `?v=undefined`.
*/
const stamp = (path: keyof typeof mark.icons | string): string => {
const hash = (mark.icons as Record<string, string>)[path]
return hash ? `${path}?v=${hash}` : path
}
const SITE_TITLE = 'Hanzo — chat, or build with dev'
const SITE_DESCRIPTION = 'The whole cloud in one binary. Build with autonomous agents, frontier models, and sovereign infrastructure. Open source.'
export const metadata: Metadata = {
metadataBase: new URL('https://hanzo.ai'),
title: SITE_TITLE,
description: SITE_DESCRIPTION,
// Full modern favicon set from the canonical Hanzo mark (public/favicon.*).
// Single source of truth — there is no app/icon.* convention file.
// THE SVG LEADS, AND THE .ico CARRIES NO `sizes`. `sizes="any"` is not a hint
// that a raster fits every box — it makes the .ico the browser's PREFERRED
// candidate, outranking a typed SVG whatever the order, so the vector never
// drew and the tab showed whatever that 5 KB raster was baked from. hanzoai/id
// hit the identical bug: it swapped the SVG per brand and the swap was
// invisible for the same reason.
//
// Unqualified, the .ico is what a browser with no SVG support falls back to,
// which is the job it should have had all along.
icons: {
icon: [
{ url: stamp('/favicon.svg'), type: 'image/svg+xml' },
{ url: stamp('/favicon-32x32.png'), type: 'image/png', sizes: '32x32' },
{ url: stamp('/favicon-16x16.png'), type: 'image/png', sizes: '16x16' },
{ url: stamp('/favicon.ico') },
],
apple: [{ url: stamp('/apple-touch-icon.png'), sizes: '180x180', type: 'image/png' }],
},
manifest: '/site.webmanifest',
openGraph: {
title: SITE_TITLE,
description: SITE_DESCRIPTION,
url: 'https://hanzo.ai',
siteName: 'Hanzo AI',
type: 'website',
images: ogImages(SITE_TITLE),
},
twitter: {
card: 'summary_large_image',
title: SITE_TITLE,
description: SITE_DESCRIPTION,
images: twitterImages,
},
}
const JSON_LD = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Organization',
'@id': 'https://hanzo.ai/#organization',
name: 'Hanzo AI',
url: 'https://hanzo.ai',
// The favicon is 16 and 32 pixels and SVG; a logo entity wants a raster
// square well past Google's 112-pixel floor, and brand/hanzo-logo.png is
// 696x696.
logo: {
'@type': 'ImageObject',
url: 'https://hanzo.ai/brand/hanzo-logo.png',
width: 696,
height: 696,
},
// Stated on /about: started in 2016, through Techstars '17.
foundingDate: '2016',
funder: { '@type': 'Organization', name: 'Techstars', sameAs: 'https://www.techstars.com' },
sameAs: [
'https://github.com/hanzoai',
'https://x.com/hanzoai',
'https://discord.gg/XthHQQj',
'https://linkedin.com/company/hanzo-ai',
'https://facebook.com/hanzoinc',
'https://youtube.com/@HanzoAI',
'https://bsky.app/profile/hanzoai.bsky.social',
'https://www.reddit.com/r/HanzoAI/',
'https://www.tiktok.com/@hanzoinc',
'https://www.instagram.com/hanzoinc',
'https://hanzo.help',
'https://hanzo.community',
'https://docs.hanzo.ai',
'https://hanzo.id',
'https://platform.hanzo.ai',
'https://status.hanzo.ai',
'https://hanzoskills.com',
],
description: 'Open-source AI platform, autonomous developer agents, and sovereign cloud infrastructure.',
// The addresses /contact and /contact-sales print. A machine asked where to
// reach a company should not have to find the page first.
contactPoint: [
{
'@type': 'ContactPoint',
contactType: 'customer support',
email: '[email protected]',
url: 'https://hanzo.ai/contact',
},
{
'@type': 'ContactPoint',
contactType: 'sales',
email: '[email protected]',
url: 'https://hanzo.ai/contact-sales',
},
{
'@type': 'ContactPoint',
contactType: 'security',
email: '[email protected]',
url: 'https://hanzo.ai/.well-known/security.txt',
},
],
},
{
'@type': 'WebSite',
'@id': 'https://hanzo.ai/#website',
url: 'https://hanzo.ai',
name: 'Hanzo AI',
description: SITE_DESCRIPTION,
publisher: {
'@id': 'https://hanzo.ai/#organization',
},
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://hanzo.ai/search?q={search_term_string}',
},
'query-input': 'required name=search_term_string',
},
},
{
'@type': 'SoftwareApplication',
'@id': 'https://hanzo.ai/#platform',
name: 'Hanzo AI Cloud & Dev Platform',
applicationCategory: 'DeveloperApplication, ArtificialIntelligence',
operatingSystem: 'Cloud, Linux, macOS, Windows',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: 'One API for every model, autonomous agent orchestration, vector storage, IAM, KMS, and serverless functions.',
},
],
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
// No font loader. Zen is ours, so @hanzo/design ships the two variable woff2
// files and declares the @font-face itself — this layout imports
// `tokens/fonts.css` and the faces are loaded and named at :root with nothing
// to bind. next/font/google could only ever serve a Google-hosted family, and
// reaching for it here meant a per-build family name that every package
// stylesheet had to be taught.
return (
// `t_dark` is here for the same reason the inline ground rule below is: the
// gui sheet scopes EVERY token to `:root.t_dark` / `:root.t_light`, and
// nothing wore either until hydration — so the first paint had no
// `--borderColor` at all and every gui border fell back to `currentColor`,
// i.e. a solid white box round the composer, the search field and every
// input on the page. Dark is only the server's guess; the appearance script
// in the head paints the reader's choice, else the device's scheme, before
// the first frame.
<html lang="en" data-brand="hanzo" suppressHydrationWarning className="t_dark">
{/* IN THE HEAD, and that is the whole point of it. This is the ground
before any stylesheet — dev serves CSS through JavaScript and prod can
race it — and it used to be the first node of the BODY, which is one
node too late: a browser paints as soon as it has a body to paint, so
the reader got a frame of default WHITE and then the site. Parsed in
the head, there is no such frame. The real theme takes over the moment
the package sheets above land. */}
<head>
{/* The reader's language and its direction on <html lang dir> before the
first paint (@hanzo/i18n): what this browser last showed, else the
browser's languages. An Arabic page opens right to left instead of
turning once the bundle runs; the words follow when its catalog loads. */}
<script dangerouslySetInnerHTML={{ __html: language.bootScript() }} />
{/* One slash at the front of the address, and a page from before a deploy
reloaded once when a chunk it names is gone (lib/head.ts). */}
<script dangerouslySetInnerHTML={{ __html: ONE_SLASH }} />
{/* The reader's theme, type, spacing, corners and accent as the last
visit painted them (@hanzo/appearance), before the first frame and
in both vocabularies the sheets key on: design's `light`, gui's
`t_light`. A device that has never chosen gets its own scheme
(`system`: prefers-color-scheme, followed as it changes). */}
<script dangerouslySetInnerHTML={{ __html: bootScript({ base: INSTALL }) }} />
<script dangerouslySetInnerHTML={{ __html: STALE }} />
{/* A shared chat's token out of the address, before anything reads the
address: the tags below and every module after them see the page
with no fragment (lib/share.ts `SEAL`). */}
<script dangerouslySetInnerHTML={{ __html: SEAL }} />
<script
dangerouslySetInnerHTML={{
__html: `if(typeof window!=='undefined'&&(window.__TAURI__||window.__TAURI_INTERNALS__)){document.documentElement.setAttribute('data-desktop','true')}`,
}}
/>
{/* gui's stylesheet is injected into this head while its module
evaluates — BEFORE hydration — so on the client it holds the slot
the server wrote this rule into, and React reports the two as one
element that changed. It is not ours to reorder: the injection
belongs to the library. Saying so here is what stops React trying to
reconcile a tag it did not render. */}
{/* `body` carries the INK here and no ground, which is not a tidy-up:
html's background propagates to the canvas and is painted before
every layer, negative ones included, while a background on `body` is
an ordinary block-level background that paints after them. The
second copy was therefore an opaque sheet over <DotMatrix/>. The
touch field size (`FIELDS`) rides the same element. The black
`--background` is the dark theme's alone (`:root:not(.light)`): a
bare `:root` rule lands after design's `.light` and turns every
`var(--background)` black in light, a filled action's ink among them.
On a phone the app's menu stands behind the pane on that floor
(`--pane-floor`), and the pane slides over it, raised; both are
@hanzo/app' own (Drawer.tsx), so no rule here draws them.
The account menu opens above the account row at the column's foot,
so it is never taller than the window above that row: the row and
its gutter take at most 80px of the foot, and the menu keeps the
gutter or the device's inset clear at the top. What does not fit
scrolls inside the menu. The rule names the menu's own panel
(@hanzo/ui's `dropdown-menu-content`): there is no popper wrapper
around it to size any more, and the panel's own `overflow: hidden`
would cut the rows below the fold instead of scrolling them.
The rooms' own phone sidebar (`[data-slot="workspace"]`, on /chat,
/home and the other rooms) is a drawer over a scrim too, so below
gui's desk breakpoint (768px) it stands on the same overlay rungs as
the app's: over the cookie banner, which waits on the banner rung
until it closes. The app's drawer (@hanzo/app Drawer.tsx) is the
whole window while it is out — the menu behind, the pane raised over
it — so the window, fixed on a phone, takes the modal rung for as
long as the drawer is not shut; shut, it is the page again. */}
<script
id="detect-desktop"
dangerouslySetInnerHTML={{
__html: `(function(){
try {
if (typeof window !== 'undefined' && ((window.__TAURI__ || window.__TAURI_INTERNALS__))) {
document.documentElement.setAttribute('data-desktop', 'true');
}
} catch(e){}
})();`,
}}
/>
<style
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html:
'html[data-desktop="true"]{background:transparent !important;background-color:transparent !important;overflow:hidden !important}html[data-desktop="true"] body{background:#000000 !important;background-color:#000000 !important;border-radius:12px !important;border:1px solid rgba(255,255,255,0.14) !important;box-shadow:0 16px 48px rgba(0,0,0,0.7) !important;overflow:hidden !important;height:100vh !important;box-sizing:border-box !important}html[data-desktop="true"].light body{background:#ffffff !important;background-color:#ffffff !important;border:1px solid rgba(0,0,0,0.14) !important}html[data-desktop="true"][data-maximized="true"] body{border-radius:0 !important;border:none !important}html[data-desktop="true"] [data-slot="window"]{height:100% !important;border-radius:12px !important;overflow:hidden !important}html[data-desktop="true"][data-maximized="true"] [data-slot="window"]{border-radius:0 !important}html[data-desktop="true"] header[data-slot="masthead"]{background:#000000 !important;background-color:#000000 !important;border-bottom:none !important;border-bottom-width:0 !important}header[data-slot="masthead"]{border-bottom:none !important;border-bottom-width:0 !important}:root,html{background:#000000 !important;background-color:#000000 !important;color-scheme:dark}body{background:#000000 !important;background-color:#000000 !important;color:#fafafa}:root.light,html.light{background:#fff !important;background-color:#fff !important;color-scheme:light}html.light body{background:#fff !important;background-color:#fff !important;color:#0a0a0a}:root:not(.light){--background:#000000;--pane-floor:#000000}@media (max-width:768px){[data-slot="frame"]{padding:0 !important;gap:0 !important}:root:not(.light) [data-slot="frame"]{background:#000000 !important;background-image:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(255,255,255,0.04),transparent 70%),linear-gradient(180deg,#000000 0%,#030303 50%,#080808 100%) !important}[data-slot="pane"]{border-radius:0 !important;border-width:0 !important;box-shadow:none !important;background:transparent !important}}@media (max-width:767.98px){[data-slot="workspace"]>[data-slot="scrim"]{z-index:var(--z-overlay) !important}[data-slot="workspace"]>[data-slot="list"]{z-index:var(--z-modal) !important}[data-slot="window"]:has([data-slot="drawer"]:not([data-state="shut"])){z-index:var(--z-modal) !important}}[data-slot="dropdown-menu-content"][role="menu"][aria-label="Account"]{max-height:calc(100dvh - 80px - max(8px,var(--safe-top)) - max(8px,var(--safe-bottom))) !important;overflow-y:auto !important;overscroll-behavior:contain}[data-audience] button,[data-audience] [role="radio"],[data-audience] div{border-radius:9999px !important}[data-slot="action"],[data-slot="chip"]{border-radius:9999px !important}[data-slot="action"][data-join="start"]{border-start-end-radius:0 !important;border-end-end-radius:0 !important}[data-slot="action"][data-join="end"]{border-start-start-radius:0 !important;border-end-start-radius:0 !important}@property --w{syntax:"<length>";inherits:true;initial-value:420px}[data-measure]{--w:min(420px,calc(100vw - 2 * var(--page-gutter)))}[data-measure]>[data-slot="lede"]{max-width:var(--w) !important;font-size:max(14px,calc(var(--w) / 26.25)) !important}[data-fit]>[data-line]{display:block;white-space:nowrap;font-weight:500;letter-spacing:-0.012em}[data-fit]>[data-line="1"]{font-size:calc(var(--w) / 10.24);line-height:1.08}[data-fit]>[data-line="2"]{font-size:calc(var(--w) / 6.13);line-height:.98}[data-flash]{--ink:100%;--x:-9999px;--lo:color-mix(in oklab,var(--foreground) var(--ink),var(--background));color:transparent;-webkit-text-fill-color:transparent;background-image:linear-gradient(90deg,var(--lo) calc(var(--x) - 1em),var(--foreground) var(--x),var(--lo) calc(var(--x) + 1em));-webkit-background-clip:text;background-clip:text;cursor:default}[data-shine]::after{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(105deg,transparent 30%,color-mix(in oklab,var(--pure-white) 55%,transparent) 50%,transparent 70%);transform:translateX(-120%);animation:shine 5s ease-in-out 1.4s infinite}@keyframes shine{0%{transform:translateX(-120%)}30%,100%{transform:translateX(120%)}}[data-shine]>[data-spark]{display:inline-block;animation:twinkle 2.6s ease-in-out infinite}@keyframes twinkle{0%,100%{transform:scale(1) rotate(0deg);opacity:1}50%{transform:scale(.8) rotate(20deg);opacity:.65}}[data-shine]>svg:last-child{transition:transform .2s ease}[data-shine]:hover>svg:last-child{transform:translateX(2px)}[data-shine]:hover>svg:last-child:dir(rtl){transform:translateX(-2px) scaleX(-1)}@media (prefers-reduced-motion:reduce){[data-shine]::after{display:none}[data-shine]>[data-spark],[data-shine]>svg{animation:none;transition:none}}[data-hue="claudecode"] svg{color:#D97757}[data-models]{display:flex !important;flex-direction:row !important;gap:10px;height:520px}[data-models]>[data-model]{flex:1 1 0 !important;min-width:0;transition:flex-grow .6s cubic-bezier(.2,.8,.2,1)}[data-models]:hover>[data-model]:hover,[data-models]>[data-model]:focus-visible,[data-models]:not(:hover):not(:focus-within)>[data-model][data-lead]{flex-grow:6 !important}[data-model] video[data-media]{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}[data-model]>[data-open]{opacity:0;transform:translateY(10px);transition:opacity .3s,transform .3s}[data-models]:hover>[data-model]:hover>[data-open],[data-models]>[data-model]:focus-visible>[data-open],[data-models]:not(:hover):not(:focus-within)>[data-model][data-lead]>[data-open]{opacity:1;transform:none;transition-delay:.18s}[data-model]>[data-media]{transition:transform 1.4s cubic-bezier(.2,.8,.2,1)}[data-models]:hover>[data-model]:hover>[data-media],[data-models]>[data-model]:focus-visible>[data-media],[data-models]:not(:hover):not(:focus-within)>[data-model][data-lead]>[data-media]{transform:scale(1.04)}[data-model]>[data-spine]{transition:opacity .2s}[data-models]:hover>[data-model]:hover>[data-spine],[data-models]>[data-model]:focus-visible>[data-spine],[data-models]:not(:hover):not(:focus-within)>[data-model][data-lead]>[data-spine]{opacity:0}@media (max-width:1023px){[data-models]{height:auto;overflow-x:auto !important;scroll-snap-type:x mandatory;padding-bottom:8px}[data-models]>[data-model]{flex:0 0 80% !important;height:440px;scroll-snap-align:start}[data-model]>[data-open]{opacity:1;transform:none}[data-model]>[data-spine]{display:none}}@media (prefers-reduced-motion:reduce){[data-models]>[data-model],[data-model]>[data-open],[data-model]>[data-spine],[data-model]>[data-media]{transition:none;transform:none}}[data-typing]>*{animation:pulse 1.1s ease-in-out infinite}[data-typing]>*:nth-child(2){animation-delay:.18s}[data-typing]>*:nth-child(3){animation-delay:.36s}[data-spin]{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){[data-typing]>*,[data-spin]{animation:none}}[data-slot="beta"]{--tilt:.5;position:relative;isolation:isolate;overflow:hidden;background:radial-gradient(50% 160% at 50% 50%,rgba(255,255,255,.045),transparent 72%),linear-gradient(180deg,#1b1c1e 0,#121314 3px,#26282a 4px,#54575b 5px,#2e3033 6px,#1a1b1d 10px,#0e0f10 38%,#121315 62%,#1e1f22 78%,#191a1c calc(100% - 7px),#3e4044 calc(100% - 3px),#585b5f calc(100% - 1px));box-shadow:inset 0 1px 0 rgba(255,255,255,.06),inset 0 -1px 0 rgba(255,255,255,.78)}[data-slot="beta"]::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:240px;margin-left:-120px;z-index:var(--z-base);pointer-events:none;background:linear-gradient(110deg,transparent calc(50% - 100px),rgba(255,255,255,.05) calc(50% - 40px),rgba(255,255,255,.2) calc(50% - 6px),rgba(255,255,255,.62) calc(50% - 1px),rgba(255,255,255,.62) calc(50% + 1px),rgba(255,255,255,.2) calc(50% + 6px),rgba(255,255,255,.05) calc(50% + 40px),transparent calc(50% + 100px));-webkit-mask-image:linear-gradient(180deg,#000 0,#000 3px,rgba(0,0,0,.52) 6px,rgba(0,0,0,.52) calc(100% - 6px),#000 calc(100% - 3px));mask-image:linear-gradient(180deg,#000 0,#000 3px,rgba(0,0,0,.52) 6px,rgba(0,0,0,.52) calc(100% - 6px),#000 calc(100% - 3px));transform:translate3d(calc((var(--tilt) - .5) * 30vw),0,0);will-change:transform}[data-slot="beta"]>*{position:relative;z-index:calc(var(--z-base) + 1);text-shadow:0 1px 1px rgba(0,0,0,.65)}[data-slot="beta"]:is(a):hover svg:last-of-type{transform:translateX(3px)}[data-slot="beta"] svg{transition:transform .25s cubic-bezier(.2,.7,.2,1)}@media (prefers-reduced-motion:reduce){[data-slot="beta"]::after{transform:none}[data-slot="beta"] svg{transition:none}}[data-marquee]{display:flex !important;flex-direction:row !important;width:max-content;animation:marquee 48s linear infinite}[data-marquee-wrap]{-webkit-mask-image:linear-gradient(90deg,transparent,black 10%,black 90%,transparent);mask-image:linear-gradient(90deg,transparent,black 10%,black 90%,transparent)}[data-marquee-wrap]:hover [data-marquee]{animation-play-state:paused}@keyframes marquee{to{transform:translate3d(-50%,0,0)}}@media (prefers-reduced-motion:reduce){[data-marquee]{animation:none;width:auto}[data-marquee]>[aria-hidden]{display:none !important}[data-marquee]>*:first-child{flex-wrap:wrap;justify-content:center;row-gap:24px}[data-marquee-wrap]{-webkit-mask-image:none;mask-image:none}}[data-hue="codex"] svg{fill:url(#hue-codex)}[data-hue="antigravity"] svg{fill:url(#hue-antigravity)}' +
FIELDS,
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(JSON_LD) }}
/>
</head>
{/* No class. The ground, the ink and the smoothing are @hanzo/design's
base; carrying them here as three utilities was the same three
declarations said twice.
Content does not depend on scripting, and no rule rescues it:
components/motion.tsx ships every reveal opaque and in place, and a
keyed entrance (`data-motion`) is a CSS animation that plays without
JavaScript. Measured with scripting off on every top-level route,
nothing a scripted reader sees is hidden. */}
<body>
<GuiProvider>
<Route>
{/* The shell: which mode you are in, and the field that starts one.
Both live here so every page has them, and both read the session
Providers resolves, so they are inside it. */}
<Providers>
{children}
<Handoff />
</Providers>
{/* One themed Toaster for the whole document. The account and
dashboard actions fire toast.success/error and had nowhere to
land — no Toaster was mounted anywhere, so every one of those
confirmations was silent. Through @hanzo/ui, so the design
system owns the material rather than a bare sonner; fetched
once React owns the page, since a toast is always something
the reader did after it (components/shell/later.tsx). */}
<Toaster />
</Route>
</GuiProvider>
{/* No analytics tag here: pageviews AND interaction autocapture already ride
the ONE @hanzo/event client in <Providers> (host + ingest key + consent
gate in one place). A second hz.js tag posted to the same /v1/event endpoint
and double-counted every pageview.
NOR an authoring widget. `hanzo.app/edit.js` used to load here, and it
is an AUTHORING tool on a PUBLISHED page: a shadow-root launcher pinned
bottom-right at z-index 2147483000, which on a phone landed on 43x32px
of the drawer's "Try Hanzo" button — this site's one primary action —
so the edge of the CTA opened an editor instead. Two rules in
the site stylesheet were written to live with it, a z-index clamp and 64px of
dock clearance, and both are gone with it. A reader of hanzo.ai is not
editing hanzo.ai; the entry point for that is the repo. */}
</body>
</html>
)
}