mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
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]>
141 lines
6.0 KiB
TypeScript
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;
|