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

96 lines
3.5 KiB
TypeScript

'use client'
import React from "react";
import { motion } from "@/components/motion";
import { ArrowRight, LineChart } from "lucide-react";
import { Band, Head } from '@/components/marketing/band'
import { Action } from '@hanzo/ui/marketing'
import { Body, Disc, Tile, Title } from './kit'
import { Box, Text, XStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
const GROUND = 'linear-gradient(to bottom, color-mix(in oklab, var(--muted) 30%, transparent), var(--background))'
const FEATURES = [
{
title: phrase("One tag"),
description: phrase("Paste it into the head of your site. Pageviews start arriving before you have decided what to name anything.")
},
{
title: phrase("Your data stays yours"),
description: phrase("The events live in your own Postgres, MySQL or MariaDB. Nothing is sampled, and nothing is held back from an export.")
},
{
title: phrase("Open source, MIT"),
description: phrase("Run it on a small server for as long as you like. The hosted version is the same code with the operations taken care of.")
}
]
/** A control that leans toward the pointer. */
const LEAN = { whileHover: { scale: 1.05 }, transition: { type: "spring", stiffness: 400, damping: 10 } } as const
const CallToAction = () => {
const { t } = useI18n()
return (
<Band rule={false} pad="$14" measure={1152} ground={GROUND}>
<Box position="relative" z="var(--z-raised)">
<Box mb="$5" $platform-web={{ textAlign: 'center' }}>
<Disc>
<LineChart size={40} />
</Disc>
</Box>
<Head
loud
title={t('Add the script')}
lede={t('One tag on the page and the dashboard fills in on its own. Host it yourself or let us run it — the software is the same either way.')}
>
<XStack flexWrap="wrap" justify="center" gap="$5">
<motion.div {...LEAN}>
<Action href="/login" fill>
{t('Try Hanzo')}
</Action>
</motion.div>
<motion.div {...LEAN}>
<Action href="/contact-sales">
{t('Request Demo')} <ArrowRight size={20} data-i18n-mirror />
</Action>
</motion.div>
</XStack>
</Head>
<Grid columns={{ min: 300, max: 3 }} gap={24}>
{FEATURES.map((feature, index) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Tile height="100%" p="$5" bg="$panel" hoverStyle={{ borderColor: 'var(--white-30)' }}>
<Title>{t(feature.title)}</Title>
<Body>{t(feature.description)}</Body>
</Tile>
</motion.div>
))}
</Grid>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<Text display="block" mt={64} text="center" fontSize="$2" lineHeight="1.15rem" color="var(--muted-foreground)">
{t('No credit card required. Free trial includes all premium features for {n} days.', { n: 14 })}
</Text>
</motion.div>
</Box>
</Band>
);
};
export default CallToAction;