mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Each drew its own centred hero at its own measure. Now the shared fold, with the page's own content beside the words: how we hire (the hiring band said it again lower down and is gone), the investor thesis, the press kit's three logos on their grounds, the design principles, the startup program's three terms, and on /contact the sales card and the teams under the heading.
453 lines
19 KiB
TypeScript
453 lines
19 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* /brand — the design system, shown rather than described.
|
|
*
|
|
* NO TAILWIND. The seven section frames are `components/marketing/band`'s
|
|
* `Band` and `Head`; @hanzo/ui's marketing components carry the type and the
|
|
* controls (`Display`, `Lede`, `Title`, `Chip`, `Action`, `Leaf`, `Line`);
|
|
* everything left is gui props over @hanzo/design's tokens. 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.
|
|
*
|
|
* THE THREE LOCAL COMPONENTS TOOK A `className` AND IT WAS THE PAYLOAD, which
|
|
* is the one shape a de-Tailwind pass cannot leave alone: `ColorSwatch` was
|
|
* handed `bg-[#171717]`, and `TypographyExample` a whole type ramp, so the
|
|
* utility string WAS the thing being demonstrated. Each now takes the VALUE it
|
|
* was demonstrating — a `fill` for the swatch, the `type` props for the
|
|
* specimen — and every call site passes one. A page about a design system may
|
|
* not be the last place still writing utilities to describe it.
|
|
*
|
|
* Two things the swap moves, both deliberate:
|
|
*
|
|
* - the section headings take `Title quiet`, so `text-2xl md:text-3xl`
|
|
* becomes the site's own section rung and steps at 640 rather than 768.
|
|
* - `Action` is the one button shape, so the four `Button` variants in the
|
|
* Components showcase render as the site's two weights plus `More` — which
|
|
* is what those four have BEEN on every other page since the roles landed.
|
|
*
|
|
* `rounded-3xl` on the CTA glows had no design token (`--radius-3xl` is
|
|
* Tailwind's alone) and neither did they need one: they are circles, so they
|
|
* take `--radius-full`, which is what `rounded-full` already said.
|
|
*/
|
|
|
|
|
|
import { colors } from '@hanzo/design'
|
|
import React from "react";
|
|
import { Fold } from '@/components/marketing/fold'
|
|
import { Steps } from '@/components/preview/steps'
|
|
import { motion } from "@/components/motion";
|
|
import {
|
|
Palette,
|
|
Download,
|
|
Type,
|
|
Grid3X3,
|
|
Sparkles,
|
|
ArrowRight,
|
|
} from "lucide-react";
|
|
import { Box, XStack, YStack } from '@hanzo/ui'
|
|
import { phrase } from '@hanzo/i18n'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { Band, Head } from '@/components/marketing/band'
|
|
import { Action, Chip, Lede, More, Title, Leaf, Lift, Line, type LineProps } from '@hanzo/ui/marketing'
|
|
|
|
/** How narrow a column may get before a grid drops one, per shape. */
|
|
const TRIO = { min: 280, max: 3 } as const
|
|
const RAMP = { min: 140, max: 6 } as const
|
|
const PAIR_UP = { min: 140, max: 4 } as const
|
|
|
|
/** The wash a mark's plate is drawn on, at the two strengths this page uses. */
|
|
const PLATE = 'color-mix(in srgb, var(--primary) 10%, transparent)'
|
|
const BADGE = 'color-mix(in srgb, var(--primary) 15%, transparent)'
|
|
const TILE = '$hover'
|
|
|
|
const CAPTION = { fontSize: '$1', lineHeight: '1.3333', color: 'var(--muted-foreground)' } as const
|
|
const LABEL = { fontSize: '$2', lineHeight: '1.4286', fontWeight: '500', color: 'var(--foreground)' } as const
|
|
|
|
/** A heading inside a section or a card, at the rung and weight it states. */
|
|
const Heading = (p: LineProps) => <Line render="h3" display="block" color="var(--foreground)" {...p} />
|
|
|
|
/** The card whose edge lifts on approach — a `Leaf`, at this page's one wash. */
|
|
const LEAF = { p: 24, borderColor: "var(--border)", bg: TILE, hoverStyle: { borderColor: "color-mix(in oklab, var(--pure-white) 30%, transparent)" } } as const
|
|
|
|
/** A mark's plate, in the page's one accent at 10%. */
|
|
const MARK = {
|
|
items: 'center',
|
|
justify: 'center',
|
|
width: 48,
|
|
height: 48,
|
|
mb: 16,
|
|
rounded: 'var(--radius-lg)',
|
|
'$platform-web': { backgroundColor: PLATE },
|
|
} as const
|
|
|
|
/**
|
|
* A colour, drawn as itself. `fill` is any CSS `background` — a hex, a token,
|
|
* or a gradient — because that is the thing the swatch exists to show, and a
|
|
* class name was only ever a way of naming it indirectly.
|
|
*/
|
|
const ColorSwatch = ({ name, hex, fill }: { name: string; hex: string; fill: string }) => {
|
|
const { t } = useI18n()
|
|
return (
|
|
<YStack gap={8}>
|
|
<Box width="100%" aspectRatio={1} rounded="var(--radius-lg)" borderWidth={1} borderColor="var(--border)" background={fill} />
|
|
<Line render="div" display="block" {...LABEL}>
|
|
{t(name)}
|
|
</Line>
|
|
<Line render="div" display="block" {...CAPTION}>
|
|
{t(hex)}
|
|
</Line>
|
|
</YStack>
|
|
)
|
|
};
|
|
|
|
/**
|
|
* One rung of the ramp, set in itself. `type` is the style the specimen is
|
|
* demonstrating, so the rung and its description travel together and neither
|
|
* can be a utility string.
|
|
*/
|
|
const TypographyExample = ({ name, type, description }: { name: string; type: LineProps; description: string }) => {
|
|
const { t } = useI18n()
|
|
return (
|
|
<YStack gap={8} mb={40}>
|
|
<Line render="div" display="block" {...type}>
|
|
{t(name)}
|
|
</Line>
|
|
<Line render="div" display="block" {...CAPTION}>
|
|
{t(description)}
|
|
</Line>
|
|
</YStack>
|
|
)
|
|
};
|
|
|
|
const ComponentShowcase = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
<Box overflow="hidden" rounded="var(--radius-xl)" borderWidth={1} borderColor="$edge">
|
|
<Box p={16} borderBottomWidth={1} borderColor="$edge" bg={TILE}>
|
|
<Heading {...LABEL}>{title}</Heading>
|
|
</Box>
|
|
<XStack
|
|
flexWrap="wrap"
|
|
items="center"
|
|
justify="center"
|
|
gap={16}
|
|
p={32}
|
|
bg="$panel"
|
|
>
|
|
{children}
|
|
</XStack>
|
|
</Box>
|
|
);
|
|
|
|
const PRINCIPLES = [
|
|
{
|
|
icon: Sparkles,
|
|
title: phrase("Minimal & Focused"),
|
|
description: phrase("Reduce visual noise and focus on essential elements. Each design decision should have purpose and enhance the user experience.")
|
|
},
|
|
{
|
|
icon: Type,
|
|
title: phrase("Elegant & Refined"),
|
|
description: phrase("Use subtle gradients, appropriate spacing, and thoughtful typography to create elegant and sophisticated interfaces.")
|
|
},
|
|
{
|
|
icon: Grid3X3,
|
|
title: phrase("Consistent & Cohesive"),
|
|
description: phrase("Maintain consistency in design patterns, spacing, and visual hierarchy to create a unified experience across all touchpoints.")
|
|
}
|
|
] as const
|
|
|
|
/** A swatch of the design's own palette: the label is the token's value, the fill is the token. */
|
|
const swatch = (name: string, token: keyof typeof colors) => ({ name, hex: colors[token], fill: `var(--${token})` })
|
|
|
|
const BRAND_COLORS = [swatch(phrase("Hanzo Black"), "hanzo-black"), swatch(phrase("Hanzo White"), "hanzo-white")] as const
|
|
|
|
const GRAY_SCALE = [
|
|
swatch(phrase("Gray 900"), "neutral-900"),
|
|
swatch(phrase("Gray 800"), "neutral-800"),
|
|
swatch(phrase("Gray 700"), "neutral-700"),
|
|
swatch(phrase("Gray 600"), "neutral-600"),
|
|
swatch(phrase("Gray 500"), "neutral-500"),
|
|
swatch(phrase("Gray 400"), "neutral-400"),
|
|
] as const
|
|
|
|
const GRADIENTS = [
|
|
{ name: phrase("Brand Glow"), hex: phrase("Radial"), fill: "linear-gradient(to bottom right in oklab, color-mix(in oklab, var(--pure-white) 30%, transparent), transparent)" },
|
|
{ name: phrase("White Fade"), hex: phrase("Linear"), fill: "linear-gradient(to bottom in oklab, color-mix(in oklab, var(--pure-white) 10%, transparent), transparent)" },
|
|
{ name: phrase("Gray Fade"), hex: phrase("Linear"), fill: "linear-gradient(to right in oklab, color-mix(in oklab, var(--neutral-800) 50%, transparent), transparent)" },
|
|
] as const
|
|
|
|
/** The ramp, each rung set in itself. `--text-*` rather than a frozen pixel,
|
|
* so the reader's own type size still moves the specimen it describes. */
|
|
const RAMP_ROWS = [
|
|
{ name: phrase("Heading 1"), description: phrase("Large page titles, hero sections"), type: { fontSize: '$10', lineHeight: '1.1111', fontWeight: '700', letterSpacing: 'var(--tracking-tight)', color: 'var(--foreground)' } },
|
|
{ name: phrase("Heading 2"), description: phrase("Section titles, major divisions"), type: { fontSize: '$8', lineHeight: '1.2', fontWeight: '700', letterSpacing: 'var(--tracking-tight)', color: 'var(--foreground)' } },
|
|
{ name: phrase("Heading 3"), description: phrase("Subsection headings, feature headings"), type: { fontSize: '$7', lineHeight: '1.3333', fontWeight: '600', color: 'var(--foreground)' } },
|
|
{ name: phrase("Heading 4"), description: phrase("Card titles, minor section headings"), type: { fontSize: '$6', lineHeight: '1.4', fontWeight: '500', color: 'var(--foreground)' } },
|
|
{ name: phrase("Body Large"), description: phrase("Featured paragraphs, pull quotes"), type: { fontSize: '$4', lineHeight: 'var(--leading-relaxed)', color: 'color-mix(in oklab, var(--foreground) 80%, transparent)' } },
|
|
{ name: phrase("Body"), description: phrase("Primary body text"), type: { fontSize: '$3', lineHeight: 'var(--leading-relaxed)', color: 'var(--muted-foreground)' } },
|
|
{ name: phrase("Caption"), description: phrase("Supporting text, metadata, labels"), type: { fontSize: '$2', lineHeight: '1.4286', color: 'var(--muted-foreground)' } },
|
|
] as const
|
|
|
|
/**
|
|
* The two heading rungs the ramp above states at `md:` — 6xl for Heading 1 and
|
|
* 4xl for Heading 2 — stay OFF the specimen. The row is a demonstration of
|
|
* one rung at every width, not the page's own
|
|
* heading; the ladder itself lives in `Display` and `Title`, which is where
|
|
* this page's real headings take it.
|
|
*/
|
|
|
|
const Brand = () => {
|
|
const { t } = useI18n()
|
|
return (
|
|
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
|
|
|
|
<Box render="main">
|
|
<Fold
|
|
label="Design System"
|
|
mark={<Palette width={14} height={14} color="currentColor" aria-hidden />}
|
|
title={t('Hanzo Brand{br}Guidelines.', { br: <br /> })}
|
|
lede="How to build interfaces that look like the rest of Hanzo across our platform and products."
|
|
actions={
|
|
<>
|
|
<Action href="#assets" fill px={24}>
|
|
<Download width={16} height={16} />
|
|
{t('Download Assets')}
|
|
</Action>
|
|
<Action href="/press" px={24} borderColor="var(--border)">
|
|
{t('Press Kit')}
|
|
</Action>
|
|
</>
|
|
}
|
|
preview={<Steps title="Design Principles" icon={<Palette size={12} color="currentColor" />} steps={PRINCIPLES.map((p) => ({ title: p.title, body: p.description }))} />}
|
|
/>
|
|
|
|
{/* Color Palette */}
|
|
<Band rule={false} pad="$10" measure={1152}>
|
|
<Head title={t('Color Palette')} lede={t('Our colors are carefully selected to create a bold, modern aesthetic.')} />
|
|
|
|
<YStack gap={48}>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<Heading mb={24} fontSize="$6" lineHeight="1.4" fontWeight="500">
|
|
{t('Brand Colors')}
|
|
</Heading>
|
|
<Grid columns={PAIR_UP} gap={24}>
|
|
{BRAND_COLORS.map((c) => (
|
|
<ColorSwatch key={c.name} name={c.name} hex={c.hex} fill={c.fill} />
|
|
))}
|
|
</Grid>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<Heading mb={24} fontSize="$6" lineHeight="1.4" fontWeight="500">
|
|
{t('Gray Scale')}
|
|
</Heading>
|
|
<Grid columns={RAMP} gap={24}>
|
|
{GRAY_SCALE.map((c) => (
|
|
<ColorSwatch key={c.name} name={c.name} hex={c.hex} fill={c.fill} />
|
|
))}
|
|
</Grid>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<Heading mb={24} fontSize="$6" lineHeight="1.4" fontWeight="500">
|
|
{t('Gradients')}
|
|
</Heading>
|
|
<Grid columns={TRIO} gap={24}>
|
|
{GRADIENTS.map((c) => (
|
|
<ColorSwatch key={c.name} name={c.name} hex={c.hex} fill={c.fill} />
|
|
))}
|
|
</Grid>
|
|
</motion.div>
|
|
</YStack>
|
|
</Band>
|
|
|
|
{/* Typography */}
|
|
<Band rule={false} pad="$10" measure={1152} ground="color-mix(in oklab, var(--secondary) 30%, transparent)">
|
|
<Head title={t('Typography')} lede={t('Clean, readable typography is essential to our design language.')} />
|
|
|
|
<Box
|
|
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />}
|
|
width="100%"
|
|
maxW={768}
|
|
mx="auto"
|
|
p={32}
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg={TILE}
|
|
>
|
|
{RAMP_ROWS.map((row) => (
|
|
<TypographyExample key={row.name} name={row.name} type={row.type} description={row.description} />
|
|
))}
|
|
</Box>
|
|
</Band>
|
|
|
|
{/* Components */}
|
|
<Band rule={false} pad="$10" measure={1152}>
|
|
<Head title={t('Components')} lede={t('Reusable UI components that maintain consistency across our products.')} />
|
|
|
|
<YStack gap={32}>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<ComponentShowcase title={t('Buttons')}>
|
|
<Action render="span" fill>
|
|
{t('Primary Button')}
|
|
</Action>
|
|
<Action render="span" borderColor="var(--border)">
|
|
{t('Secondary Button')}
|
|
</Action>
|
|
<Action render="span" borderWidth={0}>
|
|
{t('Ghost Button')}
|
|
</Action>
|
|
<More render="span">
|
|
{t('Link Button')}
|
|
</More>
|
|
</ComponentShowcase>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<ComponentShowcase title={t('Cards')}>
|
|
<Leaf width="100%" maxW={448} {...LEAF}>
|
|
<Heading mb={8} fontSize="$6" lineHeight="1.4" fontWeight="500">
|
|
{t('Card Title')}
|
|
</Heading>
|
|
<Line render="p" display="block" mb={16} color="var(--muted-foreground)">
|
|
{t('This is a standard card component with hover states and consistent styling.')}
|
|
</Line>
|
|
<Action render="span" minH={36} px={16} borderColor="var(--border)">
|
|
{t('Learn More')}
|
|
</Action>
|
|
</Leaf>
|
|
</ComponentShowcase>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<ComponentShowcase title={t('Badges')}>
|
|
<Chip px={12} py={4} borderWidth={0} fontWeight="500" bg={BADGE} color="var(--primary)">
|
|
{t('Brand Badge')}
|
|
</Chip>
|
|
<Chip px={12} py={4} borderColor="var(--border)" fontWeight="500" bg="var(--secondary)" color="color-mix(in oklab, var(--foreground) 80%, transparent)">
|
|
{t('Default Badge')}
|
|
</Chip>
|
|
<Chip px={12} py={4} borderColor="var(--border)" fontWeight="500" bg={PLATE} color="color-mix(in oklab, var(--foreground) 70%, transparent)">
|
|
{t('Success Badge')}
|
|
</Chip>
|
|
</ComponentShowcase>
|
|
</motion.div>
|
|
</YStack>
|
|
</Band>
|
|
|
|
{/* Download Assets */}
|
|
<Band id="assets" rule={false} pad="$10" measure={1152} ground="color-mix(in oklab, var(--secondary) 30%, transparent)">
|
|
<Head title={t('Download Assets')} lede={t('Get our brand assets for your projects and presentations.')} />
|
|
|
|
<Box width="100%" maxW={448} mx="auto">
|
|
<Leaf
|
|
render={<motion.a initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />}
|
|
href="/press/hanzo-press-kit.zip"
|
|
{...LEAF}
|
|
hoverStyle={{ borderColor: "color-mix(in oklab, var(--pure-white) 30%, transparent)" }}
|
|
>
|
|
<XStack {...MARK}>
|
|
<Download width={24} height={24} />
|
|
</XStack>
|
|
{/* The card decides the lift; the content says who takes part. */}
|
|
<Lift render="h3" display="block" mb={4} fontSize="$4" lineHeight="1.5556" fontWeight="600" color="var(--foreground)">
|
|
{t('Press Kit')}
|
|
</Lift>
|
|
<Line render="p" display="block" mb={8} fontSize="$2" lineHeight="1.4286" color="var(--muted-foreground)">
|
|
{t('Logos and brand assets (ZIP)')}
|
|
</Line>
|
|
</Leaf>
|
|
</Box>
|
|
</Band>
|
|
|
|
{/*
|
|
CTA Section — NOT a `Band`, and the reason is the two glows. They are
|
|
direct children of the section and are placed from ITS corners; inside
|
|
a Band they would fall to the inner measure box and step ~270px in from
|
|
each edge. A decorative element positioned against the frame keeps the
|
|
frame it was positioned against.
|
|
*/}
|
|
<Box
|
|
render="section"
|
|
position="relative"
|
|
overflow="hidden"
|
|
py={96}
|
|
px={16}
|
|
$md={{ px: 32 }}
|
|
background="linear-gradient(to bottom in oklab, color-mix(in oklab, var(--neutral-900) 30%, transparent), var(--background))"
|
|
>
|
|
<Box position="absolute" t={-160} r={-160} width={320} height={320} rounded="var(--radius-full)" pointerEvents="none" filter="blur(64px)" $platform-web={{ backgroundColor: 'color-mix(in oklab, var(--primary) 10%, transparent)' }} />
|
|
<Box position="absolute" b={-160} l={-160} width={320} height={320} rounded="var(--radius-full)" pointerEvents="none" filter="blur(64px)" $platform-web={{ backgroundColor: 'color-mix(in oklab, var(--primary) 5%, transparent)' }} />
|
|
|
|
<Box position="relative" z="var(--z-raised)" width="100%" maxW={896} mx="auto" $platform-web={{ textAlign: 'center' }}>
|
|
<Title
|
|
render={<motion.h2 initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />}
|
|
quiet
|
|
mb={16}
|
|
>
|
|
{t('Need brand assistance?')}
|
|
</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"
|
|
>
|
|
{t('Our team can help with custom assets, co-branding requests, and other brand-related inquiries.')}
|
|
</Lede>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.2 }}
|
|
>
|
|
<XStack flexWrap="wrap" items="center" justify="center" gap={16}>
|
|
<Action href="/contact" fill px={32}>
|
|
{t('Contact Us')}
|
|
<ArrowRight width={20} height={20} data-i18n-mirror />
|
|
</Action>
|
|
<Action href="mailto:[email protected]" px={32} borderColor="var(--border)">
|
|
brand@hanzo.ai
|
|
</Action>
|
|
</XStack>
|
|
</motion.div>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default Brand;
|