design.
Clean card-based minimal. Light-first, near-white surfaces, one blue primary, five accent hues that each own a section.
Every sample here is rendered from src/design/tokens.json. No hardcoded values, so it cannot go stale.
color
surface
backgrounds and edges. what things sit on.
page background base
page gradient midpoint
page gradient end
raised card surface
inset wells, code blocks
hairlines, card borders
content
text. three weights of emphasis, all AA on every surface.
primary text
secondary text, captions
tertiary text, metadata
brand
the one blue. links, CTAs, focus rings.
links, CTAs, focus ring
accent text on light surfaces (AA-safe)
label sitting on an accent fill
section
one hue per bento section. fills and dots, never body text.
sights section accent. Fills and dots only.
sounds section accent. Fills and dots only.
curiosity section accent, live status. Fills and dots only.
creativity section accent. Fills and dots only.
destructive fill
sights TEXT on its own tint (AA-safe)
sounds TEXT on its own tint (AA-safe)
curiosity TEXT on its own tint (AA-safe)
creativity TEXT on its own tint (AA-safe)
destructive TEXT (AA-safe on card)
accent ramp
fixed tints of the primary. components use sunrise-600 for hover fills.
type
families
--font-displayAdam Pang . 2026
Space Grotesk 700. Headings.
--font-bodyAdam Pang . 2026
Lato 300/400/700. Prose.
--font-monoAdam Pang . 2026
JetBrains Mono. Numerals, labels.
scale
--text-caption12px. Metadata, footnotes.
--text-label13px. Eyebrows, nav.
--text-body16px. Default prose.
--text-lead18px. Intro paragraphs.
--text-h324px. Card titles.
--text-h230px. Section titles.
--text-h136px. Page title. 3rem at sm+.
tracking
--tracking-tightest-0.02emdisplay headings--tracking-tighter-0.01emsubheads--tracking-label0.12emuppercase eyebrows--tracking-wide0.22emuppercase kickersspace
a 4px base step. mobile gutters and card padding use --space-5, opening to --space-6 at sm+.
--space-1--space-2--space-3--space-4--space-5--space-6--space-8--space-10--space-14radius + shadow
radius
--radius-sm--radius-md--radius-lg--radius-fullshadow
--shadow-card--shadow-card-md--shadow-card-lgcomponents
hover and press the real controls, and tab through them to see :focus-visible, a 2px accent outline at 3px offset set globally. States marked simulated are frozen copies so the styling is visible without a pointer.
button
rounded-full, accent fill. the primary CTA.
button . secondary
card surface, hairline border. the alternative action.
link
1px underline, hairline decoration until hover.
input
these are real inputs. click and tab through them.
badge
status pill. section hues as fills, never as text.
card
--color-card on --color-line at --radius-lg. the standard container.
resting
shadow-card, no transform
hover
lift 2px, accent border, shadow-card-lg
motion
one easing curve everywhere. all of it collapses to 0ms under prefers-reduced-motion: reduce, enforced globally rather than per component.
--motion-easecubic-bezier(0.16, 1, 0.3, 1)the one easing curve--motion-duration-micro200mshover, color change--motion-duration-state400msstate transition--motion-duration-reveal700msscroll reveal--motion-lift-2pxhover translateYbuilding something on-brand?
Fetch the tokens rather than reading hexes off this page. Both files are generated from the same source in the same build, are CORS-open, and carry every value shown above.
curl https://adampang.com/design/tokens.json
curl https://adampang.com/design/tokens.cssthree rules that keep work on-brand
- 01Reference the variable, never the hex. Write
var(--color-fg), not#1a1a1a. Every token is mode-aware, so one reference is correct in both themes. A literal hex is right in one mode and wrong in the other. - 02One accent, one hue per section.
--color-accentowns links, CTAs, and focus. Section hues are decorative fills and dots. Never set body text in one: at ~2:1 on white they fail AA, which is why--color-alert-inkexists separately from--color-alert. - 03Three text weights, no opacity.
--color-fg,--color-muted,--color-faint. Do not dim text with opacity to invent a fourth. That is exactly what failed AA here:text-ink/55measured 3.90:1 against a 4.5:1 requirement.
Identity data lives at /api/profile.json and /llms.txt.