@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.
@@ -0,0 +1,466 @@
1
+ # Nate Mills Design System
2
+
3
+ This is the system that powers my portfolio: the philosophy, the token model, the rules, and the
4
+ file map. It is small on purpose, and strict on purpose. If you came to see how a one-person design
5
+ system stays consistent without a framework or a build pipeline you have to babysit, this is the
6
+ whole thing.
7
+
8
+ **The bar.** This is a portfolio system, not a utilitarian toolkit. It is built to clear the bar,
9
+ not scrape it: considered polish, a little playfulness, and engineering rigour, from one person
10
+ doing design, systems, and build.
11
+
12
+ ---
13
+
14
+ ## Philosophy
15
+
16
+ A design system is easy to start and hard to keep honest. Most do not die at launch. They fall
17
+ apart later, quietly, when nobody is looking: a stray hex here, a one-off font size there, a card
18
+ that drifts a few pixels from its siblings. This system is built so that drift is hard to introduce
19
+ and easy to catch.
20
+
21
+ Five values shape every decision:
22
+
23
+ - **One source, one edit.** Every value lives once, in `tokens.json`. Change a primitive and every
24
+ semantic and component that references it follows. Reskinning the brand is a single line.
25
+ - **Accessible by default.** Accessibility is a constraint the system enforces, not a checklist bolted
26
+ on at the end. Tokens that cannot pass contrast are simply not allowed where they would fail.
27
+ - **Restraint over reach.** One accent, a tuned neutral scale, two fonts. No states the portfolio
28
+ does not use, no themes it does not need. The smaller the system, the easier it is to govern.
29
+ - **Plain platform.** CSS custom properties, semantic HTML, no runtime framework. The whole thing
30
+ loads in one network round trip and works even when opened as a local file.
31
+ - **Borrowed and proven, custom where it counts.** The architecture and the naming conventions follow
32
+ the leaders (DTCG, Material 3, Polaris, Tailwind), under one rule: match the naming to the scale,
33
+ words for small fixed sets, numbers for open ramps, intent for roles. The palette and the
34
+ proportions are hand-tuned for this portfolio. Eclectic on purpose, not by accident.
35
+
36
+ **Who this is for.** Engineers reading the token and component layers, designers reading the scales
37
+ and rules, and anyone evaluating whether the claims this portfolio makes about systems are real.
38
+
39
+ ---
40
+
41
+ ## Getting started
42
+
43
+ - **If you build interfaces:** start with `tokens.css` (the generated variables) and `components.css`
44
+ (the classes that consume them). Read the semantic table below to learn which role token to reach
45
+ for.
46
+ - **If you design:** start with the Typography, Spacing, and Colour sections. The scales are short
47
+ and the reasoning for each is written down.
48
+ - **If you write product copy:** jump to Content Voice and Tone at the end. The system governs words
49
+ as well as pixels.
50
+ - **If you maintain the system:** read the Contribution Rules and the Guardrails. They are the
51
+ difference between a system and a stylesheet.
52
+
53
+ For every token value in full detail, see the live Under-the-Hood page. The token tables in this
54
+ document and the live page are both generated straight from `tokens.json` and drift-gated in CI;
55
+ when a text description and a generated table disagree, trust the table.
56
+
57
+ ---
58
+
59
+ ## 1. The token model
60
+
61
+ ```
62
+ Tier 1 PRIMITIVES Raw values, named by hue and stop. No meaning on their own.
63
+ Tier 2 SEMANTICS Roles. Reference primitives through var(). Consumed by every component.
64
+ Tier 3 COMPONENTS CSS classes that read semantics only. The only layer that styles markup.
65
+ ```
66
+
67
+ The rule is one-way: components read semantics, semantics read primitives, primitives are literals.
68
+ No component reads a primitive directly. The one exception is the `[data-theme="dark"]` block, which
69
+ re-binds the same semantic names to a different set of primitives. This is the same shape that
70
+ Polaris, Carbon, and Material 3 standardise around, for the same reason: when you change a primitive,
71
+ every semantic that references it follows, and every component that reads those semantics follows.
72
+
73
+ **The pipeline.** The tiers are authored once in `tokens.json`, in the
74
+ [W3C Design Tokens Community Group](https://www.designtokens.org/) (DTCG) format. `tokens.css` is
75
+ generated from that source by [Style Dictionary](https://styledictionary.com), the open-source
76
+ standard token build tool. The same source could also emit iOS, Android, or JS from one edit; here
77
+ it emits the CSS the site consumes. The workflow is simple: edit `tokens.json`, run the build, never
78
+ hand-edit `tokens.css`. A drift guard rebuilds the tokens at deploy time and refuses to ship if the
79
+ committed CSS does not match what the source generates.
80
+
81
+ Honest scope note: DTCG is a Community Group format, the industry's interoperability standard, not a
82
+ formal W3C Recommendation, and the colour module is still settling. "DTCG-format" is accurate;
83
+ "fully spec-compliant" would not be.
84
+
85
+ ---
86
+
87
+ ## 2. Primitives
88
+
89
+ Defined once at `:root` in the generated `tokens.css`.
90
+
91
+ **Neutrals.** A custom 15-stop scale, tuned so the brand ink (`--neutral-850 #1c1c1f`) sits a touch
92
+ warmer than Tailwind's zinc-900. A few half-steps (`-150`, `-550`, `-850`) preserve values that the
93
+ rendered design actually lands on but that do not fall on a clean grid.
94
+
95
+ | Token | Hex | Where it shows up |
96
+ |---|---|---|
97
+ | `--neutral-0` | `#ffffff` | Card surface, text on accent |
98
+ | `--neutral-100` | `#f5f5f5` | Page background (brand off-white) |
99
+ | `--neutral-300` | `#e4e4e7` | Hairline border |
100
+ | `--neutral-700` | `#3f3f46` | Secondary text (light) |
101
+ | `--neutral-850` | `#1c1c1f` | Brand ink, accent (light), subtle surface (dark) |
102
+ | `--neutral-950` | `#0a0a0b` | Page background (dark), primary text (light) |
103
+
104
+ **Green (lime).** The only colour primitive the brand needs. `--green-500 #d2ff37` is the brand
105
+ lime; the rest are derived for hover and dim states.
106
+
107
+ **Authored in OKLCH.** The colour primitives are written in OKLCH, the perceptual colour space from
108
+ CSS Color Module Level 4 (by Björn Ottosson), as DTCG colour objects that also carry the original
109
+ hex. The build emits a hex fallback line then the `oklch()`, so the colour is identical everywhere
110
+ and pre-2023 browsers still get the hex. The OKLCH values are exact conversions of the hand-tuned
111
+ hexes, verified to land on the same pixel, not a regenerated ramp, so the lime keeps its character.
112
+
113
+ **What is deliberately missing.** No emerald, amber, or red. A portfolio has no warning or error UI,
114
+ and padding the system with state colours no component reads would be the opposite of the discipline
115
+ it is meant to show. If a future consumer needs them, the rule is: add at the primitive layer, route
116
+ a semantic, then consume that. Lime plus neutrals is the entire palette.
117
+
118
+ ---
119
+
120
+ ## 3. Semantics
121
+
122
+ Roles that reference primitives through `var()`. Every component reads these, never raw hex.
123
+
124
+ _Nine tokens shown here. The full set of surface, text, border, and accent tokens is on the live Under-the-Hood page._
125
+
126
+ <!-- TOKENS:pub-semantics:start -->
127
+
128
+ | Token | Light | Dark |
129
+ |---|---|---|
130
+ | `--color-bg` | `--neutral-100` | `--neutral-950` |
131
+ | `--color-surface` | `--neutral-0` | `--neutral-850` |
132
+ | `--color-surface-sunken` | `--neutral-200` | `--neutral-900` |
133
+ | `--color-text-primary` | `--neutral-950` | `--neutral-150` |
134
+ | `--color-text-secondary` | `--neutral-700` | `--neutral-500` |
135
+ | `--color-border` | `--neutral-300` | `--neutral-800` |
136
+ | `--color-border-brand` | `--brand-lime` | same |
137
+ | `--color-accent` | `--neutral-850` | `--green-500` |
138
+ | `--color-focus-ring` | `--neutral-850` | `--green-500` |
139
+
140
+ <!-- TOKENS:pub-semantics:end -->
141
+
142
+ **Brand identity aliases** sit alongside the role tokens: `--brand-lime` (`var(--green-500)`),
143
+ `--brand-ink` (`var(--neutral-850)`), and the paired "on" tokens that name the correct text colour
144
+ for a fixed-colour surface (`--brand-on-lime` resolves to ink, `--brand-on-ink` resolves to lime).
145
+
146
+ **The one rule worth memorising.** The base accent is ink, not colour. Lime is a background and
147
+ accent, never foreground text on a light surface. The contrast math forces it: `#d2ff37` on
148
+ `#f5f5f5` is far below the 4.5 to 1 floor (see the live Under-the-Hood page for current ratio). So the system gives you the right tool instead:
149
+ `--color-accent-emphasis` (theme-aware, resolves to ink on light and lime on dark) for brand-tinted
150
+ words in running copy, and the "on" tokens for text on fixed lime or ink surfaces. If you reach for
151
+ `color: var(--brand-lime)` outside a known-dark scope, it is almost always the wrong token.
152
+
153
+ ---
154
+
155
+ ## 4. Typography
156
+
157
+ **Two fonts, no more.** Inter for everything; JetBrains Mono for labels, eyebrows, code, and tabular
158
+ numbers. PT Serif appears in exactly one place: the testimonial pull quotes.
159
+
160
+ The lean semantic scale that new work consumes:
161
+
162
+ <!-- TOKENS:pub-type-semantic:start -->
163
+
164
+ | Token | Value | Used for |
165
+ |---|---|---|
166
+ | `--text-h1` | `clamp(34px, 5.8vw, 72px)` | Page-level section headings (every section h2). The hero h1 is a documented exception with its own mobile-tuned clamp. |
167
+ | `--text-h2` | `clamp(22px, 4vw, 32px)` | Card titles, intro lines, sub-headings inside section bodies. |
168
+ | `--text-body` | `--size-base` | Paragraphs, lists, default reading text, card body copy. |
169
+ | `--text-label` | `--size-2xs` | Uppercase mono labels, eyebrows, stat labels, badges. |
170
+ | `--text-caption` | `--size-xs` | Tiny captions, footnotes, stat descriptions. |
171
+ | `--text-nav` | `--size-2xs` | Header menu links; equals --text-label today, kept separate so nav can diverge. |
172
+
173
+ <!-- TOKENS:pub-type-semantic:end -->
174
+
175
+ Headings use fluid `clamp()` so type scales smoothly between a mobile floor and a desktop ceiling
176
+ without per-breakpoint overrides. If you find yourself reaching for a media query to nudge a font
177
+ size, the floor or the ceiling is wrong; fix the clamp, not the breakpoint.
178
+
179
+ Five weights are loaded from Inter (400 to 800). One emphasis rule matters: a full italic display
180
+ heading is banned. Italic on a single word inside a heading is allowed, paired with a solid muted
181
+ token, never an alpha-modified colour (overlapping italic glyphs at tight tracking double the alpha
182
+ and leave visible dark spots at the letter joins).
183
+
184
+ ---
185
+
186
+ ## 5. Spacing
187
+
188
+ A doubling rhythm from 4 to 256 px, with two medium-tight additions for breaks that need them.
189
+
190
+ <!-- TOKENS:pub-spacing:start -->
191
+
192
+ | Token | Value | Description |
193
+ |---|---|---|
194
+ | `--space-1` | `4px` | Hairline gap. Icon-to-label, tight inline spacing. |
195
+ | `--space-2` | `8px` | Tight gap. Chip padding, small stacks. |
196
+ | `--space-3` | `12px` | Medium-tight break (added between 2 and 4). Heading-to-body, compact rows. |
197
+ | `--space-4` | `16px` | Base unit. Default gap between related elements; the compact card-padding tier (--card-pad-compact). |
198
+ | `--space-5` | `24px` | Medium break (added between 4 and 6). Group separation inside a section; the standard card-padding tier (--card-pad-standard). |
199
+ | `--space-6` | `32px` | Large gap. Between subsections and card to card; the spacious card-padding tier (--card-pad-spacious). |
200
+ | `--space-7` | `48px` | Block spacing between stacked content blocks. |
201
+ | `--space-8` | `64px` | Major block separation and section inner padding. |
202
+ | `--space-9` | `96px` | Section rhythm. The lower bound of vertical section padding (--section-padding). |
203
+ | `--space-10` | `128px` | Section rhythm. The upper bound of vertical section padding (--section-padding). |
204
+ | `--space-11` | `192px` | Outsized spacing for full-bleed editorial breaks. |
205
+ | `--space-12` | `256px` | Largest step. Rare, marquee-scale vertical space. |
206
+
207
+ <!-- TOKENS:pub-spacing:end -->
208
+
209
+ Never use raw px for spacing in a component. The whole point is one place to edit the rhythm.
210
+
211
+ ---
212
+
213
+ ## 6. Radii, borders, shadows
214
+
215
+ <!-- TOKENS:pub-radii-borders-shadows:start -->
216
+
217
+ **Radii**
218
+
219
+ | Token | Value | Description |
220
+ |---|---|---|
221
+ | `--radius-none` | `0` | Square. No rounding, for sharp-cornered surfaces and full-bleed media. |
222
+ | `--radius-sm` | `8px` | Small radius. The workhorse for inputs, chips, and small controls. |
223
+ | `--radius-md` | `10px` | Medium radius. Mid-size controls and insets, a touch rounder than sm. |
224
+ | `--radius-lg` | `14px` | Large radius. The default for cards and modals. |
225
+ | `--radius-xl` | `20px` | Extra-large radius. Prominent panels and feature surfaces. |
226
+ | `--radius-full` | `999px` | Full round. Pills, tags, and circular buttons; forces a complete radius at any height. |
227
+
228
+ **Borders**
229
+
230
+ | Token | Value | Description |
231
+ |---|---|---|
232
+ | `--border-hairline` | `1px solid var(--color-border)` | Default 1px hairline border; reads --color-border so it follows the theme. The standard card edge and divider. |
233
+ | `--border-strong` | `1px solid var(--color-border-strong)` | Heavier 1px border; reads --color-border-strong. For edges that need to read past a hairline, such as interactive-card hover outlines. (Button borders moved to the btn.* component tokens in Task 87.) |
234
+ | `--border-focus` | `2px solid var(--color-focus-ring)` | 2px focus outline; reads --color-focus-ring (ink on light, lime on dark). The keyboard-focus indicator, never removed without a replacement. |
235
+ | `--focus-ring-offset` | `2px` | Offset between an element and its focus ring. Outsets the 2px ring so it clears the element edge. |
236
+
237
+ **Shadows**
238
+
239
+ | Token | Value | Description |
240
+ |---|---|---|
241
+ | `--shadow-none` | `none` | No shadow. The default; the system favours borders over shadows. |
242
+ | `--shadow-soft` | `0 1px 2px rgb(var(--brand-ink-rgb) / 0.04), 0 1px 3px rgb(var(--brand-ink-rgb) / 0.06)` | Subtle resting elevation, two stacked ink-alpha layers, for cards that lift just off the page. Dark mode swaps to deeper black-alpha. |
243
+ | `--shadow-lift` | `0 4px 12px rgb(var(--brand-ink-rgb) / 0.06), 0 2px 4px rgb(var(--brand-ink-rgb) / 0.04)` | Stronger elevation for overlays and float-on-scroll chrome only, not resting cards. |
244
+ | `--shadow-modal` | `0 24px 64px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.18)` | Modal and lightbox elevation. The deepest shadow, for surfaces floating above a scrim. |
245
+ | `--accent-period-shadow` | `0 1px 2px rgba(0,0,0,0.42), 0 0 0 1px rgba(0,0,0,0.18)` | Depth under the lime period in section titles. Keeps the lime dot legible on light surfaces; resolves to none in dark, where lime already reads. |
246
+ | `--accent-dot-shadow` | `0 1px 3px rgba(0,0,0,0.24)` | Depth under the active carousel pagination dot. Keeps the lime dot legible on light surfaces; resolves to none in dark. |
247
+
248
+ <!-- TOKENS:pub-radii-borders-shadows:end -->
249
+
250
+ The aesthetic is Swiss-minimal, so borders carry structure and shadows are rare. Two small
251
+ accent-shadow tokens keep the lime accents legible on light backgrounds and resolve to `none` in
252
+ dark mode, where lime already reads cleanly.
253
+
254
+ ---
255
+
256
+ ## 7. Motion
257
+
258
+ <!-- TOKENS:pub-motion:start -->
259
+
260
+ **Durations**
261
+
262
+ | Token | Value | Used for |
263
+ |---|---|---|
264
+ | `--duration-fast` | `150ms` | Hover, focus, and micro state feedback. |
265
+ | `--duration-base` | `220ms` | Reveal, modal, and swap transitions. |
266
+ | `--duration-slow` | `400ms` | Context shift and slide transitions. |
267
+ | `--duration-expressive` | `1300ms` | Expressive tier: the slow, cinematic duration for a single large hero moment (the two-phone Spline scene scales in over this). Deliberately longer than the general-purpose slow tier and reserved for one large-surface entrance, never UI feedback. Collapsed to 1ms under reduced motion by the block in tokens/build.mjs. |
268
+
269
+ **Easings**
270
+
271
+ | Token | Value | Used for |
272
+ |---|---|---|
273
+ | `--ease-default` | `cubic-bezier(0.4, 0, 0.2, 1)` | The workhorse symmetric curve. Begins and ends on screen. |
274
+ | `--ease-entrance` | `cubic-bezier(0, 0, 0.2, 1)` | Decelerate, for elements entering the screen. |
275
+ | `--ease-exit` | `cubic-bezier(0.4, 0, 1, 1)` | Accelerate, for elements leaving the screen. |
276
+ | `--ease-emphasized` | `cubic-bezier(0.16, 1, 0.3, 1)` | Pronounced ease-out for overlay and menu entrance motion (items rising into view). A stronger, slower-settling decelerate than --ease-entrance. |
277
+ | `--ease-linear` | `cubic-bezier(0, 0, 1, 1)` | Constant rate, no acceleration. For continuous loop motion: the hero ticker, both marquees, the glass column scroll, and the loading-icon cycles. Authored as a cubic-bezier twin of the linear keyword so it lives in the ease group with its siblings. |
278
+
279
+ <!-- TOKENS:pub-motion:end -->
280
+
281
+ A lean ladder: three general-purpose steps plus one `expressive` tier for a single large hero moment, and role-named easings.
282
+
283
+ Every animation respects `prefers-reduced-motion`. A single override at the bottom of `tokens.css`
284
+ collapses every duration to 1 ms and disables transforms, so honouring the preference is automatic,
285
+ not per-component.
286
+
287
+ ---
288
+
289
+ ## 8. Layout
290
+
291
+ ```
292
+ --container-base 960px (prose, single-column sections)
293
+ --container-wide clamp(1200px, 92vw, 1440px) (galleries, grids)
294
+
295
+ --bp-mobile 375px --bp-tablet 768px --bp-desktop 1024px --bp-wide 1440px
296
+ ```
297
+
298
+ Sections run full-bleed at the outer level and centre their content in one of the two container
299
+ widths, so every section's gutter aligns.
300
+
301
+ ---
302
+
303
+ ## 9. Exemplar components
304
+
305
+ Components live in `components.css`, read semantic tokens only, and document their variants. Three
306
+ are worth showing in full, because they carry most of the system's decisions.
307
+
308
+ ### Button
309
+
310
+ | Variant | Background | Text | Use on |
311
+ |---|---|---|---|
312
+ | `.btn--primary` | `--brand-lime` | `--brand-ink` | Off-white surfaces |
313
+ | `.btn--secondary` | transparent | `--color-text-primary` | Lime surfaces |
314
+ | `.btn--icon` | transparent | `--color-text-secondary` | Chrome (toggles, controls) |
315
+
316
+ The pairing rule is strict: lime-on-lime is forbidden, so `.btn--primary` never sits on a lime
317
+ background; the secondary variant carries contrast there with ink text and a border. Icons trail the
318
+ label and map to action type: `mail` for `mailto:`, `external-link` for outbound links, `download`
319
+ for files, no icon for same-page anchors. `arrow-up-right` is not a substitute for `external-link`;
320
+ they mean different things to a screen-reader user.
321
+
322
+ ### Card
323
+
324
+ `.card` is the light-surface base: off-white background, hairline border, soft radius. `.card--dark`
325
+ is the modifier for dark slabs: it swaps the surface to ink, the text to the on-ink tokens, and the
326
+ border to the on-ink hairline, while inheriting the radius and border style from `.card`. Compose it
327
+ as `class="card card--dark"`. A surface that must stay dark in both themes uses these fixed tokens,
328
+ never a pinned `data-theme`, because a pinned theme propagates when an element is cloned and silently
329
+ breaks dark mode.
330
+
331
+ ### The section heading triplet
332
+
333
+ Every section uses one heading pattern: a label, a title, and a subtitle. Shape signals role across
334
+ the whole system: pills are identity (status, taxonomic tags), rounded rectangles are actions
335
+ (buttons, cards), circles are people and brands (avatars, social icons), squares are chrome
336
+ utilities (toggles). Mixing those metaphors is the fastest way to make an interface feel arbitrary,
337
+ so the system names the rule and holds to it.
338
+
339
+ ---
340
+
341
+ ## 10. Accessibility
342
+
343
+ WCAG 2.2 AA is the floor here, not the ceiling. WCAG 2.2 is a W3C Recommendation, and since October
344
+ 2025 it is also the international standard ISO/IEC 40500:2025. Every AA claim on this site is a
345
+ self-assessment, backed by an axe-core gate that runs on both themes on every deploy, not a
346
+ third-party certification.
347
+
348
+ I also explore APCA, the perceptual contrast model, as a forward-looking readability ceiling above
349
+ that floor. APCA is not a conformance standard: it was pulled from the WCAG 3 Working Draft in 2023,
350
+ and WCAG 3 is still years from being a standard, so I treat APCA as a sharper readability check,
351
+ never a compliance claim. The short version: WCAG 2.2 AA as the floor, APCA explored as a
352
+ forward-looking readability ceiling.
353
+
354
+ - WCAG 2.2 AA contrast for every text token. The token source is contrast-linted in CI (a blocking
355
+ Terrazzo check over the semantic pairs in both themes), and every swatch on the live
356
+ Under-the-Hood page computes its ratio from the rendered tokens at load.
357
+ - The focus ring is a 2 px brand outline, never removed without a replacement.
358
+ - Touch targets clear 44 by 44 px.
359
+ - Every animation is gated behind `prefers-reduced-motion`.
360
+ - Semantic HTML first; ARIA only where HTML cannot carry the meaning.
361
+ - Lime is forbidden as foreground text on light surfaces. Decorative bars, the brand dot, and
362
+ dark-slab accents only.
363
+ - **Never dim accessible text with `opacity`.** Opacity multiplies against whatever sits behind the
364
+ element and silently drops a passing colour below 4.5 to 1. The token looks right in the source and
365
+ fails on screen. Choose a lighter text token that itself clears the floor.
366
+ - Every `<img>` declares `width` and `height` to prevent layout shift, which disorients screen-reader
367
+ and magnifier users tracking position.
368
+
369
+ ---
370
+
371
+ ## 11. File map
372
+
373
+ ```
374
+ tokens.json The single source of truth (DTCG format)
375
+ tokens.css Generated CSS custom properties (primitives + semantics)
376
+ components.css The component layer
377
+ design-system.md This document
378
+ ```
379
+
380
+ `tokens.json` is the source; `tokens.css` is generated and never hand-edited. (An inline token
381
+ mirror existed early on; it was removed when the tokens moved to the generated external file.)
382
+
383
+ ---
384
+
385
+ ## 12. Contribution rules
386
+
387
+ 1. Never write raw hex outside `tokens.css`. Define a primitive or use a semantic.
388
+ 2. Brand tokens are aliases, not duplicates: `--brand-lime: var(--green-500)`, not the hex.
389
+ 3. Components read semantics only, never a raw primitive.
390
+ 4. Test in light and dark. Every semantic has both.
391
+ 5. WCAG 2.2 AA for every text colour, against every surface it could land on.
392
+ 6. Every animation gets a reduced-motion collapse.
393
+ 7. One container width per section.
394
+ 8. The label, title, subtitle triplet is the only heading pattern.
395
+ 9. No full italic display headings.
396
+ 10. Be explicit about which viewport a CSS change targets, and verify at three widths before calling
397
+ it done.
398
+
399
+ ---
400
+
401
+ ## 13. What this system is NOT
402
+
403
+ - Not a component library. The custom build is the point; there is no DaisyUI or Material under it.
404
+ - Not Tailwind-driven. Tailwind is a one-off layout convenience, not the system.
405
+ - Not runtime CSS-in-JS. Plain custom properties, one network round trip.
406
+ - Not theme-able beyond light and dark. The Bupa case study scopes blue back inside its own section;
407
+ that is the only third theme, and it is section-scoped, never global.
408
+
409
+ Fork it for another brand by changing `--green-500` and watching every consumer follow. That is the
410
+ system working as designed.
411
+
412
+ ---
413
+
414
+ ## 14. Guardrails
415
+
416
+ The mistakes that have actually surfaced, kept here so they do not surface again.
417
+
418
+ - No blue outside the Bupa case study. The base accent is ink.
419
+ - Inter only. PT Serif for pull quotes, JetBrains Mono for labels and numbers.
420
+ - No full italic display headings.
421
+ - Surfaces are off-white or near-black only. No cream, no tinted "warm" backgrounds.
422
+ - Lime is never foreground text on a light surface.
423
+ - No raw values in components. Every colour, space, radius, and duration resolves to a token.
424
+
425
+ ---
426
+
427
+ ## 15. Content voice and tone
428
+
429
+ Consistency is linguistic, not just visual. A system that governs pixels but lets copy wander still
430
+ feels inconsistent. So the words follow one rule, the same way the colours do.
431
+
432
+ ### One voice
433
+
434
+ I write the way I'd talk to another designer over coffee: plain words, short sentences, say the
435
+ thing. No jargon, no sales pitch, no fake excitement. First person. Proof carries the brag: "18
436
+ hours back to the team," not "huge productivity gains." Warmth comes from honesty, not enthusiasm.
437
+ No hedges.
438
+
439
+ ### Only the length changes
440
+
441
+ The voice never changes; only the length does. Body copy gets whole sentences with room to breathe
442
+ (6 to 14 words each). Buttons and microcopy compress to verb plus outcome: "Read the case study,"
443
+ "Get in touch," never "Submit" or "Learn more." Errors name the problem and the workaround with no
444
+ apology preamble. Status pills are declarative present tense ("Available for work"), never
445
+ aspirational. Internal docs get the same voice at note length: present-tense facts, decisions
446
+ explained, not defended.
447
+
448
+ ### Word list
449
+
450
+ | Use | Avoid |
451
+ |---|---|
452
+ | system, token, governance, pipeline, foundation | passionate, world-class, cutting-edge, seamless |
453
+ | ship, scale, adoption, contribution | amazing, incredible, excited |
454
+ | multi-brand, cross-platform, token-driven | we believe, we think, arguably |
455
+ | let's talk, get in touch | click here, learn more, submit |
456
+
457
+ ### Copy patterns
458
+
459
+ - **CTA buttons:** verb plus outcome. "Read the case study," not "Click to learn more."
460
+ - **Section subtitle:** one sentence, sets the stake, 18 words or fewer.
461
+ - **Error message:** what happened plus the workaround, no "sorry."
462
+ - **Empty state:** what is missing, what to do.
463
+ - **Loading state:** set an expectation if it runs over a second, otherwise no copy.
464
+
465
+ Across all three registers: numbers do the bragging, headings are sentence case, and superlatives
466
+ stay out.
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@n8mills/design-tokens",
3
+ "version": "1.0.0",
4
+ "description": "The design tokens behind natemills.me: W3C DTCG source, generated CSS custom properties, the component layer, and a resolved JS/TS export. Two-tier, OKLCH-authored, WCAG 2.2 AA.",
5
+ "license": "MIT",
6
+ "author": "Nate Mills <mail@natemills.me> (https://natemills.me)",
7
+ "homepage": "https://github.com/n8mills-UI/nate-mills-design-system#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/n8mills-UI/nate-mills-design-system.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/n8mills-UI/nate-mills-design-system/issues"
14
+ },
15
+ "keywords": [
16
+ "design-tokens",
17
+ "design-system",
18
+ "dtcg",
19
+ "w3c-design-tokens",
20
+ "css-variables",
21
+ "custom-properties",
22
+ "oklch",
23
+ "style-dictionary",
24
+ "tokens",
25
+ "theme"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": [
29
+ "*.css"
30
+ ],
31
+ "main": "./tokens.js",
32
+ "module": "./tokens.js",
33
+ "types": "./tokens.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./tokens.d.ts",
37
+ "import": "./tokens.js",
38
+ "default": "./tokens.js"
39
+ },
40
+ "./css": "./tokens.css",
41
+ "./tokens.css": "./tokens.css",
42
+ "./components.css": "./components.css",
43
+ "./tokens.json": "./tokens.json",
44
+ "./package.json": "./package.json"
45
+ },
46
+ "files": [
47
+ "tokens.js",
48
+ "tokens.d.ts",
49
+ "tokens.css",
50
+ "tokens.json",
51
+ "components.css",
52
+ "design-system.md",
53
+ "DESIGN.md"
54
+ ],
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }