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

141 lines
6.0 KiB
TypeScript

'use client'
import React, { useState, type ReactNode } from "react";
import { motion } from "@/components/motion";
import { Code, Terminal, PenTool, Layers } from "lucide-react";
import { Band, Head, INK } from '@/components/marketing/band'
import { Action, Title } from '@hanzo/ui/marketing'
import { Body } from './kit'
import { Box, Text, XStack, YStack } from '@hanzo/ui'
import { DOCS } from '@/components/home/nav-data'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
const tabs = [
{ id: "js", label: "JavaScript", icon: <Code size={16} /> },
{ id: "py", label: "Python", icon: <Terminal size={16} /> },
{ id: "api", label: "API", icon: <PenTool size={16} /> }
];
const PROMISES = [
{
lead: phrase('Your database, your rows'),
body: phrase('It runs against Postgres, MySQL or MariaDB — one you already operate. The raw events stay somewhere you can query directly.'),
},
{
lead: phrase('Many sites, one install'),
body: phrase('Add a site, get a tracking id. Group people into teams, and share a board with someone who should not have an account.'),
},
{
lead: phrase('Open source, MIT'),
body: phrase('Read it, fork it, run it on a small server for as long as you like. The hosted version exists so you do not have to.'),
},
]
/** Under each sample: the install command for the two clients, a sentence for HTTP. */
const FOOT: Record<string, string> = {
js: 'npm install hanzo-analytics',
py: 'pip install hanzo-analytics',
}
const KEY = phrase('API Key: Get yours from the Hanzo Analytics dashboard')
/** The three samples, highlighted by the server page (components/analytics/samples). */
export type Samples = Record<'js' | 'py' | 'api', ReactNode>
const DeveloperFirst = ({ samples }: { samples: Samples }) => {
const [activeTab, setActiveTab] = useState("js");
const { t } = useI18n();
return (
<Band rule={false} pad="$14" measure={1280} ground={INK}>
<Box position="relative" z="var(--z-raised)">
<Head
loud
title={t('A script, then an API')}
lede={t('The tag collects pageviews on its own. Everything past that is a function call.')}
/>
{/* ONE column on a phone, seven from `$lg` — and the spans have to move
with it. A fixed `columns={7}` never collapses, so at 390px the seven
tracks are 50px each and the code panel hangs 51px past the edge,
where `overflow-x: clip` makes it unreachable rather than scrollable.
The gate names exactly that. */}
<Box display="grid" gridTemplateColumns="minmax(0,1fr)" gap="$7" $lg={{ gridTemplateColumns: 'repeat(7, minmax(0,1fr))' }}>
<Box $lg={{ gridColumn: 'span 3' }} display="flex" flexDirection="column" justify="center">
<motion.div initial={{ opacity: 0, x: -20 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true }} transition={{ duration: 0.5 }}>
<XStack items="center" mb={20} gap="$3">
<Layers size={24} color="var(--foreground)" />
<Title render="h3" quiet>
{t('What you are signing up for')}
</Title>
</XStack>
<YStack render="ul" gap="$4" mb="$6">
{PROMISES.map((p) => (
<XStack render="li" key={p.lead}>
<Text mr="$3" fontSize="$3" lineHeight="1.25rem" color="var(--foreground)">
•
</Text>
<Box>
<Text fontSize="$3" lineHeight="1.25rem" fontWeight="500" color="var(--foreground)">
{t(p.lead)}
</Text>
<Body mt={4}>{t(p.body)}</Body>
</Box>
</XStack>
))}
</YStack>
<XStack flexWrap="wrap" gap="$3">
<Action href={`${DOCS}/docs/analytics`}>{t('View Documentation')}</Action>
<Action href={`${DOCS}/docs/openapi`}>{t('API Reference')}</Action>
<Action href="https://github.com/hanzoai/insights">{t('Example Projects')}</Action>
</XStack>
</motion.div>
</Box>
<Box $lg={{ gridColumn: 'span 4' }} minW={0}>
<motion.div initial={{ opacity: 0, x: 20 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.2 }}>
<Box rounded="var(--radius-xl)" borderWidth={1} borderColor="var(--neutral-800)" bg="$panel" overflow="hidden" boxShadow="var(--shadow-xl)">
<XStack borderBottomWidth={1} borderColor="var(--neutral-800)">
{tabs.map((tab) => {
const on = activeTab === tab.id
return (
<XStack
key={tab.id}
render={<button type="button" onClick={() => setActiveTab(tab.id)} />}
items="center"
gap={8}
borderWidth={0}
py={12}
px={16}
cursor="pointer"
bg={on ? 'var(--neutral-800)' : 'transparent'}
$platform-web={{ color: on ? 'var(--pure-white)' : 'var(--muted-foreground)' }}
>
{tab.icon}
<Text fontSize="$2" lineHeight="1.15rem" fontWeight="500" color="inherit">
{tab.label}
</Text>
</XStack>
)
})}
</XStack>
{samples[activeTab as keyof Samples]}
<Text display="block" p="$3" bg="var(--muted)" borderTopWidth={1} borderColor="var(--neutral-800)" fontSize="$2" lineHeight="1.15rem" color="var(--muted-foreground)">
{FOOT[activeTab] ?? t(KEY)}
</Text>
</Box>
</motion.div>
</Box>
</Box>
</Box>
</Band>
);
};
export default DeveloperFirst;