HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import Link from 'next/link';2import type { ReactNode } from 'react';3import { cn } from '@/lib/cn';45/** Page-width wrapper. Pages render inside <main> without padding: wrap content in Container (1280) or Container wide (1600). */6export function Container({ children, className, wide = false }: { children: ReactNode; className?: string; wide?: boolean }) {7 return <div className={cn('container-x mx-auto w-full', wide ? 'max-w-[1600px]' : 'max-w-[1280px]', className)}>{children}</div>;8}910/** Section with eyebrow + title + optional action link. Spatial composition with a hairline — not a card. */11export function Section({12 eyebrow,13 title,14 lede,15 action,16 children,17 className,18 id,19 hairline = true,20}: {21 eyebrow?: string;22 title?: ReactNode;23 lede?: ReactNode;24 action?: { href: string; label: string };25 children: ReactNode;26 className?: string;27 id?: string;28 hairline?: boolean;29}) {30 return (31 <section id={id} className={cn('section-y scroll-mt-20', hairline && 'hairline', className)}>32 {(eyebrow || title) && (33 <div className="mb-4 flex items-end justify-between gap-4 md:mb-5">34 <div className="min-w-0">35 {eyebrow && <p className="eyebrow">{eyebrow}</p>}36 {title && <h2 className="mt-1 text-lg font-semibold tracking-tight md:text-xl">{title}</h2>}37 {lede && <p className="mt-1 max-w-2xl text-sm text-ink-2">{lede}</p>}38 </div>39 {action && (40 <Link href={action.href} className="link shrink-0 py-1 text-sm">41 {action.label} →42 </Link>43 )}44 </div>45 )}46 {children}47 </section>48 );49}5051export function PageHeader({ eyebrow, title, lede, children, className, aside }: { eyebrow?: ReactNode; title: ReactNode; lede?: ReactNode; children?: ReactNode; className?: string; aside?: ReactNode }) {52 return (53 <div className={cn('pb-5 pt-7 md:pb-7 md:pt-10', className)}>54 <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">55 <div className="min-w-0">56 {eyebrow && <div className="eyebrow flex flex-wrap items-center gap-2">{eyebrow}</div>}57 <h1 className="display mt-2 text-[28px] md:text-[40px]">{title}</h1>58 {lede && <p className="mt-3 max-w-2xl text-[15px] leading-relaxed text-ink-2 md:text-base">{lede}</p>}59 </div>60 {aside && <div className="shrink-0">{aside}</div>}61 </div>62 {children}63 </div>64 );65}6667/** Stat tile: big tabular number + label + optional delta/hint. Composes in a hairline grid, never in a card. */68export function Stat({ label, value, hint, delta, className, accent = false, href }: { label: string; value: ReactNode; hint?: ReactNode; delta?: { value: string; tone?: 'positive' | 'negative' | 'neutral' }; className?: string; accent?: boolean; href?: string }) {69 const body = (70 <>71 <p className="eyebrow">{label}</p>72 <p className={cn('tnum mt-1 text-[26px] font-semibold leading-none tracking-tight md:text-[32px]', accent && 'text-accent')}>{value}</p>73 {(hint || delta) && (74 <p className="mt-1.5 flex items-center gap-2 text-xs text-ink-3">75 {delta && <span className={cn('tnum font-medium', delta.tone === 'positive' && 'text-positive', delta.tone === 'negative' && 'text-danger', (!delta.tone || delta.tone === 'neutral') && 'text-accent-2')}>{delta.value}</span>}76 {hint}77 </p>78 )}79 </>80 );81 if (href)82 return (83 <Link href={href} className={cn('block min-w-0 py-3 hover:text-accent md:py-4', className)}>84 {body}85 </Link>86 );87 return <div className={cn('min-w-0 py-3 md:py-4', className)}>{body}</div>;88}8990/** Grid of Stat tiles separated by hairlines (2 columns on mobile, n on desktop). */91export function StatGrid({ children, cols = 4, className }: { children: ReactNode; cols?: 3 | 4 | 5 | 6 | 8; className?: string }) {92 const desktop = { 3: 'md:grid-cols-3', 4: 'md:grid-cols-4', 5: 'md:grid-cols-5', 6: 'md:grid-cols-6', 8: 'md:grid-cols-4 lg:grid-cols-8' }[cols];93 return <div className={cn('grid grid-cols-2 gap-x-6 border-y border-rule [&>*]:border-b [&>*]:border-rule md:[&>*]:border-b-0', desktop, className)}>{children}</div>;94}9596/** Small honest note (derived / estimated / methodology pointer). */97export function Note({ children, className }: { children: ReactNode; className?: string }) {98 return <p className={cn('text-xs leading-relaxed text-ink-3', className)}>{children}</p>;99}100