// LandingStats — strip of 3-4 big numbers under a "by the numbers" // section. Each value uses AnimatedNumber so the digits roll up when // the section scrolls into view. // // Layout per cell (top → bottom): // 1. Eyebrow row: icon chip + small uppercase label aligned together // so the icon reads AS a label, not as decoration. Chip is sized // to match cap-height of the label (gives the icon real weight // without competing with the number). // 2. Big number with tabular figures, generous breathing room. // Prefix + unit are visually subordinate via size, not opacity // collapse (kept above the 4.5:1 contrast floor). // 3. Sub-caption — one line of detail, muted but readable. // // The previous version had a 16px svg next to a 60px number, a // freestanding hairline "accent rail" that aligned to nothing, and a // glass background with a decorative hover-gradient hairline. All // three were AI-grammar tells; this revision removes them. Hierarchy // is carried by the size step between the number and the eyebrow, // plus the kit's normal hover state on the cell. import type { ReactNode } from 'react' import { cn } from '../cn' import { AnimatedNumber } from '../primitives/animatedNumber' export interface StatSpec { /** Numeric value — drives the count-up animation. */ readonly value: number /** Optional unit suffix shown next to the value (e.g. "ms", "%"). */ readonly unit?: string /** Static prefix (e.g. "<", "~"). Useful for things like "<5ms". */ readonly prefix?: string /** Formatter — passed through to AnimatedNumber. */ readonly format?: 'integer' | 'percent' | ((n: number) => string) /** Caption below the number (becomes the eyebrow label at the top * of the cell). */ readonly label: ReactNode /** Small sub-caption below the number. */ readonly sub?: ReactNode /** Optional icon — pass any kit icon. Sized to the eyebrow's * cap-height so it reads as part of the label, not as decoration. */ readonly icon?: ReactNode } interface LandingStatsProps { readonly stats: ReadonlyArray readonly className?: string } export const LandingStats = ({ stats, className, }: LandingStatsProps): ReactNode => (
{stats.map((s, i) => (
{/* Eyebrow row — icon + label travel together. Icon is sized * to the label's cap-height so it reads as a sibling, not a * floating chip. */}
{s.icon ? ( {s.icon} ) : null} {s.label}
{/* Big number — tabular figures, leading-none for tight * vertical rhythm against the eyebrow above and the sub * below. Prefix/unit are subordinate via SIZE (not via * pushing opacity below readable contrast). */}
{s.prefix ? ( {s.prefix} ) : null} {s.unit ? ( {s.unit} ) : null}
{s.sub ? (
{s.sub}
) : null}
))}
)