Files
zeekayandHanzo Dev f11062f432 fix(analytics): theme-aware hero ground and surfaces to fix light-mode contrast
The /analytics landing page hardcoded theme-independent colors
(var(--pure-black), var(--pure-white), --neutral-*) into the hero ground,
dashboard mock, code windows and section washes. In light mode that meant
near-black ink on a pure-black hero and white chart lines on light
surfaces — unreadable and harsh.

Swap them for @hanzo/design's theme-aware tokens (--background, --muted,
--secondary, --primary, --muted-foreground). Dark rendering is unchanged;
light mode now resolves correctly.

Verified with headless Chromium (hero bg/ink before vs after) and clean
tsc --noEmit / design lint.


Co-authored-by: Hanzo Dev <[email protected]>
2026-10-25 09:02:26 -07:00

163 lines
6.6 KiB
TypeScript

'use client'
import React from "react";
import { motion } from "@/components/motion";
import { ArrowRight, LineChart } from "lucide-react";
import { Action, Chip, Display, Lede } from '@hanzo/ui/marketing'
import { Delta, Dots, Figure, Label, Panel, ink, tint } from './kit'
import { Box, Text, View, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
/** Each figure's change is an argument of one sentence, `{change} vs last week`. */
const STATS = [
{ label: phrase('Users'), value: '127,543', change: '+12.4%', at: 1 },
{ label: phrase('Conversion Rate'), value: '4.87%', change: '+0.8%', at: 1.2 },
{ label: phrase('Revenue'), value: '$45,892', change: '+15.2%', at: 1.4 },
]
/**
* The points that breathe behind the fold. Spread by the golden angle rather
* than drawn at random, so the page the server writes is the page the browser
* hydrates — a random place differs between the two and React says so.
*/
const POINTS = Array.from({ length: 20 }, (_, i) => ({
x: (i * 61.803) % 100,
y: (i * 38.197 + 17) % 100,
duration: 3 + ((i * 7) % 10) / 5,
delay: (i * 13) % 50 / 10,
}))
const HeroSection = () => {
const { t } = useI18n()
return (
<Box
render="section"
position="relative"
display="flex"
flexDirection="row"
minH="100vh"
items="center"
justify="center"
overflow="hidden"
px="$4"
py="$14"
$sm={{ px: '$5' }}
$lg={{ px: '$6' }}
>
<Box position="absolute" t={0} r={0} b={0} l={0} z="var(--z-base)" bg="var(--background)" />
{/* Animated dots/points background */}
<Box position="absolute" t={0} r={0} b={0} l={0} overflow="hidden" opacity={0.3}>
{POINTS.map((p, i) => (
<View key={i} position="absolute" l={`${p.x}%`} t={`${p.y}%`}>
<motion.div animate={{ opacity: [0.2, 0.8, 0.2], scale: [1, 1.5, 1] }} transition={{ duration: p.duration, repeat: Infinity, delay: p.delay }}>
<View width={4} height={4} rounded={9999} bg="var(--primary)" />
</motion.div>
</View>
))}
</Box>
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1280} mx="auto">
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6 }}>
<Box mb={32} $platform-web={{ textAlign: 'center' }}>
<Chip
mb={24}
py={4}
px={16}
fontWeight="500" borderColor="var(--white-30)" bg={tint(10)} color={ink(70)}
>
{t('Web analytics you host yourself')}
</Chip>
<Display>Hanzo Analytics</Display>
<Lede mt={24} mx="auto" maxW={768}>
{t('Traffic, sessions and revenue for the sites you run, in a dashboard you can read without training. It is one script on the page and one database you own — no sampling, no data-processing agreement, and no export limits on your own numbers.')}
</Lede>
</Box>
</motion.div>
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, delay: 0.3 }}>
<XStack mt={40} flexWrap="wrap" gap={16} justify="center">
<Action href="/login" fill>
{t('Try Hanzo')}
</Action>
<Action href="/contact-sales">
{t('Request Demo')} <ArrowRight size={20} data-i18n-mirror />
</Action>
</XStack>
</motion.div>
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.6, delay: 0.6 }}>
<Box
position="relative"
mt={64}
mx="auto"
maxW={1024}
p="$4"
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--neutral-800)"
overflow="hidden"
boxShadow="var(--shadow-2xl)"
$platform-web={{ background: 'linear-gradient(to bottom right, var(--muted), var(--background))' }}
>
<XStack mb="$4" items="center" justify="space-between" borderBottomWidth={1} borderColor="var(--neutral-800)" pb="$2">
<XStack items="center" gap="$2">
<LineChart size={20} color="var(--foreground)" />
<Text fontWeight="500" color="var(--foreground)">
{t('Hanzo Analytics Dashboard')}
</Text>
</XStack>
<Dots />
</XStack>
<Grid columns={3} gap={16}>
<Panel gridColumn="span 2" height={256}>
{/* Animated chart lines */}
<Box position="relative" height="100%" width="100%">
<Box position="absolute" b={0} l={0} width="100%" height={1} bg="var(--neutral-700)" />
<Box position="absolute" l={0} t={0} height="100%" width={1} bg="var(--neutral-700)" />
<svg width="100%" height="100%" viewBox="0 0 100 50">
<motion.path
d="M0,50 L10,40 L20,45 L30,35 L40,38 L50,25 L60,30 L70,20 L80,15 L90,10 L100,5"
fill="none"
stroke="var(--primary)"
strokeWidth="1"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, delay: 0.5 }}
/>
<motion.path
d="M0,50 L10,42 L20,40 L30,38 L40,30 L50,32 L60,25 L70,28 L80,20 L90,18 L100,15"
fill="none"
stroke="var(--muted-foreground)"
strokeWidth="1"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, delay: 0.8 }}
/>
</svg>
</Box>
</Panel>
<YStack gap="$4">
{STATS.map((s) => (
<Panel key={s.label} height="30%">
<Label>{t(s.label)}</Label>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: s.at }}>
<Figure>{s.value}</Figure>
</motion.div>
<Delta>{t('{change} vs last week', { change: s.change })}</Delta>
</Panel>
))}
</YStack>
</Grid>
</Box>
</motion.div>
</Box>
</Box>
);
};
export default HeroSection;