Files
zeekay 7ce9992a28 chrome: every offered page has a link in
/realtime and /tabs had lost theirs with the old footer; Platform and
Developers carry them. /founder is the Founder Program, linked from
/open-source's Contribute & Earn. /blog lists the essays published here,
/blog/synthesis among them.
2026-10-30 01:02:34 -07:00

181 lines
8.3 KiB
TypeScript

"use client"
/**
* /blog — the index of what we have published, and where to read it.
*
* NO TAILWIND. The hero keeps its own frame because of the wash behind it; the
* two sections under it are `components/marketing/band`'s `Band`, and the type
* is @hanzo/ui's marketing components (`Display`, `Lede`, `Title`, `Chip`,
* `Action`, `Leaf`). Everything left is gui props over @hanzo/design's
* tokens.
*
* Three things the swap moves:
*
* - the `h1` takes `Display` and each CTA heading `Title quiet`, so
* this page's ladder is the site's rather than its own copy of one.
* - a post row is `Leaf`, so the hover lives in one rule instead of a `group`
* on the row and a `group-hover:` on the title. The title now BRIGHTENS on
* approach where it used to dim to `foreground/80` — the same correction
* /press made, and for the same reason: a row you are pointing at should not
* be the dimmest thing in the list.
* - the arrow still slides, and the slide is `translate`. Tailwind v4's
* `translate-x-*` sets the `translate` property rather than `transform`, so
* a transition naming `transform` would animate nothing.
*/
import { motion } from "@/components/motion"
import { ArrowRight, BookOpen, Calendar } from "lucide-react"
import { useI18n } from "@hanzo/i18n/react"
import { Box, Text, XStack, YStack } from '@hanzo/ui'
import { Band } from '@/components/marketing/band'
import blog from '@/lib/data/blog.json'
import { Fold } from '@/components/marketing/fold'
import { Steps } from '@/components/preview/steps'
import { Action, Lede, More, Title, Leaf, Lift } from '@hanzo/ui/marketing'
import { mix } from '@hanzo/app/lib/mix'
const DIM = 'var(--muted-foreground)'
/** A post row: `Leaf` with its edge drawn at rest and 24px inside. */
const POST = { p: 24, borderColor: "var(--border)", bg: "color-mix(in oklab, var(--secondary) 50%, transparent)", hoverStyle: { borderColor: "color-mix(in oklab, var(--foreground) 20%, transparent)" } } as const
/** A post's category, on a pill. */
const TAG = {
display: 'inline-flex',
items: 'center',
px: 10,
py: 2,
rounded: 'var(--radius-full)',
fontSize: '$1',
lineHeight: '0.9167rem',
fontWeight: '500',
bg: mix('var(--pure-white)', 10),
color: mix('var(--foreground)', 80),
} as const
/** When a post went out, beside its mark. */
const WHEN = { display: 'inline-flex', items: 'center', gap: 6, fontSize: '$1', lineHeight: '0.9167rem', color: DIM } as const
/** A blob of light behind the last section. */
const BLOB = { position: 'absolute', width: 320, height: 320, rounded: 'var(--radius-full)', filter: 'blur(64px)', pointerEvents: 'none' } as const
/** The latest posts on hanzo.blog, synced from its feed at build (scripts/sync-blog.mjs). */
const posts = blog.posts
/** The essays published on this site rather than on hanzo.blog, by their own titles. */
const ESSAYS = [
{ title: 'The Next Frontier Will Be Synthesis', href: '/blog/synthesis' },
{ title: 'A New Pareto Frontier', href: '/blog/pareto-frontier' },
{ title: 'Kai 1: Decisions, Not Completions', href: '/blog/kai-decision-models' },
]
const day = (iso: string) =>
new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' })
export default function BlogPage() {
const { t } = useI18n()
return (
<Box minH="100vh">
<main>
{/* The first screen: what the blog is, beside the latest posts walked one at a time. */}
<Fold
label={t('Blog')}
mark={<BookOpen width={12} height={12} color="currentColor" />}
title={t('News{br}& Guides.', { br: <br /> })}
lede={t('Product launches, architecture deep dives, and how-to guides from the team building the AI workforce platform.')}
actions={
<>
<Action href={posts[0].link} fill>
{t('Read the latest')} <ArrowRight width={16} height={16} />
</Action>
<Action href="https://hanzo.blog">hanzo.blog</Action>
</>
}
preview={<Steps title="hanzo.blog" steps={posts.slice(0, 6).map((p) => ({ title: p.title, body: `${day(p.date)}. ${p.description}` }))} />}
/>
{/* Posts */}
<Box render="section" py="$6" px={16} $md={{ px: 32 }}>
<Box width="100%" maxW={1024} mx="auto">
<YStack gap={16}>
{posts.map((post, index) => (
<motion.div key={post.link} initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: Math.min(index * 0.05, 0.3) }}>
<Leaf
render="a"
href={post.link}
target="_blank"
rel="noopener noreferrer"
{...POST}
hoverStyle={{ borderColor: "color-mix(in oklab, var(--foreground) 20%, transparent)" }}
>
<XStack items="flex-start" justify="space-between" gap={16}>
<Box minW={0}>
<XStack items="center" gap={12} mb={8}>
<Text {...TAG}>{post.category}</Text>
<Text {...WHEN}>
<Calendar width={12} height={12} />
{day(post.date)}
</Text>
</XStack>
<Lift render="h2" display="block" mb={8} fontWeight="600">
{post.title}
</Lift>
<Text render="p" display="block" fontSize="$2" lineHeight="1.3203rem" color={DIM}>
{post.description}
</Text>
</Box>
{/* The resting ink rides the wrapper so the lift can still
reach the mark. `Lift step` is the slide — the site's
one step, read off the link this mark sits in. */}
<Text display="inline-flex" shrink={0} mt={4} color={mix('var(--muted-foreground)', 60)}>
<Lift display="inline-flex" step><ArrowRight width={20} height={20} data-i18n-mirror /></Lift>
</Text>
</XStack>
</Leaf>
</motion.div>
))}
</YStack>
<XStack mt={32} flexWrap="wrap" columnGap={24} rowGap={8} justify="center">
{ESSAYS.map((e) => (
<More key={e.href} href={e.href}>
{e.title} →
</More>
))}
</XStack>
</Box>
</Box>
{/* CTA */}
<Band rule={false} pad="$12" measure={896} ground="linear-gradient(to bottom in oklab, color-mix(in oklab, var(--neutral-900) 30%, transparent), var(--background))">
<Box {...BLOB} t={-160} r={-160} bg={mix('var(--pure-white)', 10)} />
<Box {...BLOB} b={-160} l={-160} bg={mix('var(--pure-white)', 5)} />
<Box position="relative" z="var(--z-raised)">
<Title render={<motion.h2 initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />} quiet mb={16} text="center">
{t('Build with Hanzo')}
</Title>
<Lede render={<motion.p initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.1 }} />} mb={40} width="100%" maxW={672} mx="auto" mt="$3.5" text="center">
{t('Deploy AI agents that code, market, and operate — starting free.')}
</Lede>
<XStack
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.2 }} />}
flexWrap="wrap"
items="center"
justify="center"
gap={16}
>
<Action href="https://hanzo.blog" target="_blank" rel="noopener noreferrer" fill>
{t('Every post')}<ArrowRight width={20} height={20} data-i18n-mirror />
</Action>
<Action href="https://docs.hanzo.ai" target="_blank" rel="noopener noreferrer">
{t('Read the docs')}
</Action>
</XStack>
</Box>
</Band>
</main>
</Box>
)
}