@fracazo/design-system 0.2.1 → 0.7.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.
Files changed (46) hide show
  1. package/DESIGN.md +11 -1
  2. package/README.md +78 -13
  3. package/css/motion.css +155 -0
  4. package/css/roles.css +3 -0
  5. package/dist/guardrails/eslint.d.ts +72 -5
  6. package/dist/guardrails/eslint.js +197 -29
  7. package/dist/guardrails/init.d.ts +2 -0
  8. package/dist/guardrails/init.js +65 -0
  9. package/dist/guardrails/intake.d.ts +2 -0
  10. package/dist/guardrails/intake.js +131 -0
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/index.js +1 -0
  13. package/dist/src/ui/offer-card.d.ts +52 -0
  14. package/dist/src/ui/offer-card.js +7 -0
  15. package/package.json +8 -3
  16. package/skills/product-design/SKILL.md +142 -0
  17. package/skills/product-design/coverage-gaps.md +41 -0
  18. package/skills/product-design/exemplars/calm-the-offering-cards.md +33 -0
  19. package/skills/product-design/exemplars/clamp-drift-to-named-roles.md +37 -0
  20. package/skills/product-design/exemplars/concentric-radii-and-button-optics.md +36 -0
  21. package/skills/product-design/exemplars/dialog-close-focus-visible.md +36 -0
  22. package/skills/product-design/exemplars/hero-glow-seam.md +34 -0
  23. package/skills/product-design/intake/2026-09-07.md +413 -0
  24. package/skills/product-design/references/components.md +43 -0
  25. package/skills/product-design/references/copy.md +25 -0
  26. package/skills/product-design/references/intake.md +66 -0
  27. package/skills/product-design/references/motion.md +22 -0
  28. package/skills/product-design/references/rules.md +319 -0
  29. package/skills/product-design/references/surfaces.md +50 -0
  30. package/skills/product-design/references/tokens.md +54 -0
  31. package/skills/product-design/references/type-and-space.md +42 -0
  32. package/skills/product-design/references/verification.md +35 -0
  33. package/template/CLAUDE.md +47 -0
  34. package/template/README.md +16 -0
  35. package/template/eslint.config.mjs +20 -0
  36. package/template/gitignore +44 -0
  37. package/template/next.config.ts +7 -0
  38. package/template/package.json +40 -0
  39. package/template/pnpm-workspace.yaml +14 -0
  40. package/template/postcss.config.mjs +7 -0
  41. package/template/src/app/globals.css +66 -0
  42. package/template/src/app/layout.tsx +55 -0
  43. package/template/src/app/page.tsx +59 -0
  44. package/template/src/components/ThemeSync.tsx +21 -0
  45. package/template/src/system/brands/starter.css +143 -0
  46. package/template/tsconfig.json +34 -0
