mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
/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.
181 lines
8.3 KiB
TypeScript
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>
|
|
)
|
|
}
|