Menu
akaSTYLE

Library · Foundations

Tokens, scale & motion

The measurable half. Every number the system uses, in one place, with the reasoning attached — because a scale you can't justify is a scale you'll abandon. Drop the block at the bottom into a new repo and the primitives land correctly.

Color

Four tokens

Ground, ink, rule, accent. Everything else is one of those four at reduced opacity — which is why the system reads as one surface instead of a palette. OKLCH throughout, so lightness is perceptual and a dark-mode flip is a lightness change, not a re-pick.

tokenvalue
--backgroundoklch(0.145 0.004 106)
--foregroundoklch(0.93 0.003 106)
--borderoklch(0.27 0.004 106)
--primaryoklch(0.71 0.11 152)
--cardforeground @ 4%
--mutedforeground @ 8%

The opacity ladder. Text steps down foreground → /85 → muted-foreground → /70 → /50 → /40. Six steps is the whole hierarchy; if something needs a seventh, the layout is wrong, not the palette.

Space

The rhythm

A 4px base, but only six steps are ever used. Sections breathe at 64, cards at 20, and related things sit 8–12 apart. Constraint is the point: fewer choices, faster decisions, consistent result.

tokenvalue
gap-2
Icon to label, chip to chip
8px
gap-3
Buttons in a row, list items
12px
mt-4
Inside a card, between blocks
16px
p-5
Card padding — the default
20px
mt-10
Header to first section
40px
mt-16
Between sections
64px

Shape

Radius & rule

Radius scales with the surface: the bigger the box, the softer the corner. One border weight everywhere — 1px at token color. No shadows anywhere in the system; depth comes from surface lightness, not from a glow.

rounded4pxcode chips
rounded-md6pxtags, sm buttons
rounded-lg8pxbuttons, inputs, media
rounded-xl12pxcards, panels
rounded-fulldots, toggles, avatars

Type

The ramp

One family, four weights, and a ramp that leans light. Display sizes get extralight with negative tracking; small text gets medium with positive tracking. The inversion is deliberate — it's what makes small type read as a label rather than shrunken body copy.

Displaytext-[clamp(1.7rem,5vw,2.4rem)] font-extralight tracking-tight

Page title

Titletext-xl font-light tracking-tight

Section title

Bodytext-[13px] font-light leading-relaxed

The default paragraph, set light for long-form comfort.

Smalltext-[12px] font-light

Captions, secondary detail.

Kickertext-[11px] font-medium uppercase tracking-[0.18em]

SECTION LABEL

Monofont-mono text-[11px]

const token = value

Motion

Timings & the rule

One rule governs everything, including the brand engines: energy moves space, never brightness — scale, position, displacement. No strobe, no flash, no opacity pulsing. It keeps long loops watchable and the whole system safe for photosensitive viewers.

tokenvalue
150msHover, focus — color onlytransition-colors
200msTab content swapanimate-in fade-in duration-200
300msImage hover scaletransition-transform duration-300
450msScroll revealopacity + translateY(16px)
700msHero entrancemotion-safe:animate-in fade-in slide-in-from-bottom-2
120msStagger stepanimationDelay: step * 120ms

Always gated. Entrances use motion-safe: and canvas loops check prefers-reduced-motion before starting — reduced motion renders one representative frame, never a frozen blank.

Layout

Widths & breakpoints

tokenvalue
max-w-2xlWrite-ups — one column of prose672px
max-w-3xlReference pages, galleries768px
max-w-siteLanding, project index1200px
site-insetThe universal gutterpx-6
sm:One column → two640px
md:Stacked hero → side by side768px
lg:Two columns → three1024px

No device sniffing. Layout responds to width; touch-specific behavior responds to pointer: coarse and hover. There is no isMobile anywhere in any repo running this system.

Transfer

The whole system, in one block

Paste into globals.css and every primitive on this site renders correctly in the new repo. Tailwind v4 reads the variables directly — no config file needed.

@import "tailwindcss";

:root {
  --background:  oklch(0.985 0.002 106);
  --foreground:  oklch(0.185 0.004 106);
  --border:      oklch(0.90  0.003 106);
  --primary:     oklch(0.42  0.08  152);   /* the one accent */
  --card:        oklch(0.185 0.004 106 / 0.03);
  --muted:       oklch(0.185 0.004 106 / 0.06);
  --radius:      0.75rem;
}

.dark {
  --background:  oklch(0.145 0.004 106);
  --foreground:  oklch(0.93  0.003 106);
  --border:      oklch(0.27  0.004 106);
  --primary:     oklch(0.71  0.11  152);
  --card:        oklch(0.93  0.003 106 / 0.04);
  --muted:       oklch(0.93  0.003 106 / 0.08);
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-border:     var(--border);
  --color-primary:    var(--primary);
  --color-card:       var(--card);
  --color-muted:      var(--muted);
  --color-muted-foreground: color-mix(in oklch, var(--foreground) 62%, transparent);
}

/* the two layout helpers everything uses */
.max-w-site { max-width: 1200px; }
.site-inset { padding-inline: 1.5rem; }

With the block above plus primitives and the brand engine, a new repo starts with the same design language on day one — which is the entire point of writing this down.