Files
zeekay 11265a21fd Highlight every code sample with shiki in the code theme
Code on hanzo.ai was plain monospace: 285 blocks across 200 pages, one of
them hand-inked in four greys. components/code now renders each sample
at build with shiki and @hanzo/ui's codeTheme (Dracula dark, GitHub
Light light), every colour written as light-dark() so the page's
color-scheme picks it, on the theme's own ground. The page ships the
coloured spans and no highlighter; e2e/gates/code.spec.ts holds both.

A client page that shows code is split into a server page.tsx that owns
the sample and a client view.tsx that takes the highlighted node:
ProductLanding's code.source, the blockchain pages' codeExamples, the
models kit Sample, the guest-key card, realtime, analytics, payments,
sdks and the rest. Transcripts, agent prompts and ASCII diagrams are not
code and keep their markup.

Takes @hanzo/ui 8.27.83 for codeTheme and @hanzo/design 0.5.35 with it,
regenerates app/gui.css for the new theme keys, and lowers the raw-colour
allowance to 58: the samples' literal inks are gone.
2026-10-05 01:31:58 -07:00

185 lines
9.6 KiB
TypeScript

"use client"
import type { ReactNode } from 'react'
/**
* /treasury — the double-entry ledger, what it is made of, and how a
* transaction is written.
*
* NO TAILWIND. The three sections under the hero are
* `components/marketing/band`'s `Band` and `Head`; the type is @hanzo/ui's
* marketing components (`Display`, `Claim`, `Lede`, `Title`, `Chip`, `Action`,
* `Leaf`, `Line`); the grids are `@hanzo/ui/grid`'s `Grid`; every frame is gui
* props. A `color-mix()` ink is not a value gui's colour props type, so a
* frame's tint rides `$platform-web`, which gui still writes as a class.
*
* One thing to know before editing the hero: the wash behind it is animated by
* framer, which owns `transform` for the length of the animation. Its centring
* is therefore the `translate` PROPERTY — which is also what Tailwind v4's
* `-translate-x-1/2` set, so nothing moved — and writing it as a
* `transform: translate(...)` would be overwritten by the `scale` keyframe on
* the first frame.
*/
import { motion } from "@/components/motion"
import { ProductFooter } from "@/components/products/ProductFooter"
import {
ArrowRight,
Banknote,
GitMerge,
Landmark,
RefreshCw,
Shield,
Wallet,
} from "lucide-react"
import { Box, XStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Band, Head } from '@/components/marketing/band'
import { Action, Chip, Claim, Display, Lede, Title, Leaf, Line } from '@hanzo/ui/marketing'
/** The inks this page draws in. */
const INK = 'var(--foreground)'
const DIM = 'var(--muted-foreground)'
const SOFT = 'color-mix(in oklab, var(--foreground) 80%, transparent)'
const SMALL = { fontSize: '$2', lineHeight: '1.4286', color: DIM } as const
const STAT = { fontSize: '$7', lineHeight: '1.3333', fontWeight: '700', color: INK } as const
/** The hairline tile a stat or a feature sits in. */
const TILE = {
p: 16,
rounded: 'var(--radius-xl)',
borderWidth: 1,
borderColor: 'var(--border)',
'$platform-web': { backgroundColor: 'color-mix(in oklab, var(--secondary) 50%, transparent)' },
} as const
/** A feature card: the same tile as a `Leaf`, so its edge answers a pointer. */
const FEATURE = { p: 24, borderColor: "var(--border)", bg: "color-mix(in oklab, var(--secondary) 50%, transparent)", hoverStyle: { borderColor: "var(--neutral-600)" } } as const
/** The 48px disc a feature's mark sits on. */
const DISC = {
width: 48,
height: 48,
items: 'center',
justify: 'center',
rounded: 'var(--radius-lg)',
'$platform-web': { backgroundColor: 'color-mix(in oklab, var(--pure-white) 10%, transparent)' },
} as const
/** The controls under the hero, wrapping on a phone. */
const WRAP = { flexWrap: 'wrap', justify: 'center', gap: 16 } as const
/** The pair that closes the page: stacked on a phone, side by side from 640.
* PROPS, so the `$sm` row is one atomic class beside the base column. */
const ROW = { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 12 } as const
const features = [
{ icon: Banknote, title: "Double-entry ledger", description: "A transaction is a set of postings with a source and a destination, and it is rejected unless it balances. Entries are immutable — a correction is another transaction, so history stays true." },
{ icon: GitMerge, title: "Reconciliation", description: "Match what the ledger believes against what a payment provider reports, and show the rows that disagree. The breaks are the output; agreeing totals need no attention." },
{ icon: Wallet, title: "Accounts and balances", description: "Hold balances per account and per currency, and move between them as ledger transactions. What an account settles against is configured per deployment." },
{ icon: RefreshCw, title: "Wallets with holds", description: "Multi-currency virtual wallets that can hold an amount and release it later. That is how you take money at checkout and pay a seller on delivery without the balance lying in between." },
{ icon: Shield, title: "Flows and webhooks", description: "Longer sequences — capture, split, pay out, retry — run as orchestrated flows, and every change of financial state is delivered as an event you can subscribe to." },
]
export default function TreasuryPage({ split }: { split: ReactNode }) {
return (
<>
<Box
render="section"
position="relative"
minH="90vh"
display="flex"
items="center"
justify="center"
overflow="hidden"
py="$11"
px={16}
$sm={{ px: 24 }}
$lg={{ px: 32 }}
>
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1024} mx="auto" $platform-web={{ textAlign: 'center' }}>
<Chip render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} />} mb={32} px={16} py={8} fontWeight="500" borderColor="var(--border)" bg="color-mix(in oklab, var(--pure-white) 5%, transparent)" color={SOFT}>
<Landmark width={16} height={16} color={INK} />
Treasury Operations
</Chip>
<Display render={<motion.h1 initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.1 }} />} mb={24}>
Hanzo{" "}
Treasury
</Display>
<Claim render={<motion.p initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.15 }} />} mb={16} fontWeight="500">
A double-entry ledger for money moving inside your product
</Claim>
<Lede render={<motion.p initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.2 }} />} mb={32} width="100%" maxW={768} mx="auto">
Every movement is postings between accounts, so the books balance by construction
rather than by a nightly job that hopes. Marketplace splits, held funds, refunds
and fees are written as transactions you can replay — which is what you need the
day someone asks where a particular dollar went.
</Lede>
<Box render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.25 }} />} mb={48} width="100%" maxW={768} mx="auto">
<Grid columns={2} gap={16}>
<Box {...TILE}><Line render="div" display="block" {...STAT}>Auto</Line><Line render="div" display="block" {...SMALL}>Sweep</Line></Box>
<Box {...TILE}><Line render="div" display="block" {...STAT}>Real-time</Line><Line render="div" display="block" {...SMALL}>Recon</Line></Box>
</Grid>
</Box>
<XStack render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.3 }} />} {...WRAP}>
<Action href="https://docs.hanzo.ai/docs/cli/treasury" fill>Read the docs <ArrowRight width={16} height={16} /></Action>
<Action href="https://github.com/hanzoai">GitHub</Action>
</XStack>
</Box>
</Box>
<Band pad="$11">
<Head title="What it is made of" lede="A ledger, a language for writing transactions, and the machinery around them." />
<Grid columns={{ min: 300, max: 3 }} gap={24}>
{features.map((feature, index) => (
<Leaf render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: index * 0.05 }} />} key={feature.title} {...FEATURE} hoverStyle={{ borderColor: "var(--neutral-600)" }}>
<XStack mb={16} {...DISC}><feature.icon width={24} height={24} color={INK} /></XStack>
<Line render="h3" display="block" mb={8} fontSize="$4" lineHeight="1.5556" fontWeight="600" color={INK}>{feature.title}</Line>
<Line render="p" display="block" {...SMALL}>{feature.description}</Line>
</Leaf>
))}
</Grid>
</Band>
<Band pad="$11" measure={896}>
<Head title="Write the transaction, not the bookkeeping" />
<Box
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.1 }} />}
overflow="hidden"
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--border)"
bg="var(--secondary)"
>
<XStack items="center" gap={8} px={16} py={8} borderBottomWidth={1} borderColor="var(--border)">
<XStack gap={6}>
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
</XStack>
<Line ml={8} fontSize="$1" lineHeight="1.3333" color={DIM}>split.num</Line>
</XStack>
{split}
</Box>
</Band>
<Band pad="$10" measure={768}>
<Box $platform-web={{ textAlign: 'center' }}>
<Title quiet mb={16}>Post your first transaction</Title>
<Box {...ROW} $sm={{ flexDirection: 'row' }}>
<Action href="https://docs.hanzo.ai/docs/cli/treasury" target="_blank" rel="noopener noreferrer" fill justify="center">
Read the docs <ArrowRight width={16} height={16} />
</Action>
<Action href="https://github.com/hanzoai" target="_blank" rel="noopener noreferrer" justify="center">
View on GitHub
</Action>
</Box>
<ProductFooter slug="treasury" name="Treasury" />
</Box>
</Band>
</>
)
}