@@ -0,0 +1,319 @@
1
+ # Rules
2
+
3
+ Every rule has a stable ID, a scope, the rule, why, exceptions, its source
4
+ and an example pair. `Enforced by` says who catches a violation today:
5
+ `lint` (the package's ESLint plugin, rule `design-system/<id>`; `lint,
6
+ warn` means it ships at warning level until the product is clean),
7
+ `contract` (ds-check-brand), `base` (the house base layer in globals.css),
8
+ `prose` (this skill and review), or `lint candidate` (checkable by code, not
9
+ yet written; see coverage-gaps.md).
10
+ Cite the ID in findings. Add a rule only per SKILL.md's integrity section.
11
+
12
+ ## Colour
13
+
14
+ ### rule/no-colour-literal
15
+ Scope: any `className` in product code, stories and the showcase.
16
+ Rule: no hex, oklch(), rgb() or hsl() value, including arbitrary utilities
17
+ like `bg-[#fff]`.
18
+ Why: a literal drifts from the brand file and breaks dark mode silently.
19
+ Exceptions: renderers that cannot use CSS variables (react-pdf, email HTML,
20
+ OG images), listed per product in its ESLint config.
21
+ Source: DESIGN.md, Guardrails; guardrails/eslint.ts.
22
+ Enforced by: lint.
23
+ Bad: `className="bg-[#c2727a] text-white"`
24
+ Good: `className="bg-primary text-primary-foreground"`
25
+
26
+ ### rule/no-dark-pairs
27
+ Scope: any `className`.
28
+ Rule: never a hand-authored light and dark pair (`bg-x dark:bg-y`) for a
29
+ colour. The token owns both themes; write one class.
30
+ Why: two literals drift independently and the dark half is never reviewed.
31
+ Exceptions: `dark:` on a non-colour property (opacity, display) is fine.
32
+ Source: DESIGN.md, Reject list; BirthGuide SPEC_016.
33
+ Enforced by: lint (the arbitrary-value form). A `dark:` token beside its
34
+ light twin is still prose.
35
+ Bad: `bg-[#E6EFE2] dark:bg-[oklch(0.34_0.045_150)]`
36
+ Good: `bg-chip-1-soft`
37
+
38
+ ### rule/on-dark-ramp
39
+ Scope: children of an always-dark surface (`bg-dark`, `bg-dark-2`).
40
+ Rule: text and icons use the mode-constant on-dark ramp (`text-dark-ink` to
41
+ `text-dark-faint-2`, `text-dark-brand` for the one accent), never a
42
+ theme-varying token such as `text-ink` or `text-ink-3`.
43
+ Why: the surface never changes theme, so its text must not; `text-ink` on
44
+ the footer renders espresso on espresso in light mode.
45
+ Exceptions: none.
46
+ Source: DESIGN.md, Colour; BirthGuide usage story.
47
+ Enforced by: lint, warn (a theme-varying text token inside a JSX subtree
48
+ whose root carries `bg-dark`; a nested light surface resets the context).
49
+ Bad: `<footer className="bg-dark"><p className="text-ink">`
50
+ Good: `<footer className="bg-dark"><p className="text-dark-ink-2">`
51
+
52
+ ### rule/semantic-first
53
+ Scope: any styling decision.
54
+ Rule: semantic utilities (`bg-card`, `text-muted-foreground`,
55
+ `border-border`) before primitives (`bg-band`, `text-ink-3`); primitives
56
+ only where no role fits.
57
+ Why: semantics move together across products and themes; primitives are
58
+ brand material.
59
+ Exceptions: alternating bands, captions on light surfaces, chips, glows and
60
+ the always-dark family, which have no semantic role by design.
61
+ Source: DESIGN.md, Colour.
62
+ Enforced by: prose.
63
+ Bad: `text-ink-2` for body copy under a heading.
64
+ Good: `text-muted-foreground`.
65
+
66
+ ### rule/divergent-six
67
+ Scope: brand files and roles.css.
68
+ Rule: secondary, muted, border, input, muted-foreground and
69
+ accent-foreground hold their own literals per theme. Never alias them to a
70
+ primitive.
71
+ Why: dark tuned them away from the primitives; dark border and input are
72
+ translucent hairlines, nothing like the opaque warm line.
73
+ Exceptions: none.
74
+ Source: DESIGN.md, Tokens; roles.css header.
75
+ Enforced by: contract (the six are required in both light and dark) and
76
+ prose.
77
+ Bad: `--border: var(--line);` in the dark block.
78
+ Good: `--border: oklch(1 0 0 / 10%);`
79
+
80
+ ### rule/no-stock-palette
81
+ Scope: product UI (not PDFs or emails).
82
+ Rule: no Tailwind default palette colour (`amber-500`, `blue-600`,
83
+ `gray-200`) in a className.
84
+ Why: it competes with the brand, ignores dark mode and looks generic.
85
+ Exceptions: none in UI. Known violation: BirthGuide `PregnancyProgress.tsx`
86
+ (coverage gap).
87
+ Source: BirthGuide brand spec 9.2; DESIGN.md, Colour.
88
+ Enforced by: lint, warn (BirthGuide has 48 occurrences, mostly in the
89
+ calculator tools; a cleanup spec turns it to error).
90
+ Bad: `bg-amber-100 text-amber-800`
91
+ Good: `bg-highlight-soft text-highlight-ink`
92
+
93
+ ### rule/status-colour-means-preference
94
+ Scope: `status-want`, `status-ifnec`, `status-no` and their `-soft` washes.
95
+ Rule: use only to encode a reader's preference state; never as decoration
96
+ or as generic success and danger colours.
97
+ Why: colour that carries meaning must carry only that meaning.
98
+ Exceptions: none. Destructive actions use `destructive`.
99
+ Source: DESIGN.md, Colour; roles.css comment.
100
+ Enforced by: prose.
101
+ Bad: a green `bg-status-want-soft` behind a marketing testimonial.
102
+ Good: the "Want" chip on a preference card.
103
+
104
+ ## Radius, type and space
105
+
106
+ ### rule/no-radius-literal
107
+ Scope: any `className`.
108
+ Rule: radii are `rounded-sm` to `rounded-4xl` plus `rounded-20`. No
109
+ `rounded-[Npx]`.
110
+ Why: a new radius is a design decision; 34 hand-written 20px radii once
111
+ drifted before `rounded-20` named them.
112
+ Exceptions: none. A genuinely new radius becomes a token first.
113
+ Source: DESIGN.md, Radius.
114
+ Enforced by: lint.
115
+ Bad: `rounded-[14px]`
116
+ Good: `rounded-xl`
117
+
118
+ ### rule/concentric-radii
119
+ Scope: a rounded child whose corners meet a rounded parent's.
120
+ Rule: outer radius = inner radius + padding, landing on a named token
121
+ (lg + p-4 = 4xl, md + p-3 = rounded-20, sm + p-4 = 3xl, md + p-2.5 = 2xl,
122
+ sm + p-3 = 2xl).
123
+ Why: concentric corners read as one shape; equal radii at different depths
124
+ read as a mistake.
125
+ Exceptions: corners that never meet; gaps that would need a negative inner
126
+ radius.
127
+ Source: DESIGN.md, Radius; BirthGuide SPEC_021 (exemplar
128
+ concentric-radii-and-button-optics).
129
+ Enforced by: prose.
130
+ Bad: `rounded-lg p-4` parent around a `rounded-lg` child.
131
+ Good: `rounded-4xl p-4` parent around a `rounded-lg` child.
132
+
133
+ ### rule/no-arbitrary-clamp
134
+ Scope: any `className`.
135
+ Rule: fluid sizes are the named roles `text-display`, `text-section-title`,
136
+ `text-lede`, `py-band`, `mt-band-gap`. No `text-[clamp(...)]`.
137
+ Why: near-miss clamps drift a few pixels from one another and nobody can
138
+ tell which is intended.
139
+ Exceptions: a deliberate one-off with an inline disable stating why (five
140
+ exist in BirthGuide). birthplans.app has the rule off pending its type-role
141
+ pass; no new clamp literals there either.
142
+ Source: DESIGN.md, Type; exemplar clamp-drift-to-named-roles.
143
+ Enforced by: lint (BirthGuide, starter); prose (birthplans until the pass).
144
+ Bad: `text-[clamp(1.9rem,4vw,3rem)]`
145
+ Good: `text-section-title`
146
+
147
+ ### rule/tap-targets
148
+ Scope: interactive elements on coarse pointers.
149
+ Rule: at least 44px; buttons and inputs 48px.
150
+ Why: one-handed use, at 3am, on a phone.
151
+ Exceptions: inline text links.
152
+ Source: PRINCIPLES.md technical rules; DESIGN.md, Accessibility.
153
+ Enforced by: base (`@media (pointer: coarse)` block) for the package
154
+ components; prose for custom controls.
155
+
156
+ ### rule/inputs-16px
157
+ Scope: text inputs, selects, textareas.
158
+ Rule: font-size at least 16px.
159
+ Why: iOS Safari zooms into smaller inputs on focus.
160
+ Exceptions: none.
161
+ Source: base layer comment.
162
+ Enforced by: base.
163
+
164
+ ## Motion
165
+
166
+ ### rule/transition-for-interactive-state
167
+ Scope: hover, selection, expand, collapse and any state the reader can
168
+ reverse mid-animation.
169
+ Rule: CSS transitions, not keyframes.
170
+ Why: transitions retarget when interrupted; keyframes restart from zero.
171
+ Exceptions: one-shot staged sequences such as a hero entrance.
172
+ Source: DESIGN.md, Motion; BirthGuide principles story.
173
+ Enforced by: prose.
174
+
175
+ ### rule/entrance-once-per-visit
176
+ Scope: entrance and load animations.
177
+ Rule: play once per visit (session flag set before first paint), render at
178
+ rest on reloads and in-visit navigation, collapse under
179
+ `prefers-reduced-motion`.
180
+ Why: replaying an entrance on every reload reads as a bug and costs the
181
+ reader time.
182
+ Exceptions: none.
183
+ Source: BirthGuide layout.tsx; DESIGN.md, Motion.
184
+ Enforced by: prose.
185
+
186
+ ### rule/no-clipped-ambient
187
+ Scope: glow blobs, gradients and other ambient layers.
188
+ Rule: an ambient layer must not be clipped at a section boundary; dissolve
189
+ it with a mask or extend the clip past the blob and its blur.
190
+ Why: a clipped blur draws a hard horizontal seam exactly where two sections
191
+ meet.
192
+ Exceptions: none.
193
+ Source: exemplar hero-glow-seam.
194
+ Enforced by: prose (verify rendered).
195
+
196
+ ## Interaction and components
197
+
198
+ ### rule/focus-visible
199
+ Scope: any element with a focus ring.
200
+ Rule: use `focus-visible:` for rings, not `focus:`.
201
+ Why: Radix autofocuses the first control on open, so a `focus:` ring paints
202
+ on a modal the reader opened with a mouse. Keyboard users still get the
203
+ ring with `focus-visible:`.
204
+ Exceptions: none. Re-running `shadcn add` reverts the dialog; the reason is
205
+ recorded above the element.
206
+ Source: exemplar dialog-close-focus-visible.
207
+ Enforced by: lint, warn (`focus:ring`, `focus:outline`, `focus:border`).
208
+ The skip link in each layout is a legitimate `focus:` use; disable inline
209
+ with the reason.
210
+ Bad: `focus:ring-2 focus:ring-ring`
211
+ Good: `focus-visible:ring-2 focus-visible:ring-ring`
212
+
213
+ ### rule/one-emphasis-signal
214
+ Scope: cards, offers, any surface competing for attention.
215
+ Rule: a surface carries one emphasis signal (a chip, a tinted border, a
216
+ heavy shadow, an accent button, bold uppercase type). When it carries
217
+ several, remove until one remains.
218
+ Why: stacked signals cancel each other and read as shouting.
219
+ Exceptions: the single primary action of a step may sit inside an
220
+ emphasised card.
221
+ Source: exemplar calm-the-offering-cards.
222
+ Enforced by: prose.
223
+
224
+ ### rule/house-pattern-for-answers
225
+ Scope: questionnaire and preference capture in both products.
226
+ Rule: enumerable answers use the house icon-card groups (single or multi
227
+ select), not bare radios, checkboxes or selects. A single date uses a
228
+ native date input.
229
+ Why: 44px tappable cards with icons are the product's tested answer
230
+ pattern; the OS date picker beats any custom one on mobile.
231
+ Exceptions: dense utilitarian or admin UI may use RadioGroup, Checkbox,
232
+ Select.
233
+ Source: component intent blocks (radio-group, checkbox, select, calendar).
234
+ Enforced by: prose.
235
+
236
+ ### rule/sheet-on-mobile-aside-on-desktop
237
+ Scope: supplementary content beside a flow.
238
+ Rule: a bottom Sheet on mobile; an aside on desktop, split at the
239
+ breakpoint. Dialog only for a focused task that needs full attention.
240
+ Why: a modal is the heaviest surface; supplementary content should not
241
+ take the whole screen on desktop.
242
+ Exceptions: previews and confirmations use Dialog on all sizes.
243
+ Source: component intent blocks (sheet, dialog, popover).
244
+ Enforced by: prose.
245
+
246
+ ### rule/no-template-reflexes
247
+ Scope: composition.
248
+ Rule: no centred hero plus three cards by default, no metric boxes, no
249
+ badges as metadata, no nested cards, no decorative gradients or glass, no
250
+ carousels or autoplay, no visible theme switcher.
251
+ Why: these are the shapes a generator reaches for when it has not framed
252
+ the reader's job.
253
+ Exceptions: a card grid when the items are genuinely peer units.
254
+ Source: DESIGN.md, Reject list.
255
+ Enforced by: prose.
256
+
257
+ ### rule/no-values-in-stories
258
+ Scope: Storybook stories and the package showcase.
259
+ Rule: stories import components and tokens; they never define a colour,
260
+ spacing or variant of their own.
261
+ Why: Storybook is a consumer; a missing value is a finding, not a local
262
+ fix.
263
+ Exceptions: none.
264
+ Source: BirthGuide usage story.
265
+ Enforced by: lint (colour rule covers stories); prose.
266
+
267
+ ## Copy
268
+
269
+ ### rule/no-em-dash
270
+ Scope: everything: UI copy, comments, commit messages, generated documents,
271
+ metadata.
272
+ Rule: never an em dash. Commas, colons, full stops, parentheses instead.
273
+ Why: house style across every product and repo.
274
+ Exceptions: none (legacy occurrences are removed when a file is touched).
275
+ Source: every CLAUDE.md; DESIGN.md.
276
+ Enforced by: lint, warn (every U+2014 in a source file, comments
277
+ included); docs and commit messages stay prose.
278
+
279
+ ### rule/voice-bans
280
+ Scope: all user-facing writing.
281
+ Rule: no wellness-speak ("your journey", "mama", "you've got this"), no
282
+ filler affirmations ("amazing", "incredible"), no AI marketing words
283
+ ("seamless", "empower", "unlock", "cutting-edge", "revolutionise",
284
+ "great question"). Short sentences, one idea each. Read aloud.
285
+ Why: the products earn trust by sounding like a midwife who respects the
286
+ reader.
287
+ Exceptions: none.
288
+ Source: PRINCIPLES.md voice rules; both CLAUDE.md copy sections.
289
+ Enforced by: prose (a word list is a lint candidate).
290
+
291
+ ### rule/sentence-case
292
+ Scope: headings, buttons, labels, navigation.
293
+ Rule: sentence case. All caps only on the mono kicker label with wide
294
+ tracking.
295
+ Why: Title Case reads as marketing; all caps reads as shouting.
296
+ Exceptions: proper nouns.
297
+ Source: PRINCIPLES.md; DESIGN.md, Type.
298
+ Enforced by: prose.
299
+
300
+ ### rule/english-per-product
301
+ Scope: copy, comments, commit messages.
302
+ Rule: BirthGuide is Australian English (caesarean, labour, colour);
303
+ birthplans.app is US English (cesarean, labor, color, anesthesiologist).
304
+ The package and the starter are Australian English.
305
+ Why: each product speaks to its market; mixing reads as carelessness.
306
+ Exceptions: Tailwind class names stay US spelling everywhere.
307
+ Source: each CLAUDE.md language section.
308
+ Enforced by: prose.
309
+
310
+ ### rule/claim-only-what-ships
311
+ Scope: landing and product copy.
312
+ Rule: outcomes and features named in copy are ones the product delivers
313
+ today.
314
+ Why: the reader is deciding under pressure; an overclaim is a broken
315
+ promise at the bedside.
316
+ Exceptions: none.
317
+ Source: BirthGuide landing commits ("make the outcome copy claim only what
318
+ the product delivers").
319
+ Enforced by: prose.
@@ -0,0 +1,50 @@
1
+ # Surfaces
2
+
3
+ Load when: starting any work, to name the scope; and for product-specific
4
+ routes.
5
+ Canonical owner: BirthGuide `_context/PRINCIPLES.md` (scopes and budgets);
6
+ `DESIGN.md`, Two surface scopes and the brand chapters.
7
+
8
+ ## Scopes
9
+
10
+ **Engagement**: the product itself. Failure is "it did not load when I
11
+ needed it". Cold start under 1.5s on a three-year-old phone on slow 4G,
12
+ first paint under 1s; no hero imagery, no decorative motion; optimistic
13
+ writes; 44px targets; 16px inputs.
14
+
15
+ **Conversion**: landing, guides, articles. Failure is "it looked cheap" or
16
+ "search buried it". LCP under 2.5s, CLS under 0.1, INP under 200ms, first
17
+ load under 1MB, images under 200KB each and 500KB per page. Illustration
18
+ over photography, custom only; one custom typeface; entrance motion once
19
+ per visit.
20
+
21
+ The hospital case applies to both: a landing page opened on a ward tour is
22
+ still read under pressure.
23
+
24
+ ## BirthGuide (Australian English)
25
+
26
+ - Conversion: `/` (hero, offerings, program curriculum, comparison,
27
+ testimonials, pricing, FAQ, footer), `/guides/*`, `/tools/*`.
28
+ - Engagement: `/questionnaire`, `/plan/edit/*`, `/plan/[slug]` (the
29
+ published plan read at the bedside), downloads, `/program/*` sessions,
30
+ `/resume`, `/unsubscribe`.
31
+ - Always-dark: footer and showcase bands. Landing phone mock uses `dark-3`
32
+ and `dark-4`.
33
+ - Served token API: `public/brand.css` (generated; lint fails when stale).
34
+ - Local Storybook is the component gallery for both products.
35
+
36
+ ## birthplans.app (US English)
37
+
38
+ - Conversion: `/` (hero, plan comparison, free answers, pricing, FAQ, stat
39
+ band), guides.
40
+ - Engagement: `/questionnaire` (with the did-you-know bar and the
41
+ preference status ramp), `/plan/*` preview and download; one output, the
42
+ PDF.
43
+ - Always-dark: footer.
44
+ - Fluid-type lint rule is off until the type-role pass; no new clamps.
45
+ - No Storybook, no served brand.css.
46
+
47
+ ## Starter
48
+
49
+ One proof page. Delete it. Everything else here applies once the product
50
+ has surfaces.
@@ -0,0 +1,54 @@
1
+ # Tokens
2
+
3
+ Load when: any decision that picks a colour, surface, shadow or theme
4
+ behaviour.
5
+ Canonical owner: `css/roles.css` (roles and the brand contract) and the
6
+ product's brand file (values). `DESIGN.md`, Tokens and Colour, explains the
7
+ model. Do not restate values here; read them.
8
+
9
+ ## Decide in this order
10
+
11
+ 1. Is there a shadcn semantic for it? `bg-background`, `bg-card`,
12
+ `bg-popover`, `bg-primary`, `bg-secondary`, `bg-muted`, `bg-accent`,
13
+ `text-foreground`, `text-muted-foreground`, `border-border`,
14
+ `border-input`, `ring-ring`, `bg-destructive`. Use it
15
+ (rule/semantic-first).
16
+ 2. Is the surface an alternating band, a caption on a light surface, an
17
+ accent chip, a glow, or a highlight pill? Use the primitive that names
18
+ it: `bg-band`, `bg-band-2`, `bg-surface-2`, `text-ink-3`, `chip-1/2/3`,
19
+ `glow-1/2`, `highlight`.
20
+ 3. Is the surface always dark in both themes? `bg-dark` or `bg-dark-2`, and
21
+ every foreground on it from the on-dark ramp (rule/on-dark-ramp).
22
+ 4. Does the colour encode a preference state? `status-*`
23
+ (rule/status-colour-means-preference).
24
+ 5. None of the above: the value does not exist. Do not inline it. Raise a
25
+ role proposal in the design-system repo (minor version, contract entry,
26
+ both brand files).
27
+
28
+ ## Things that look wrong and are not
29
+
30
+ - The six divergent semantics hold literals in both themes and are not
31
+ aliased (rule/divergent-six). Dark `border` and `input` are translucent
32
+ white hairlines.
33
+ - `--dark-3` and `--dark-4` exist for the landing phone mock; nothing else
34
+ should use them.
35
+ - `--headline-accent` behaves differently per brand (BirthGuide: brand ink
36
+ in light, sand in dark; birthplans: sand, applied only under `dark:`).
37
+ - `chip-3` is the sister product's tint in each brand. Do not "correct" it.
38
+ - `--dark-soft-2`, `--dark-faint`, `--dark-faint-2` are near-identical greys
39
+ kept on purpose (exact-match discipline). Collapsing them needs Alex.
40
+
41
+ ## Shadows
42
+
43
+ `shadow-card`, `shadow-card-hover`, `shadow-card-selected` replace flat
44
+ borders with layered depth; selected embeds `var(--primary)` as a ring.
45
+ `shadow-warm-sm/md/lg` for landing cards (dark swaps to black-based
46
+ shadows). `shadow-bar` under a floating bottom bar. Spacing separates
47
+ before a border does. No raw `shadow-[...]`.
48
+
49
+ ## Adding a role
50
+
51
+ A new or renamed role: add to `roles.css` (theme mapping and the contract
52
+ lists), value it in every brand file, bump the package minor, note it in
53
+ the changelog, then bump each product and snapshot compare (additions
54
+ only). A change that a brand must satisfy anew is a major.
@@ -0,0 +1,42 @@
1
+ # Type and space
2
+
3
+ Load when: sizes, weights, radii, spacing rhythm, alignment.
4
+ Canonical owner: `css/roles.css` (fluid type roles, radius ramp, band
5
+ rhythm); `DESIGN.md`, Radius and Type.
6
+
7
+ ## Type
8
+
9
+ - One sans per product, one mono, mapped in the brand file's `@theme` block
10
+ from `next/font` variables. `display: optional` so text never blocks.
11
+ - Headings: weight 600, tracking -0.02em, `text-wrap: balance` (base
12
+ layer). Paragraphs `text-wrap: pretty`.
13
+ - Fluid sizes are roles (rule/no-arbitrary-clamp): `text-display` (with its
14
+ 0.9 line-height companion) for the one display headline,
15
+ `text-section-title` for section headings, `text-lede` for the lede
16
+ (leading set at the use site). Everything else is the static scale.
17
+ - Numbers in columns: `tabular-nums`. Headline figures stay proportional.
18
+ - Mono is for code, identifiers and the small uppercase kicker
19
+ (`font-mono text-xs font-semibold uppercase tracking-[0.16em]
20
+ text-ink-3`); nothing else, and nothing else is all caps
21
+ (rule/sentence-case).
22
+ - Prose measure around 60 to 68 characters (`max-w-prose`).
23
+
24
+ ## Radius
25
+
26
+ Ramp from the brand's `--radius`: `sm` to `4xl`, plus `rounded-20`
27
+ (rule/no-radius-literal). Concentric corners: outer = inner + padding on a
28
+ named token (rule/concentric-radii). Text buttons sit on a size-scaled
29
+ ladder (xs sm, sm md, default lg, lg xl); icon-only buttons are circles.
30
+
31
+ ## Rhythm
32
+
33
+ Landing sections: `py-band` for section verticals, `mt-band-gap` between a
34
+ section header and its content. Inside a card the padding steps are `p-4`,
35
+ `p-6`, `p-7` (the house card is `rounded-20 p-7 shadow-warm-sm`). Spacing
36
+ off the 4px grid is a smell; check whether an existing step fits first.
37
+
38
+ ## Alignment
39
+
40
+ Shared baselines and unmistakable gutters. A narrow table in a wide
41
+ section, or a card that is the only misaligned element in a row, is a
42
+ defect at P2.
@@ -0,0 +1,35 @@
1
+ # Verification
2
+
3
+ Load when: before claiming a change is safe, complete or zero-visual.
4
+ Canonical owner: each product's CLAUDE.md verification section and
5
+ `scripts/design-snapshot.ts`; the package's `pnpm check` and `pnpm build`.
6
+
7
+ ## Every UI change
8
+
9
+ 1. `pnpm lint` (ESLint guardrails plus the brand contract; BirthGuide also
10
+ checks the served brand.css).
11
+ 2. `pnpm typecheck` or `tsc --noEmit`; `pnpm build`.
12
+ 3. Render it: both themes, 360px and 1280px, every materially changed
13
+ state, keyboard order and focus, long content. Say what you saw; never
14
+ claim visual verification from source alone.
15
+
16
+ ## Token, role or component work
17
+
18
+ - Snapshot before editing (`pnpm design:snapshot baseline.json`), after
19
+ (`after.json`), compare. A refactor compares identical. An intentional
20
+ change shows only the keys you meant to move; state the count.
21
+ - Renames: apply the rename map to the baseline's keys and require zero
22
+ value differences (a small comparer script did this twice on 5 Sep 2026;
23
+ map order matters, longest names first).
24
+ - Clear `.next` after restructuring the CSS import graph; stop any other
25
+ dev server on the same checkout first.
26
+ - `design-baseline.json` is the committed intended state. An intentional
27
+ change regenerates it in the same branch and the commit names the delta.
28
+
29
+ ## Package release
30
+
31
+ `pnpm check`, `pnpm build`, `ds-check-brand` against both product brand
32
+ files, `ds-build-brand-css --check` against BirthGuide's committed
33
+ `public/brand.css`. Alex publishes (browser auth); tag `vX.Y.Z`. After a
34
+ merge that adds devDependencies, `pnpm install` in the main checkout before
35
+ publishing.
@@ -0,0 +1,47 @@
1
+ # CLAUDE.md
2
+
3
+ ## What this is
4
+
5
+ A new product on `@fracazo/design-system`, written by `ds-init` from the
6
+ package's `template/`: Next 16, Tailwind v4, the package, one blank brand
7
+ file, the guardrails on. Rewrite the top of this file to describe the
8
+ product and keep the rules below.
9
+
10
+ ## Read first
11
+
12
+ When shaping, building, reviewing or writing copy for user-facing UI, load
13
+ `node_modules/@fracazo/design-system/skills/product-design/SKILL.md` first
14
+ It
15
+ names the request mode, routes to the reference that applies and cites rules
16
+ by stable ID. Skip it for backend-only work, telemetry, generated files and
17
+ tests with no shipped UI.
18
+
19
+ `node_modules/@fracazo/design-system/DESIGN.md` is the written authority:
20
+ who the reader is, the priority order, how a page is composed, the reject
21
+ list. `css/roles.css` in the same package holds the roles and the brand
22
+ contract. The component intent lives in each component's source.
23
+
24
+ ## Rules
25
+
26
+ - The brand lives in `src/system/brands/*.css` and nowhere else. No colour
27
+ literal, radius literal or clamp() size in a `className`; ESLint errors on
28
+ them. A value the roles do not cover is a proposal for the design-system
29
+ repo, not a local addition.
30
+ - Components come from `@fracazo/design-system/ui/*`. Never copy one into
31
+ this repo to change it; change it upstream and bump the dependency. A
32
+ component that must import app code is the exception and stays here.
33
+ - `pnpm lint` runs ESLint and the brand contract check and must pass before
34
+ a merge. `pnpm typecheck` and `pnpm build` too.
35
+ - Never use em dashes, anywhere. Commas, colons, full stops instead.
36
+ - Feature branch, then fast-forward merge to `main`; no PRs. Conventional
37
+ Commits, present tense.
38
+ - Do not add dependencies without discussing first.
39
+
40
+ ## Tooling notes
41
+
42
+ - pnpm 11: `pnpm-workspace.yaml` approves the native build scripts and
43
+ excludes `@fracazo/design-system` from the minimum-release-age gate, so a
44
+ fresh release of the package installs the day it ships.
45
+ - The site follows the system colour scheme; there is no manual toggle. The
46
+ inline script in `layout.tsx` sets `.dark` before first paint and
47
+ `ThemeSync` follows live changes.
@@ -0,0 +1,16 @@
1
+ # starter
2
+
3
+ A product on [`@fracazo/design-system`](https://github.com/fracazo/design-system),
4
+ written by `ds-init`. Rename this file's title, then follow the numbered
5
+ steps in the package README under "Start a product".
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm dev
10
+ ```
11
+
12
+ Before every merge:
13
+
14
+ ```bash
15
+ pnpm lint && pnpm typecheck && pnpm build
16
+ ```
@@ -0,0 +1,20 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+ import { designSystemGuardrails } from "@fracazo/design-system/eslint";
5
+
6
+ const eslintConfig = defineConfig([
7
+ ...nextVitals,
8
+ ...nextTs,
9
+ globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
10
+ // Both design system guardrails, on from day one: no raw colour values and
11
+ // no arbitrary clamp() type sizes in a className. Add an exemption only for
12
+ // a renderer that genuinely cannot use CSS variables (react-pdf, email HTML,
13
+ // OG images), and say why in a comment next to it.
14
+ designSystemGuardrails({
15
+ files: ["src/**/*.{ts,tsx}"],
16
+ ignores: [],
17
+ }),
18
+ ]);
19
+
20
+ export default eslintConfig;
@@ -0,0 +1,44 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
42
+
43
+ # Claude Code local tooling
44
+ .claude/
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "starter",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint && pnpm brand:contract",
10
+ "typecheck": "tsc --noEmit",
11
+ "brand:contract": "ds-check-brand src/system/brands/starter.css",
12
+ "brand:build": "ds-build-brand-css --brand src/system/brands/starter.css --out public/brand.css --name Starter --url https://example.com"
13
+ },
14
+ "dependencies": {
15
+ "@dnd-kit/core": "^6.3.1",
16
+ "@dnd-kit/sortable": "^10.0.0",
17
+ "@dnd-kit/utilities": "^3.2.2",
18
+ "@fracazo/design-system": "^0.6.0",
19
+ "class-variance-authority": "^0.7.1",
20
+ "clsx": "^2.1.1",
21
+ "lucide-react": "^0.577.0",
22
+ "next": "16.1.6",
23
+ "radix-ui": "^1.6.7",
24
+ "react": "19.2.3",
25
+ "react-day-picker": "^9.14.0",
26
+ "react-dom": "19.2.3",
27
+ "react-hook-form": "^7.87.0",
28
+ "tailwind-merge": "^3.6.0"
29
+ },
30
+ "devDependencies": {
31
+ "@tailwindcss/postcss": "^4",
32
+ "@types/node": "^20",
33
+ "@types/react": "^19",
34
+ "@types/react-dom": "^19",
35
+ "eslint": "^9",
36
+ "eslint-config-next": "16.1.6",
37
+ "tailwindcss": "^4",
38
+ "typescript": "^5"
39
+ }
40
+ }