Files
zeekayandGitHub 3df66c3990 i18n: /m* through /r* and the product kits draw their words through t (#70)
Pages from /machines to /risk, the model pages, and the kits they are made of
(page-kit, ProductLanding, LayerPage, Stack, the models kit, band's Head) mark
authored copy where it is written and draw it in the reader's language. A server
page marks a literal phrase('…') or wraps it in <T>; a client page calls t. The
kits translate a string prop through useSay (components/marketing/say), so a
page passes phrase('…') and markup passes as written. An arrow that points
turns over right to left (data-i18n-mirror).

Rebased on main: the fold-based heroes (Fold, PageHero, MachinesHero, the
product and layer pages) keep main's structure and copy, with their label,
title and lede marked phrase. /platform is main's CloudLanding; the deleted
/platform/layers and the old referral components stay deleted.

Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
2026-10-30 00:18:44 -07:00

131 lines
5.5 KiB
TypeScript

'use client'
import type { ReactNode } from 'react'
/**
* /playground — the Playground, the catalog product the console serves: every
* model in every mode, with each run's tokens and cost, then the same request
* from code. Where to start lives on /learn.
*
* NO TAILWIND. The sections under the hero are
* `components/marketing/band`'s `Band` and `Head`; the type and the controls are
* @hanzo/ui's marketing components (`ChromeText` for the heading and its badge,
* `Title`, `Lede`, `Action`, `More`, `Leaf`); the card grids are
* `@hanzo/ui/grid`'s `Grid`.
*
* Every ink, edge and ground is a semantic token of @hanzo/design (`--card`,
* `--border`, `--muted-foreground`), so both themes draw from one statement.
* `e2e/gates/playground.spec.ts` measures the page at every width in both.
*/
import React from 'react'
import { motion } from '@/components/motion'
import { ArrowRight } from 'lucide-react'
import ChromeText from '@/components/ui/chrome-text'
import { ProductFooter } from "@/components/products/ProductFooter"
import { Box, Text, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Band, Head } from '@/components/marketing/band'
import { AgentPrompt } from '@/components/product/AgentPrompt'
import { Related } from '@/components/product/Related'
import { CONSOLE } from '@/components/home/nav-data'
import { product } from '@/lib/product'
import { Action, Lede } from '@hanzo/ui/marketing'
import { mix } from '@hanzo/app/lib/mix'
import { under } from '@/components/shell/dock'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
/** What the two shared blocks say about this product: the audited command, the
* route the catalog proves it by, and the docs page or null. The table has no
* playground command and docs publishes no page for it, so each block is
* silent on both rather than guessing. */
const PRODUCT = product('playground')
/** The quiet ink. The page's own ink is the layout's `--foreground`. */
const DIM = 'var(--muted-foreground)'
/** A code frame: the same tile, with its label bar clipped by the corner. */
const FRAME = {
overflow: 'hidden',
rounded: 'var(--radius-xl)',
borderWidth: 1,
borderColor: 'var(--border)',
bg: 'var(--card)',
} as const
/** The pair of controls that closes a section: stacked on a phone, side by side
* from 640. PROPS, not a style object — an inline `flex-direction` would
* outrank the atomic class `$sm` emits and the row would never turn. */
const ROW = { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 12 } as const
/** A starter: its language, its label, and its code highlighted by the server page. */
export interface Example {
lang: string
label: string
code: ReactNode
}
const Playground = ({ examples }: { examples: Example[] }) => {
const { t } = useI18n()
return (
<Box minH="100vh">
{/* $11 and $12, the two rhythms the bands under it keep. */}
<Box render="section" position="relative" {...under('$11')} pb="$11" $lg={{ pt: under('$12').pt, pb: '$12' }}>
<Box position="absolute" t={0} r={0} b={0} l={0} background={`linear-gradient(to bottom in oklab, ${mix('var(--pure-white)', 10)}, transparent)`} />
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1280} mx="auto" px={16} $sm={{ px: 24 }} $lg={{ px: 32 }}>
<Box width="100%" maxW={768} mx="auto">
{/* The badge is ChromeText's own, which centres the stack it heads. */}
<ChromeText preHeading="Playground" mb={24}>{t('Try a model, then ship it')}</ChromeText>
<Lede mb={32} text="center">
{t("Chat, completions, decisions, embeddings, image, video, audio and vision, against every model in the catalog, with each run's tokens and cost beside the answer.")}
</Lede>
<Box {...ROW} $sm={{ flexDirection: 'row' }}>
<Action href={`${CONSOLE}/playground`} fill justify="center">
{t('Open the Playground')} <ArrowRight width={16} height={16} data-i18n-mirror />
</Action>
<Action href="/api" justify="center">
{t('Read the API')}
</Action>
</Box>
</Box>
</Box>
</Box>
{PRODUCT && <AgentPrompt product={PRODUCT} />}
<Band rule={false} pad="$11">
<Head title={phrase('Then the same request from code')} lede={phrase('A key, a model name, and a message is the whole first request.')} />
{/* Two starters, two columns: a third track would leave the row half empty. */}
<Grid columns={{ min: 320, max: 2 }} gap={24}>
{examples.map((e, i) => (
<YStack
key={e.label}
render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.05 }} />}
{...FRAME}
>
<XStack items="center" justify="space-between" px={16} py={8} borderBottomWidth={1} borderColor="var(--border)">
<Text fontSize="$1" lineHeight="0.9167rem" textTransform="uppercase" letterSpacing={0.55} color={DIM}>
{e.lang}
</Text>
<Text fontSize="$1" lineHeight="0.9167rem" color={mix('var(--foreground)', 70)}>
{t(e.label)}
</Text>
</XStack>
{e.code}
</YStack>
))}
</Grid>
</Band>
{PRODUCT && <Related product={PRODUCT} />}
<ProductFooter slug="playground" name="Playground" />
</Box>
)
}
export default Playground