@n8mills/design-tokens 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md ADDED
@@ -0,0 +1,429 @@
1
+ ---
2
+ # The value blocks below (colors, typography, rounded, spacing, elevation) are GENERATED from
3
+ # tokens.json by tokens/designmd.mjs and drift-gated on deploy. Do not hand-edit between the
4
+ # `# DESIGN:*:start` / `# DESIGN:*:end` markers; edit tokens.json and regenerate. The prose, the
5
+ # component recipe map, and everything outside the markers is hand-authored.
6
+ version: 1.0.0
7
+ name: Nate Mills Portfolio
8
+ description: >
9
+ A design-systems consultant's portfolio where the system IS the pitch. The voltage is a single
10
+ high-chroma lime held as a scarce accent against warm off-white and warm near-black, with honest
11
+ hairline borders instead of decorative shadows or gradients. It counter-positions against the
12
+ typical portfolio that scatters colour and leans on drop shadows: here depth is carried by a
13
+ surface ladder and 1px hairlines, type hierarchy comes from weight not opacity, and every value
14
+ traces to a token. Both a light theme (off-white canvas, ink accent) and a dark theme (near-black
15
+ canvas, lime accent) are first-class, and the whole thing is authored to WCAG 2.2 AA in both.
16
+
17
+ colors:
18
+ # DESIGN:colors:start
19
+ # Brand identity (fixed, never theme-flips)
20
+ brand-lime: "#d2ff37" # --brand-lime
21
+ brand-lime-dim: "#b8e030" # --brand-lime-dim
22
+ brand-lime-vivid: "#eeff00" # --brand-lime-vivid
23
+ brand-ink: "#1c1c1f" # --brand-ink
24
+ brand-gradient: "linear-gradient(135deg, #d2ff37 0%, #eeff00 100%)" # --brand-gradient
25
+
26
+ # Light theme semantics (the default page)
27
+ bg: "#f5f5f5" # --color-bg
28
+ surface: "#ffffff" # --color-surface
29
+ surface-sunken: "#ececec" # --color-surface-sunken
30
+ surface-inverse: "#0a0a0b" # --color-surface-inverse (always-dark, both themes)
31
+ text-primary: "#0a0a0b" # --color-text-primary
32
+ text-secondary: "#3f3f46" # --color-text-secondary
33
+ text-tertiary: "#67676f" # --color-text-tertiary
34
+ border: "#e4e4e7" # --color-border
35
+ border-strong: "#d4d4d8" # --color-border-strong
36
+ border-brand: "#d2ff37" # --color-border-brand (lime, fixed both themes)
37
+ accent: "#1c1c1f" # --color-accent (theme-aware: ink light, lime dark)
38
+ accent-hover: "#0a0a0b" # --color-accent-hover
39
+ on-accent: "#ffffff" # --color-on-accent
40
+ focus-ring: "#1c1c1f" # --color-focus-ring (theme-aware)
41
+ link: "#1c1c1f" # --color-link (theme-aware)
42
+ link-hover: "#0a0a0b" # --color-link-hover
43
+
44
+ # Text on always-dark slabs (theme-invariant)
45
+ on-ink-primary: "rgba(255, 255, 255, 0.88)" # --color-on-ink-primary
46
+ on-ink-secondary: "rgba(255, 255, 255, 0.66)" # --color-on-ink-secondary
47
+ on-ink-muted: "rgba(255, 255, 255, 0.4)" # --color-on-ink-muted
48
+ on-ink-border: "rgba(255, 255, 255, 0.08)" # --color-on-ink-border
49
+
50
+ # Dark theme semantics (data-theme="dark" or OS dark)
51
+ dark-bg: "#0a0a0b" # --darkTheme-color-bg
52
+ dark-surface: "#1c1c1f" # --darkTheme-color-surface
53
+ dark-surface-sunken: "#141416" # --darkTheme-color-surface-sunken
54
+ dark-text-primary: "#f4f4f5" # --darkTheme-color-text-primary
55
+ dark-text-secondary: "#a1a1aa" # --darkTheme-color-text-secondary
56
+ dark-text-tertiary: "#909096" # --darkTheme-color-text-tertiary
57
+ dark-border: "#27272a" # --darkTheme-color-border
58
+ dark-accent: "#d2ff37" # --darkTheme-color-accent (brand lime, deliberate)
59
+ dark-accent-hover: "#b8e030" # --darkTheme-color-accent-hover
60
+ dark-focus-ring: "#d2ff37" # --darkTheme-color-focus-ring
61
+ dark-link: "#d2ff37" # --darkTheme-color-link
62
+
63
+ # Scrims
64
+ overlay-soft: "rgba(0, 0, 0, 0.35)" # --color-overlay-soft
65
+ overlay-base: "rgba(0, 0, 0, 0.62)" # --color-overlay-base
66
+ overlay-strong: "rgba(0, 0, 0, 0.88)" # --color-overlay-strong
67
+ # DESIGN:colors:end
68
+
69
+ typography:
70
+ families:
71
+ # DESIGN:type-families:start
72
+ display: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' # --font-display
73
+ body: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' # --font-body
74
+ mono: '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace' # --font-mono
75
+ serif: '"PT Serif", Georgia, serif' # --font-serif
76
+ # DESIGN:type-families:end
77
+ # DESIGN:type-styles:start
78
+ hero: # hero headline only
79
+ family: display
80
+ size: "clamp(40px, 9vw, 118px)" # --display-hero
81
+ weight: 600 # --display-weight
82
+ lineHeight: 1.05 # --line-display
83
+ tracking: "-0.04em" # --display-tight
84
+ h1: # every section heading
85
+ family: display
86
+ size: "clamp(34px, 5.8vw, 72px)" # --text-h1
87
+ weight: 600 # --display-weight
88
+ lineHeight: 1.05 # --line-display
89
+ tracking: "-0.04em" # --display-tight
90
+ h2: # card titles, sub-heads
91
+ family: display
92
+ size: "clamp(22px, 4vw, 32px)" # --text-h2
93
+ weight: 600 # --display-weight
94
+ lineHeight: 1.15 # --line-heading
95
+ tracking: "-0.02em" # --tracking-tight
96
+ card-title: # large feature-card titles
97
+ family: display
98
+ size: "clamp(22px, 4vw, 44px)" # --display-card-title
99
+ weight: 600 # --display-weight
100
+ lineHeight: 1.15 # --line-heading
101
+ tracking: "-0.04em" # --display-tight
102
+ stat-num: # key-metrics numerals
103
+ family: display
104
+ size: "clamp(40px, 7vw, 88px)" # --display-stat-num
105
+ weight: 600 # --display-weight
106
+ lineHeight: 1.05 # --line-display
107
+ tracking: "-0.04em" # --display-tight
108
+ intro: # section intro line
109
+ family: body
110
+ size: "clamp(20px, 5vw, 24px)" # --display-intro
111
+ weight: 400 # --weight-regular
112
+ lineHeight: 1.45 # --line-snug
113
+ tracking: "-0.02em" # --tracking-tight
114
+ body: # paragraphs
115
+ family: body
116
+ size: "16px" # --text-body
117
+ weight: 400 # --weight-regular
118
+ lineHeight: 1.5 # --line-body
119
+ tracking: 0 # --tracking-normal
120
+ body-compact: # footer, card bullets, dense UI
121
+ family: body
122
+ size: "14px" # --size-sm
123
+ weight: 400 # --weight-regular
124
+ lineHeight: 1.5 # --line-body
125
+ caption: # captions, footnotes
126
+ family: body
127
+ size: "12px" # --text-caption
128
+ weight: 400 # --weight-regular
129
+ lineHeight: 1.5 # --line-body
130
+ label: # uppercase eyebrows, chips, badges; line-height 1.4
131
+ family: mono
132
+ size: "11px" # --text-label
133
+ weight: 500 # --weight-medium
134
+ tracking: "0.08em" # --tracking-label
135
+ # DESIGN:type-styles:end
136
+
137
+ rounded:
138
+ # DESIGN:rounded:start
139
+ none: 0 # --radius-none (square)
140
+ sm: "8px" # --radius-sm (inputs, small controls)
141
+ md: "10px" # --radius-md (mid-size controls, insets)
142
+ lg: "14px" # --radius-lg (DEFAULT for cards and modals)
143
+ xl: "20px" # --radius-xl (prominent panels)
144
+ full: "999px" # --radius-full (pills, chips, badges, buttons, icon buttons)
145
+ # DESIGN:rounded:end
146
+
147
+ spacing:
148
+ # DESIGN:spacing:start
149
+ s1: "4px" # --space-1
150
+ s2: "8px" # --space-2
151
+ s3: "12px" # --space-3
152
+ s4: "16px" # --space-4
153
+ s5: "24px" # --space-5
154
+ s6: "32px" # --space-6
155
+ s7: "48px" # --space-7
156
+ s8: "64px" # --space-8
157
+ s9: "96px" # --space-9 (section padding lower bound)
158
+ s10: "128px" # --space-10 (section padding upper bound)
159
+ s11: "192px" # --space-11
160
+ s12: "256px" # --space-12
161
+ section-padding: "clamp(96px, 12vw, 128px)" # --section-padding
162
+ card-pad-compact: "16px" # --card-pad-compact
163
+ card-pad-standard: "24px" # --card-pad-standard
164
+ card-pad-spacious: "32px" # --card-pad-spacious
165
+ # DESIGN:spacing:end
166
+
167
+ elevation:
168
+ # DESIGN:elevation:start
169
+ none: "none" # --shadow-none (default; borders carry depth)
170
+ soft: "0 1px 2px rgb(28 28 31 / 0.04), 0 1px 3px rgb(28 28 31 / 0.06)" # --shadow-soft (rare resting lift)
171
+ lift: "0 4px 12px rgb(28 28 31 / 0.06), 0 2px 4px rgb(28 28 31 / 0.04)" # --shadow-lift (overlays / float-on-scroll only)
172
+ modal: "0 24px 64px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.18)" # --shadow-modal (lightboxes above a scrim)
173
+ # DESIGN:elevation:end
174
+ focus: "0 0 0 2px {colors.focus-ring}" # 2px keyboard-focus outline, offset 2px
175
+
176
+ # Component recipes reference {token} names that resolve against the blocks above, so a value change
177
+ # propagates automatically and no raw value can drift here. Each variant is its own entry.
178
+ components:
179
+ nav:
180
+ background: "transparent (frosted glass pill once scrolled)"
181
+ textColor: "{colors.text-tertiary}"
182
+ typography: "{typography.label}"
183
+ activeTextColor: "{colors.text-primary}"
184
+ activeUnderline: "1px, {colors.accent} (ink on light, lime on dark)"
185
+ note: "State reads from underline shape PLUS colour, never colour alone."
186
+ hero-headline:
187
+ typography: "{typography.hero}"
188
+ textColor: "{colors.text-primary}"
189
+ emphasisWord: "one word may be italic and a muted colour; never the whole heading"
190
+ button-primary:
191
+ backgroundColor: "{colors.brand-lime}"
192
+ textColor: "{colors.brand-ink}"
193
+ border: "1px solid {colors.border-brand}"
194
+ typography: "{typography.label} at 14px, weight 600"
195
+ rounded: "{rounded.full}"
196
+ padding: "10px 18px"
197
+ height: "44px (md); 32px (sm); 52px (lg)"
198
+ button-primary-hover:
199
+ backgroundColor: "{colors.brand-ink}"
200
+ textColor: "{colors.brand-lime}"
201
+ button-secondary:
202
+ backgroundColor: "transparent"
203
+ textColor: "{colors.text-primary}"
204
+ border: "1px solid {colors.text-tertiary}"
205
+ rounded: "{rounded.full}"
206
+ padding: "10px 18px"
207
+ button-secondary-hover:
208
+ backgroundColor: "{colors.surface-sunken}"
209
+ border: "1px solid {colors.text-primary}"
210
+ button-icon:
211
+ shape: "circle, {rounded.full}"
212
+ border: "1px solid {colors.text-tertiary}"
213
+ iconColor: "{colors.text-secondary}"
214
+ size: "44px, glyph 16px"
215
+ note: "The one canonical icon button. An icon-only button is never a pill with a label."
216
+ card:
217
+ backgroundColor: "{colors.surface}"
218
+ textColor: "{colors.text-primary}"
219
+ border: "1px solid {colors.border}"
220
+ rounded: "{rounded.lg}"
221
+ padding: "{spacing.card-pad-standard}"
222
+ hover: "outline and slight lift, NOT a fill"
223
+ card-dark:
224
+ backgroundColor: "{colors.brand-ink}"
225
+ textColor: "{colors.on-ink-primary}"
226
+ border: "1px solid {colors.on-ink-border}"
227
+ carousel-nav:
228
+ layout: "bare chevron buttons and a counter on a rail below the cards"
229
+ dotActive: "{colors.accent}"
230
+ dotInactive: "{colors.text-tertiary} (tuned to clear 3:1)"
231
+ dotInactiveOnInverse: "{colors.on-ink-muted}"
232
+ counter: "{typography.label} mono"
233
+ badge:
234
+ backgroundColor: "transparent"
235
+ textColor: "{colors.text-secondary}"
236
+ border: "1px solid {colors.border}"
237
+ typography: "{typography.label} mono, weight 500"
238
+ rounded: "{rounded.full}"
239
+ padding: "4px 12px"
240
+ dot: "{colors.brand-lime} (6px, stays lime in both themes; the word carries meaning)"
241
+ chip:
242
+ backgroundColor: "{colors.surface-sunken}"
243
+ textColor: "{colors.text-primary}"
244
+ border: "1px solid {colors.border}"
245
+ typography: "{typography.label} mono, weight 500"
246
+ rounded: "{rounded.full}"
247
+ padding: "4px 10px"
248
+ footer:
249
+ backgroundColor: "{colors.brand-lime}"
250
+ textColor: "{colors.brand-ink}"
251
+ note: "A fixed lime slab, lime in BOTH themes; one of the few large lime fills allowed."
252
+ link:
253
+ textColor: "{colors.link}"
254
+ decoration: "underline"
255
+ hover: "{colors.link-hover}"
256
+ external: "opens a new tab, rel set, trailing up-right arrow glyph plus a screen-reader cue"
257
+ ---
258
+
259
+ # Nate Mills Portfolio: Brand Contract
260
+
261
+ This is a portable brand contract. Hand it to any AI coding agent, or a contractor, and it has
262
+ everything needed to build a page that feels like natemills.me: no repo, no build tools, just this
263
+ file. Every colour, size, radius, and spacing value in the frontmatter above is resolved to a literal
264
+ and generated straight from the design token source, so it cannot drift from the real system.
265
+
266
+ ## Overview
267
+
268
+ This is the portfolio of a Senior Product Designer (Design Systems). The site is the argument: a
269
+ calm, Swiss-minimal editorial surface where a single high-chroma lime does all the talking and
270
+ everything else stays quiet. The atmosphere is warm off-white paper in light mode and warm near-black
271
+ in dark mode, never pure white and never pure black. Depth is built from a surface ladder and honest
272
+ 1px hairlines, not from drop shadows or decorative gradients. Type is Inter, set large and tight for
273
+ display, with JetBrains Mono for eyebrows and labels and PT Serif reserved for editorial quotes only.
274
+
275
+ The counter-position is deliberate: most portfolios scatter accent colour across whole sections and
276
+ lean on soft shadows to fake hierarchy. This one refuses both. Colour is scarce, borders are honest,
277
+ and hierarchy comes from weight and scale. If you are building a new page, the system is the pitch:
278
+ make it look composed, restrained, and inevitable.
279
+
280
+ ### Key characteristics
281
+
282
+ - Warm off-white canvas (`{colors.bg}`), not white. Ink is warm near-black, not pure black.
283
+ - One accent, one lime (`{colors.brand-lime}`), used scarcely. Never a section fill or body text.
284
+ - Borders over shadows. Shadows are rare and reserved for overlays.
285
+ - Display type is Inter at weight 600, tight tracking. Never italic as a whole heading.
286
+ - Mono (JetBrains Mono) is for uppercase eyebrows, chips, and badges only.
287
+ - Both themes are first-class. In dark mode the accent IS the lime, by design.
288
+ - WCAG 2.2 AA in both themes is a floor, not a feature.
289
+
290
+ ## Color roles
291
+
292
+ The palette is exactly two families: a warm neutral ramp and one lime. There is no red, amber, or
293
+ blue in the system (external brand colours like a client blue are locked constants used only inside
294
+ their own case-study or logo context, never as system colour).
295
+
296
+ **Brand voltage rule: use the lime (`{colors.brand-lime}`) ONLY for** the primary button fill, brand
297
+ outlines (`{colors.border-brand}`), focus rings on dark surfaces, the lime period after a section
298
+ title, the active carousel dot on dark, status dots, and the footer slab. **Never** use lime as body
299
+ text, as a link on a light background, as a section background, or as a large fill on the light page.
300
+ On the off-white canvas raw lime composites to roughly 1.06:1 and fails WCAG outright.
301
+
302
+ **The theme-aware accent.** `{colors.accent}` is the one interactive role that follows the theme: it
303
+ resolves to ink on light and to lime on dark. Anything that signals an active or selected state
304
+ (current nav item, pressed toggle, active dot) uses `accent`, never raw lime, so it stays legible on
305
+ the light page. `{colors.border-brand}` is the deliberate exception: it stays fixed lime in both
306
+ themes because it is a brand-identity outline, not a state signal.
307
+
308
+ **Dark-mode accent is the lime, deliberately.** On the dark theme, `accent`, `link`, and `focus-ring`
309
+ all resolve to brand lime. This is a locked brand ruling, not a contrast bug: lime on near-black reads
310
+ at roughly 10:1 (AAA). Do not "fix" it to a near-white. What is forbidden in dark mode is a solid lime
311
+ panel; the calm dark treatment is charcoal with lime text, not lime wallpaper.
312
+
313
+ **Text on dark slabs.** Some surfaces (testimonials, contact) stay near-black in both themes. Text on
314
+ them uses the `on-ink-*` family (88% / 66% / 40% white), never the theme-aware text tokens. Do not use
315
+ an `on-ink` colour on a light surface; it is white-on-dark only and will vanish on a white card.
316
+
317
+ ## Typography rules
318
+
319
+ - Weight and scale carry hierarchy, never opacity. Do not dim body text with alpha to make it read
320
+ secondary; step down a text style instead.
321
+ - Display is always roman. A full italic heading is banned. Italic is allowed on a single word or short
322
+ phrase inside a heading, and only when paired with a muted colour to mark it as secondary emphasis.
323
+ - Eyebrows and labels are uppercase JetBrains Mono with positive tracking (0.08em labels, 0.18em
324
+ eyebrows) to mark them as taxonomy.
325
+ - Body copy is `text-align: start`, ragged right. Never justify.
326
+ - Line length caps at 52ch. The 11px mono label is the floor; never go smaller.
327
+
328
+ ## Component stylings
329
+
330
+ - **Buttons** are full pills, Inter semibold at 14px, padding 10px/18px, default height 44px. The
331
+ primary button is a lime fill with ink label; on hover it inverts to an ink fill with a lime label.
332
+ The secondary button is transparent with a `{colors.text-tertiary}` outline (that outline colour is
333
+ chosen to clear the 3:1 non-text contrast floor).
334
+ - **Icon button** is the one canonical circle: a `{colors.text-tertiary}` hairline ring,
335
+ `{colors.text-secondary}` glyph, 44px, `{rounded.full}`. Never a pill with a label.
336
+ - **Cards** sit on `{colors.surface}` with a 1px `{colors.border}` hairline and `{rounded.lg}`, padded
337
+ 24px (compact 16, spacious 32). Interaction is outline-plus-lift, not a background fill. Cards on a
338
+ dark slab use the `card-dark` recipe.
339
+ - **Chips vs badges vs counters** resolve by job: a label is a chip, a status is a badge, a count is
340
+ the carousel counter. A badge is read-only and never interactive; its dot stays lime in both themes
341
+ but colour is never the only signal, every status also carries a word.
342
+ - **Carousel nav** is bare chevrons plus a mono counter on a rail below the cards. The active dot
343
+ follows `{colors.accent}`; the inactive dot is a solid tuned grey (not a translucent one, which
344
+ would fail 3:1).
345
+ - **Footer** is a fixed lime slab with ink text, lime in both themes.
346
+ - **Links** are underlined, `{colors.link}` (ink on light, lime on dark). External links open a new
347
+ tab, set `rel`, and carry a trailing up-right arrow glyph plus a screen-reader cue.
348
+
349
+ ## Layout principles
350
+
351
+ - Content sits in a wide band that stays proportional on normal screens and caps at 1680px so
352
+ ultrawide layouts do not stretch without earning it.
353
+ - Vertical rhythm is `{spacing.section-padding}` between sections.
354
+ - Spacing is a strict 4px scale. Never use raw px for spacing; reach for a scale step.
355
+ - Full-bleed inverse bands (testimonials, contact) break out to the screen edge and stay near-black in
356
+ both themes.
357
+ - Stacking uses a named z-index scale with 100-unit gaps. Never hand-write a raw page-level z-index.
358
+
359
+ ## Depth and elevation
360
+
361
+ Depth is carried by the surface ladder plus hairline borders, not by shadows. The ladder, light to
362
+ recessed: `{colors.bg}` (page) to `{colors.surface}` (card, a genuine lift) to
363
+ `{colors.surface-sunken}` (wells and insets). Dark mode runs the same three steps in near-blacks. The
364
+ generated `elevation` values above are the light-theme shadows; dark mode swaps `soft` and `lift` to
365
+ deeper black-alpha. Radii: cards and modals use `{rounded.lg}`; small controls use `{rounded.sm}`;
366
+ pills, chips, badges, and buttons use `{rounded.full}`.
367
+
368
+ ## Do's and Don'ts
369
+
370
+ Do:
371
+ - Do keep the lime scarce. If two primary buttons would appear in one viewport, make one secondary.
372
+ - Do build hierarchy from weight and scale.
373
+ - Do use `{colors.accent}` for any active or selected state so it stays legible on light.
374
+ - Do give every status a word, not just a coloured dot.
375
+ - Do design the static, reduced-motion frame first; motion is garnish that never carries meaning.
376
+
377
+ Don't:
378
+ - Don't use pure white as the page canvas or pure black as ink. The canvas is `{colors.bg}`.
379
+ - Don't put lime on a light background as text, a link, or a large fill (it fails WCAG at ~1.06:1).
380
+ - Don't paint a solid lime panel in dark mode; use charcoal with lime text.
381
+ - Don't dim text with opacity to fake a secondary tone; step down a text style.
382
+ - Don't italicise a whole heading, and don't use an alpha colour for heading emphasis.
383
+ - Don't reach for a drop shadow to separate a card; use a hairline border.
384
+ - Don't justify body text, and don't drop below the 11px mono label floor.
385
+ - Don't leak an external client brand colour into general UI; those are locked constants for their
386
+ own case-study context only.
387
+
388
+ ## Responsive behavior
389
+
390
+ Reference breakpoints: mobile 375px, tablet 768px, desktop 1024px, wide 1440px, ultrawide 1600px.
391
+
392
+ - Type is fluid via `clamp()` and capped, so headings grow on wider screens and stop; they never balloon.
393
+ - At and below 768px, section-level primary CTAs go full-width and centre for a thumb-friendly tap
394
+ target; inline buttons inside paragraphs stay auto-width.
395
+ - Dense value tables tighten below ~640px but never drop a column and never shrink labels below 11px.
396
+ - Touch targets meet 44px; small dots keep a 24px hit area.
397
+ - Verify layout at ~375px alongside both light and dark themes whenever you touch a layout.
398
+
399
+ ## Agent prompt guide
400
+
401
+ When building a new page or prototype with this system:
402
+
403
+ 1. Work one component at a time and reference tokens by name (`{colors.accent}`, `{typography.h1}`,
404
+ `{rounded.lg}`), never by inventing a hex or px value.
405
+ 2. Decide first which surface a section sits on (`bg`, `surface`, `surface-sunken`, or an always-dark
406
+ `surface-inverse`), then pick text colours to match: `text-*` on light, `on-ink-*` on dark slabs.
407
+ 3. Keep the lime scarce. It appears on the primary button, brand outlines, focus rings on dark, status
408
+ dots, and the footer. Nowhere else.
409
+ 4. For any active or selected state use `{colors.accent}` (theme-aware), not raw lime, so it survives
410
+ the light theme.
411
+ 5. Build hierarchy from weight (600 display, 400 body) and scale, not opacity.
412
+ 6. Use hairline borders for separation; reach for a shadow only on an overlay or lightbox.
413
+ 7. Support both themes: the dark accent is intentionally the lime, and the dark treatment for a dark
414
+ surface is charcoal with lime text, never lime wallpaper.
415
+ 8. Hold WCAG 2.2 AA in both themes: 4.5:1 for body text, 3:1 for non-text and large text, visible
416
+ focus, 44px targets.
417
+
418
+ ## Known gaps
419
+
420
+ - This contract carries resolved values for portability; the source of truth authors every colour in
421
+ OKLCH as well, used automatically where the browser supports it. The values here are exact
422
+ round-trips of those OKLCH colours.
423
+ - The system has no semantic status colours (success/warning/danger); a portfolio has no such UI. If a
424
+ consumer needs them, add at the token layer first, then route a semantic.
425
+ - Fonts are Inter, JetBrains Mono, and PT Serif. Open-source substitutes if unavailable: Inter is
426
+ itself open source; fall back to a neutral grotesque for display and body, any monospace with a
427
+ slashed zero for labels, and a transitional serif (Georgia) for quotes.
428
+ - Exact computed contrast ratios are verified in a live specimen; the floors above (4.5:1 text, 3:1
429
+ non-text) are the targets to design to.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nate Mills
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/n8mills-UI/nate-mills-design-system/main/assets/header-banner.png" alt="Nate Mills Design System. Authored once as DTCG tokens, generated to CSS, audited to WCAG 2.2 AA." width="860">
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@n8mills/design-tokens?style=flat-square&labelColor=1c1c1f&color=d2ff37&logo=npm&logoColor=white)](https://www.npmjs.com/package/@n8mills/design-tokens)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-d2ff37?style=flat-square&labelColor=1c1c1f)](./LICENSE)
7
+ [![Design Tokens: DTCG](https://img.shields.io/badge/Design_Tokens-DTCG-d2ff37?style=flat-square&labelColor=1c1c1f)](https://www.designtokens.org/)
8
+ [![WCAG 2.2 AA](https://img.shields.io/badge/WCAG-2.2_AA-d2ff37?style=flat-square&labelColor=1c1c1f)](https://www.w3.org/WAI/WCAG22/quickref/)
9
+ [![Built with Style Dictionary](https://img.shields.io/badge/Built_with-Style_Dictionary-1c1c1f?style=flat-square)](https://styledictionary.com)
10
+
11
+ [![Design system, live](https://img.shields.io/badge/Design_system-Live-d2ff37?style=flat-square&labelColor=1c1c1f)](https://nate-mills-portfolio.netlify.app/#uth-colour)
12
+ [![Live site](https://img.shields.io/badge/Live-natemills.me-1c1c1f?style=flat-square)](https://natemills.me)
13
+ [![Case study](https://img.shields.io/badge/Case_study-Bupa-1c1c1f?style=flat-square)](https://bupa.natemills.me)
14
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-millsdesign-0a66c2?style=flat-square&logo=data:image/svg%2Bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0yMC40NDcgMjAuNDUyaC0zLjU1NHYtNS41NjljMC0xLjMyOC0uMDI3LTMuMDM3LTEuODUyLTMuMDM3LTEuODUzIDAtMi4xMzYgMS40NDUtMi4xMzYgMi45Mzl2NS42NjdIOS4zNTFWOWgzLjQxNHYxLjU2MWguMDQ2Yy40NzctLjkgMS42MzctMS44NSAzLjM3LTEuODUgMy42MDEgMCA0LjI2NyAyLjM3IDQuMjY3IDUuNDU1djYuMjg2ek01LjMzNyA3LjQzM2EyLjA2MiAyLjA2MiAwIDAxLTIuMDYzLTIuMDY1IDIuMDY0IDIuMDY0IDAgMTEyLjA2MyAyLjA2NXptMS43ODIgMTMuMDE5SDMuNTU1VjloMy41NjR2MTEuNDUyek0yMi4yMjUgMEgxLjc3MUMuNzkyIDAgMCAuNzc0IDAgMS43Mjl2MjAuNTQyQzAgMjMuMjI3Ljc5MiAyNCAxLjc3MSAyNGgyMC40NTFDMjMuMiAyNCAyNCAyMy4yMjcgMjQgMjIuMjcxVjEuNzI5QzI0IC43NzQgMjMuMiAwIDIyLjIyNSAweiIvPjwvc3ZnPg==&logoColor=white)](https://www.linkedin.com/in/millsdesign)
15
+
16
+ </div>
17
+
18
+ This is the real design system behind my portfolio, opened up so you can see how it is built, not just that it exists. It is small on purpose: one accent, a tuned neutral scale, two fonts, and a strict two-tier token model. Everything starts in one `tokens.json` file and propagates outward, so reskinning the brand is a one-line change and every component follows.
19
+
20
+ I publish it because the discipline is the point. A design system is easy to claim and hard to govern. This repo is the audit trail for the claims: the token source, the generated CSS, the component layer, and the written rules that keep them honest.
21
+
22
+ > **See it live.** The [under-the-hood design system](https://nate-mills-portfolio.netlify.app/#uth-colour) resolves every token in real time, in light and dark, section by section. This repo is the source it runs on.
23
+
24
+ ## What's inside
25
+
26
+ One source, three generated outputs, and the discipline between them. That is the whole system.
27
+
28
+ - **`tokens.json`** : the single source of truth, in the [W3C Design Tokens Community Group](https://www.designtokens.org/) (DTCG) format. Two tiers: primitives (raw values) and semantics (roles).
29
+ - **`tokens.css`** : the generated CSS custom properties, built from `tokens.json` by [Style Dictionary](https://styledictionary.com). All three themes (light, dark, high-contrast) included. Never hand-edited.
30
+ - **`tokens.js` / `tokens.d.ts`** : the same tokens as a typed JavaScript object, resolved to literal values for the default theme. For build tools that read tokens in JS, not CSS. Generated, never hand-edited.
31
+ - **`components.css`** : the component layer. Buttons, cards, chips, and the section heading pattern, each reading semantic tokens only.
32
+ - **[`design-system.md`](./design-system.md)** : the full write-up. Philosophy, the token model, the type and spacing scales, accessibility, contribution rules, and the content voice guide.
33
+ - **[`DESIGN.md`](./DESIGN.md)** : the portable brand contract. One self-contained file that hands an AI coding agent (or a contractor) everything it needs to build on-brand without the repo: resolved colour, type, spacing and radius values in the frontmatter, a component recipe map, and the do's and don'ts. The value blocks are generated from `tokens.json` and drift-gated, so they never fall out of sync with the system.
34
+
35
+ Prefer the guided tour to the raw source? The [live view](https://nate-mills-portfolio.netlify.app/#uth-colour) walks every section with the real values resolved.
36
+
37
+ ## The model in one breath
38
+
39
+ ```
40
+ PRIMITIVES raw values, named by hue and stop --> e.g. --neutral-850: #1c1c1f
41
+ SEMANTICS roles, reference primitives via var() --> e.g. --color-accent: var(--neutral-850)
42
+ COMPONENTS classes that read semantics only --> e.g. .btn--primary { background: var(--brand-lime) }
43
+ ```
44
+
45
+ One way, top to bottom: components read semantics, semantics read primitives, primitives are literals. Nothing reaches back up. So one edit at the top, a new brand hue, propagates through every semantic and component that uses it. No find-and-replace, no drift. That propagation is the whole point.
46
+
47
+ ## The foundations at a glance
48
+
49
+ <div align="center">
50
+ <picture>
51
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/n8mills-UI/nate-mills-design-system/main/assets/bento-foundations-dark.png">
52
+ <img src="https://raw.githubusercontent.com/n8mills-UI/nate-mills-design-system/main/assets/bento-foundations-lime.png" alt="The design system foundations in one view: the lime and ink colour ramps, the Inter display face, the type ladder, the motion curve, and the 12-step spacing scale" width="860">
53
+ </picture>
54
+ </div>
55
+
56
+ Colour, type, motion, and spacing, all generated from `tokens.json`. The type is Inter for everything, JetBrains Mono for labels and numbers, and PT Serif for the one place that earns it: the pull quotes.
57
+
58
+ ## Accessibility is a constraint, not a feature
59
+
60
+ It is wired into the tokens, so you cannot opt out of it by accident:
61
+
62
+ - Every text token is contrast-checked for WCAG 2.2 AA: a blocking CI lint over the token source in both themes, plus live ratio computation on the Under-the-Hood page.
63
+ - Focus rings are never removed without a replacement.
64
+ - Touch targets clear 44 by 44 px.
65
+ - Every animation is gated behind `prefers-reduced-motion`, collapsed in one place, not per component.
66
+
67
+ There is one honest carveout. The brand lime is a background and accent only. It fails as foreground text on the light surface, roughly 1.06 to 1, so the system refuses to let you use it there and hands you a theme-aware token instead. The full reasoning is in [`design-system.md`](./design-system.md#10-accessibility).
68
+
69
+ ## Use it
70
+
71
+ Install from npm:
72
+
73
+ ```bash
74
+ npm install @n8mills/design-tokens
75
+ ```
76
+
77
+ Pull in the CSS custom properties (light, dark, and high-contrast themes all included), through your bundler:
78
+
79
+ ```css
80
+ @import "@n8mills/design-tokens/css";
81
+ @import "@n8mills/design-tokens/components.css"; /* optional component layer */
82
+
83
+ .cta {
84
+ background: var(--brand-lime);
85
+ color: var(--brand-ink);
86
+ border-radius: var(--radius-md);
87
+ padding: var(--space-3) var(--space-5);
88
+ }
89
+ ```
90
+
91
+ Or read the resolved values in JavaScript or TypeScript (the default theme, fully typed):
92
+
93
+ ```js
94
+ import { tokens } from "@n8mills/design-tokens";
95
+
96
+ tokens["brand-lime"]; // "#d2ff37"
97
+ tokens["color-text-primary"]; // "#0a0a0b"
98
+ tokens["space-5"]; // "24px"
99
+ ```
100
+
101
+ The raw DTCG source ships too, for your own token pipeline:
102
+
103
+ ```js
104
+ import dtcg from "@n8mills/design-tokens/tokens.json" with { type: "json" };
105
+ ```
106
+
107
+ Prefer the source directly? Clone it:
108
+
109
+ ```bash
110
+ git clone https://github.com/n8mills-UI/nate-mills-design-system.git
111
+ ```
112
+
113
+ Read [`design-system.md`](./design-system.md) for the why. Read `tokens.json` and `components.css` for the how. Fork it for another brand by changing `--green-500` and watching every consumer follow. That is the system working as designed.
114
+
115
+ ## License
116
+
117
+ [MIT](./LICENSE). Use it, learn from it, build on it.
118
+
119
+ ---
120
+
121
+ <div align="center">
122
+
123
+ Built by Nate Mills, Design Systems and Full-Stack Design.
124
+ [natemills.me](https://natemills.me) | [LinkedIn](https://www.linkedin.com/in/millsdesign)
125
+
126
+ </div>