import type { CSSProperties } from 'react'; /** * sammillan.com design system. * * One source of truth for the values that were previously redeclared inline in * roughly 25 page files. The scale is lifted from the homepage, which the July * easlo-calm pass established as the reference, not invented here. * * The site styles with inline `style={{}}` objects rather than utility classes. * That is the existing idiom and it is kept. What changes is that the values now * come from one place. * * Usage: * import { color, text, rule, link } from '@/lib/design'; *
...
* * Compose with spread when a one-off tweak is needed: *
*
* Mirrored in app/globals.css :root. If you change a colour here, change it there.
* See DESIGN.md.
*/
// ── Type stacks ──────────────────────────────────────────────────────────────
// Geist Mono everywhere since 24 Sep 2026, loaded in app/layout.tsx as
// --font-geist-mono. `sans` and `mono` are the same stack; both keys stay so
// existing imports keep working.
export const font = {
sans: 'var(--font-personal-inter), Arial, sans-serif',
mono: 'var(--font-personal-inter), Arial, sans-serif',
} as const;
// ── Colour ───────────────────────────────────────────────────────────────────
export const color = {
/** Page background. Giants background. */
bg: '#ffffff',
/** Cards and raised surfaces. Giants dark_background. */
surface: '#f0f0f0',
/** Secondary fill, for wells and inset blocks. Giants darker_background, recessed below the page. */
surfaceSunk: '#f0f0f0',
/** Headings and primary text. */
ink: '#000000',
/** Body copy. Softer than ink, passes AA on bg. */
body: '#5f5f5f',
/** Secondary text and section labels. */
muted: '#5f5f5f',
/**
* Meta, captions, timestamps.
*
* Giants foreground. Minimum 7.82:1 across the four shared surfaces.
* Keep the source dark_foreground out of small text; it fails on hover fills.
*/
faint: '#5f5f5f',
/** Giants magenta, the text-safe terracotta (minimum 4.91:1 on shared surfaces). */
accent: '#000000',
accentBorder: 'rgba(0,0,0,0.35)',
/** Hover and glow. Giants bright_magenta. */
accentBright: '#303030',
accentDim: 'rgba(0,0,0,0.12)',
/** Semantic only. Marks something live or free. Not decorative. */
live: '#000000',
/** Hairlines. The main structural device on this site. Foreground at low alpha. */
hair: 'rgba(0,0,0,0.08)',
hairStrong: 'rgba(0,0,0,0.14)',
hairHover: 'rgba(0,0,0,0.22)',
} as const;
/** Topic colours for indexes and wayfinding. Never use these as CTA colours. */
export const domainColor = {
performance: '#000000',
ownership: '#000000',
experiments: '#000000',
craft: '#000000',
race: '#000000',
reading: '#000000',
} as const;
export type Domain = keyof typeof domainColor;
/** Low-alpha companions for semantic wayfinding surfaces. */
export const domainTint = {
performance: 'rgba(0,0,0,0.12)',
ownership: 'rgba(0,0,0,0.12)',
experiments: 'rgba(0,0,0,0.12)',
craft: 'rgba(0,0,0,0.12)',
race: 'rgba(224,175,104,0.12)',
reading: 'rgba(158,206,106,0.12)',
} as const;
// ── Spacing ──────────────────────────────────────────────────────────────────
// Sparse on purpose. Whitespace is doing structural work, so the big steps get used most.
export const space = {
1: '4px',
2: '8px',
3: '12px',
4: '16px',
5: '24px',
6: '32px',
7: '48px',
8: '64px',
9: '72px',
10: '96px',
} as const;
export const radius = {
sm: '8px',
md: '12px',
lg: '12px',
xl: '12px',
} as const;
export const ease = {
smooth: 'cubic-bezier(0.32, 0.72, 0, 1)',
quick: 'cubic-bezier(0.22, 1, 0.36, 1)',
} as const;
/** Readable measure. Long-form prose should never exceed this. */
export const measure = '68ch';
/** Tighter measure for lead paragraphs and intros. */
export const measureTight = '58ch';
// ── Type scale ───────────────────────────────────────────────────────────────
// Typography does most of the visual work on this site, so the scale is small
// and the jumps are decisive. Prefer picking a named style over hand-setting px.
export const text = {
/** Homepage identity masthead. One use only, above the hero statement. */
masthead: {
fontFamily: font.sans,
fontSize: 'clamp(64px, 10vw, 96px)',
fontWeight: 700,
letterSpacing: '-0.035em',
lineHeight: 1,
color: color.ink,
margin: 0,
},
/** Page title. One per page. */
h1: {
fontFamily: font.sans,
fontSize: 'var(--site-h1-size)',
fontWeight: 500,
letterSpacing: '-.045em',
lineHeight: 'var(--site-h1-line)',
color: color.ink,
margin: 0,
},
/** Section heading inside a page. */
h2: {
fontFamily: font.sans,
fontSize: 'var(--site-h2-size)',
fontWeight: 500,
letterSpacing: '-.035em',
lineHeight: 'var(--site-h2-line)',
color: color.ink,
margin: 0,
},
h3: {
fontFamily: font.sans,
fontSize: 'var(--site-h3-size)',
fontWeight: 500,
letterSpacing: '-.025em',
lineHeight: 'var(--site-h3-line)',
color: color.ink,
margin: 0,
},
/**
* The workhorse section marker. Small uppercase tracked label over a hairline.
* This is the editorial signature of the site. Use it instead of a big bold
* heading for homepage and index sections.
*/
label: {
fontFamily: font.sans,
fontSize: '13px',
fontWeight: 600,
letterSpacing: '0',
textTransform: 'none',
lineHeight: 1.2,
color: color.muted,
margin: 0,
},
/** Opening statement. One per page, directly under the title. */
lead: {
fontFamily: font.sans,
fontSize: 'var(--site-lead-size)',
fontWeight: 400,
lineHeight: 'var(--site-lead-line)',
color: color.body,
maxWidth: measureTight,
margin: 0,
},
/** Long-form prose. */
body: {
fontFamily: font.sans,
fontSize: 'var(--site-body-size)',
fontWeight: 400,
lineHeight: 'var(--site-body-line)',
color: color.body,
maxWidth: measure,
margin: 0,
},
/** Supporting copy inside cards and rows. */
small: {
fontFamily: font.sans,
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.55,
color: color.muted,
margin: 0,
},
/** Dates, statuses, captions. */
meta: {
fontFamily: font.sans,
fontSize: '12px',
fontWeight: 400,
lineHeight: 1.5,
color: color.faint,
margin: 0,
},
/** A single number carrying weight. Revenue, distance, a count. */
display: {
fontFamily: font.sans,
fontSize: '36px',
fontWeight: 700,
letterSpacing: '-0.035em',
lineHeight: 1,
color: color.ink,
margin: 0,
},
/** Status pills and live markers. Mono earns its place here. */
mono: {
fontFamily: font.mono,
fontSize: '11px',
fontWeight: 500,
letterSpacing: '0.04em',
lineHeight: 1.4,
color: color.faint,
margin: 0,
},
} satisfies Record