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

222 lines
8.6 KiB
TypeScript

'use client'
import React, { useRef } from "react";
import { motion } from "@/components/motion";
import { useScroll, useTransform } from "framer-motion";
import { ShoppingCart, DollarSign, CreditCard, Zap } from "lucide-react";
import { Band, Head, INK } from '@/components/marketing/band'
import { Body, Figure, Label, Panel, Sub, Title, ink } 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'
const FEATURES = [
{
icon: Zap,
title: phrase("Revenue beside the traffic"),
description: phrase("Send an amount and a currency with the event and it lands in the same reports as everything else. Totals, order values and the sessions behind them.")
},
{
icon: DollarSign,
title: phrase("Which campaign paid for itself"),
description: phrase("Attribution splits revenue across the channels and campaigns that touched the session, so spend can be argued with numbers rather than instinct.")
},
{
icon: CreditCard,
title: phrase("The checkout, step by step"),
description: phrase("Cart to payment as a funnel, with the abandonment rate per step. Add a goal for the one step you would defend in a meeting.")
},
]
const PRODUCTS = [
{ name: "Premium Headphones", score: 98 },
{ name: "Wireless Keyboard", score: 85 },
{ name: "Smart Watch", score: 79 },
]
/** The arrow beside a trend. */
const UP = (
<svg width={12} height={12} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
)
/** A figure's change, with its arrow. */
function Trend({ children }: { children: React.ReactNode }) {
return (
<XStack items="center" mt={4} gap={4} $platform-web={{ color: ink(70) }}>
{UP}
<Text fontSize="$2" lineHeight="1.15rem" color="inherit">
{children}
</Text>
</XStack>
)
}
const CommerceAnalytics = () => {
const { t } = useI18n();
const sectionRef = useRef<HTMLElement>(null);
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ["start end", "end start"]
});
const scale = useTransform(scrollYProgress, [0, 0.5], [0.95, 1]);
const opacity = useTransform(scrollYProgress, [0, 0.3], [0.5, 1]);
return (
<Band rule={false} pad="$14" measure={1280} ground={INK}>
{/* Background wash */}
<View
aria-hidden
position="absolute"
t={0}
r={0}
b={0}
l={0}
opacity={0.5}
$platform-web={{ background: 'linear-gradient(to bottom right, var(--white-10), var(--white-10))' }}
/>
<Box position="relative" z="var(--z-raised)" ref={sectionRef as never}>
<Head
loud
title={t('If you sell something')}
lede={t('Revenue is a report here, not an integration you buy on top.')}
/>
<Grid columns={{ min: 420, max: 2 }} gap={64}>
<YStack gap={48}>
{FEATURES.map((feature, index) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<XStack>
<XStack
render="span"
mr={16}
p={12}
self="flex-start"
rounded={16}
borderWidth={1}
borderColor="var(--border)"
$platform-web={{ background: 'linear-gradient(to bottom right, var(--white-20), var(--white-10))', color: 'var(--foreground)' }}
>
<feature.icon size={24} />
</XStack>
<Box>
<Title mb={8}>{t(feature.title)}</Title>
<Body>{t(feature.description)}</Body>
</Box>
</XStack>
</motion.div>
))}
</YStack>
{/* The dashboard grows into place as the section scrolls in. */}
{/* inline-style: `scale` and `opacity` are scroll-driven MotionValues framer writes per frame; no gui prop takes one. */}
<motion.div style={{ scale, opacity }}>
<Box
rounded={16}
overflow="hidden"
borderWidth={1}
borderColor="var(--neutral-800)"
boxShadow="var(--shadow-xl)"
$platform-web={{ background: 'linear-gradient(to bottom right, var(--muted), var(--background))' }}
>
<Box p="$4" borderBottomWidth={1} borderColor="var(--neutral-800)">
<XStack items="center" gap="$2">
<ShoppingCart size={20} color="var(--foreground)" />
<Text fontWeight="500" color="var(--foreground)">
{t('Commerce Dashboard')}
</Text>
</XStack>
</Box>
<Box p="$5">
<Box mb="$5">
<Sub mb={16}>{t('Sales Performance')}</Sub>
<Box position="relative" height={160} overflow="hidden" rounded="var(--radius-lg)" bg="$hover">
{/* Bar chart */}
<XStack position="absolute" t={0} r={0} b={0} l={0} items="flex-end" justify="space-around" px="$4" pb="$4">
{[65, 80, 55, 90, 70, 85, 60].map((height, i) => (
<motion.div
key={i}
initial={{ height: 0 }}
whileInView={{ height: `${height}%` }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
>
<View
width={24}
height="100%"
borderTopLeftRadius={4}
borderTopRightRadius={4}
$platform-web={{ background: 'linear-gradient(to top, var(--primary), var(--white-10))' }}
/>
</motion.div>
))}
</XStack>
</Box>
</Box>
<Grid columns={2} gap={16} mb={24}>
<Panel bg="$panel">
<Label>{t('Conversion Rate')}</Label>
<Figure mt={8} color="inherit">
4.78%
</Figure>
<Trend>+0.6%</Trend>
</Panel>
<Panel bg="$panel">
<Label>{t('Avg. Order Value')}</Label>
<Figure mt={8} color="inherit">
$87.32
</Figure>
<Trend>+2.4%</Trend>
</Panel>
</Grid>
<Box borderTopWidth={1} borderColor="var(--neutral-800)" pt="$4">
<Sub mb={16}>{t('Product Recommendations')}</Sub>
<YStack gap="$3">
{PRODUCTS.map((product, i) => (
<XStack key={product.name} items="center" justify="space-between">
<Text fontSize="$3" lineHeight="1.25rem" $platform-web={{ color: ink(80) }}>
{product.name}
</Text>
<XStack items="center" gap="$2">
<Box width={96} height={8} overflow="hidden" rounded="var(--radius-full)" bg="var(--neutral-800)">
<motion.div
initial={{ width: 0 }}
whileInView={{ width: `${product.score}%` }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.3 + i * 0.1 }}
>
<View height={8} bg="var(--primary)" />
</motion.div>
</Box>
<Text fontSize="$2" lineHeight="1.15rem" color="var(--foreground)">
{product.score}%
</Text>
</XStack>
</XStack>
))}
</YStack>
</Box>
</Box>
</Box>
</motion.div>
</Grid>
</Box>
</Band>
);
};
export default CommerceAnalytics;