@fractaldesign/fractalstyler 0.0.0-stage → 0.9.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/LICENSE +19 -0
  2. package/README.md +123 -2
  3. package/cli/main.mjs +141 -0
  4. package/cli/scaffold.mjs +406 -0
  5. package/data/recipes.json +61 -0
  6. package/dist/asset.d.ts +3 -0
  7. package/dist/css/fractalstyler.css +2453 -0
  8. package/dist/css/fractalstyler.min.css +2 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +6 -0
  11. package/dist/styles/_00_config.sass +29 -0
  12. package/dist/styles/_00_fonts.sass +58 -0
  13. package/dist/styles/_00_tokens.sass +211 -0
  14. package/dist/styles/_01_base.sass +58 -0
  15. package/dist/styles/_02_dimensions.sass +70 -0
  16. package/dist/styles/_03_typography.sass +170 -0
  17. package/dist/styles/_04_containers.sass +173 -0
  18. package/dist/styles/_05_layouts.sass +104 -0
  19. package/dist/styles/_06_shells.sass +135 -0
  20. package/dist/styles/_07_interactions.sass +212 -0
  21. package/dist/styles/_08_visuals.sass +136 -0
  22. package/dist/styles/_09_own.sass +174 -0
  23. package/dist/styles/colorpacks.sass +119 -0
  24. package/dist/styles/index.sass +14 -0
  25. package/dist/styles/themeplates.sass +159 -0
  26. package/docs/REGISTRY.api.md +477 -0
  27. package/docs/REGISTRY.md +14 -0
  28. package/docs/references/configurations-api.md +220 -0
  29. package/docs/references/configurations.md +258 -0
  30. package/lint/browser.mjs +79 -0
  31. package/lint/cli.mjs +212 -0
  32. package/lint/lib/agent-reporter.mjs +51 -0
  33. package/lint/lib/fuzzy.mjs +45 -0
  34. package/lint/lib/registry.mjs +107 -0
  35. package/lint/lib/sass-linter.mjs +71 -0
  36. package/lint/lib/svelte-linter.mjs +116 -0
  37. package/lint/lib/token-linter.mjs +97 -0
  38. package/package.json +112 -5
  39. package/registry.json +3999 -0
  40. package/scripts/class-vocab.js +197 -0
  41. package/scripts/update-registry.js +934 -0
  42. package/skills/fractal-styler/references/fractals.md +630 -0
  43. package/skills/fractal-styler/references/tokens.md +226 -0
  44. package/skills/fractalstyler/SKILL.md +59 -0
  45. package/skills/fractalstyler/references/fractals.md +630 -0
  46. package/skills/fractalstyler/references/tokens.md +226 -0
@@ -0,0 +1,220 @@
1
+ ---
2
+ title: Configurations API
3
+ description: Complete registry of every configuration variable, runtime token, attribute axis and class pattern in the current system, read verbatim from the Sass sources.
4
+ type: fractalstyler
5
+ ---
6
+
7
+ # Configurations API — every variable, token, attribute and class in the system
8
+
9
+ > Companion to [`configurations.md`](configurations.md) (the *why* and *how to edit*).
10
+ > This document is the *what*: every Sass configuration variable in
11
+ > `_00_config.sass`, every CSS custom property in `_00_tokens.sass`, every
12
+ > `data-*` axis, and every class family the loops and partials emit — with
13
+ > values, defaults, consumers, and who reads what.
14
+ >
15
+ > Sources read verbatim:
16
+ > `_00_config.sass`, `_00_fonts.sass`, `_00_tokens.sass`, `_01_base.sass`,
17
+ > `_02_dimensions.sass`, `_03_typography.sass`, `_04_containers.sass`,
18
+ > `_05_layouts.sass`, `_06_shells.sass`, `_07_interactions.sass`,
19
+ > `_08_visuals.sass`, `_09_own.sass`, `index.sass`.
20
+ > `colorpacks.sass` and `themeplates.sass` are raw Sass variable palettes
21
+ > (`$green90`-style ladder constants); they emit no classes and no tokens and
22
+ > are intentionally not part of this API.
23
+ >
24
+ > Three kinds of API — do not mix them:
25
+ >
26
+ > | Kind | Example | Set where / when |
27
+ > |---|---|---|
28
+ > | **A. Sass source variables** | `$steps`, `$gap-families`, `$frame-ratios`, `$shadow-specs` | `src/lib/styles/_00_config.sass` — edit in place, then recompile |
29
+ > | **B. Runtime CSS props + `data-*` modes** | `--space-md`, `data-mode="dark"`, `data-variant="ghost"` | In the browser, live, no recompile |
30
+ > | **C. Classes** (loop families + partial classes) | `.gap-md`, `.grid-4`, `button[data-variant="ghost"]` | Baked at compile time from A; themed at runtime by B |
31
+
32
+ ---
33
+
34
+ ## 1. A — Sass configuration (`_00_config.sass`)
35
+
36
+ The whole compile-time surface is seven variables. They are *not* `!default`
37
+ and have no `with (…)` override: they are the system's own config, edited in
38
+ place. Everything loop-generated (the entire L1 space ladder, the L3 frames,
39
+ the `--shade-*` elevation tokens) is expanded from them.
40
+
41
+ | Variable | Value | Consumed by | Effect of a change |
42
+ |---|---|---|---|
43
+ | `$steps` | `('xs' 'sm' 'md' 'bs' 'lg' 'xl' '2xl' '3xl')` — 8 steps | `_02_dimensions.sass` (gap/pad/mar loops), `scripts/update-registry.js` | Every space family is crossed with this list: a new step emits `.gap-…`/`.pad-…`/`.mar-…` rows for all 17 families — **only if** a matching `--space-<step>` token exists in `_00_tokens.sass`, else the emitted `var()` points at nothing |
44
+ | `$gap-families` | `(gap: gap, rgap: row-gap, cgap: column-gap)` — 3 families | `_02_dimensions.sass` | Key = class prefix, value = CSS property. Add `xgap: column-gap` and `.xgap-*` classes compile; remove a key and its classes cease to exist |
45
+ | `$pad-families` | `(pad: padding, px: padding-inline, py: padding-block, pt: padding-top, pr: padding-right, pb: padding-bottom, pl: padding-left)` — 7 families | `_02_dimensions.sass` | Same key→property contract. `px`/`py` are logical (inline/block, RTL-safe); named sides are physical |
46
+ | `$mar-families` | `(mar: margin, mx: margin-inline, my: margin-block, mt: margin-top, mr: margin-right, mb: margin-bottom, ml: margin-left)` — 7 families | `_02_dimensions.sass` | Same key→property contract. There are no negative-margin classes and no side-family aliases beyond these |
47
+ | `$frame-ratios` | `('16-9': '16 / 9', '9-16': '9 / 16', '4-3': '4 / 3', '3-4': '3 / 4', '3-2': '3 / 2', '2-3': '2 / 3', '1-1': '1 / 1')` — 7 ratios | `_05_layouts.sass` (`.frame-*` loop) | Key = class suffix, value = `aspect-ratio` literal. Add `'21-9': '21 / 9'` and `.frame-21-9` compiles |
48
+ | `$breakpoints` | `(md: 768px, bs: 1024px, lg: 1280px)` | `_06_shells.sass` (`$md`/`$bs`/`$lg` locals → rail visibility media queries) | Renaming or moving a key moves the rail seams. **Not** a complete seam inventory: `_05_layouts` and parts of `_06` hardcode `1025px`/`1281px`/`1441px`/`769px` literals; change both together |
49
+ | `$shadow-specs` | 8 steps, each `(y1, b1, a1, y2, b2, a2)` — key/ambient layer pairs | `:root` loop in `_00_config.sass` | Emits the `--shade-*` token per step (`var(--shadow-rim)` + two shadow layers). The `.shadow-*` classes read the separate `--shadow-*` channel tokens instead |
50
+
51
+ The header table of the file also fixes the step ladder semantics for humans:
52
+ key alpha tightens `0.14 → 0.02`, ambient alpha widens `0.00 → 0.08`, key blur
53
+ `1 → 24px`, ambient blur `0 → 64px` as steps grow.
54
+
55
+ ---
56
+
57
+ ## 2. B — runtime tokens (`_00_tokens.sass`)
58
+
59
+ Structure: two authoring mixins, `=light-theme-tokens` and
60
+ `=dark-theme-tokens`, are applied by `[data-mode='dark']` and
61
+ `[data-mode='light']` respectively. **A `data-mode` attribute on `<html>` is
62
+ mandatory** — the colour roles live inside the mode mixins, not `:root`;
63
+ without the attribute no colour token is defined. The `:root` block carries
64
+ everything mode-independent: fonts, type/space/radius scales, motion language,
65
+ control metrics, page chrome.
66
+
67
+ ### 2.1 Colour roles (light / dark)
68
+
69
+ | Token | Light | Dark | Read by |
70
+ |---|---|---|---|
71
+ | `--white-fixed` | `#FFFFFF` | `#FFFFFF` | `.text-white`, `button[data-variant="primary"|"soft"|"destructive"]` |
72
+ | `--black-fixed` | `#171717` | `#171717` | `.text-black` |
73
+ | `--bg` | `#ffffff` | `#101010` | `.bg`, `button[data-variant="outline"]` |
74
+ | `--bg-surface` | `#FBFBF9` | `#202021` | `.surface`, `body`, `.app-header` |
75
+ | `--bg-panel` | `#FBFBF9` | `#1f1f20` | `.panel`, `.card` |
76
+ | `--bg-sunken` | `#F7F6F0` | `#1f1f20` | `.sunken` |
77
+ | `--bg-raised` | `#F4F4F5` | `#3a3a3a` | `.raised`, `.accordion-trigger:hover` |
78
+ | `--bg-extra` | `rgb(247, 248, 249)` | `#111111` | `.extra` |
79
+ | `--bg-input` | `#f8f9fa` | `#1c1c1d` | `.input`, `.select` |
80
+ | `--bg-button` | `#CED8D3` | `#171818` | reserved button fill channel |
81
+ | `--text-primary` | `#262627` | `#EDF2F7` | `.text-primary`, body, headings |
82
+ | `--text-secondary` | `#777777` | `#c7c7c7` | `.text-secondary` |
83
+ | `--text-muted` | `#a3a4a7` | `#7c7c7c` | `.text-muted`, `.breadcrumb-*`, `.inputs-wrapper label` |
84
+ | `--text-inverse` | `#ffffff` | `#7c7c7c` | `.text-inverse` — flips with mode by design |
85
+ | `--state-surface` | `#F2EFE9` | `#7c7c7c` | `button[data-variant="secondary"]` |
86
+ | `--state-hover` | `#ECE7DF` | `#4a4a4b` | `a.nav-link:hover`, `button[data-variant="secondary"|"outline"|"ghost"]:hover`, `button.icon:hover` |
87
+ | `--state-selected` | `#E4DFD5` | `#666668` | selected-state channel |
88
+ | `--border` | `#d8d8d8` | `#2f2f2f` | `.border`, `.bt`/`.br`/`.bb`/`.bl`, `.input`, `.select`, `.card`, `.accordion-item` |
89
+ | `--border-subtle` | `#eceaea` | `#1e1e1f` | `.border-subtle`, `.bt-sub`/… |
90
+ | `--border-strong` | `#cfcfcf` | `#7c7c7c` | `.border-strong`, `.bt-str`/…, `button[data-variant="secondary"]` |
91
+ | `--theme-color` | `#ff4400` | `#2F9E44` | `.link`, `.text-theme`, `button[data-variant="primary"]`, `a.primary`, focus rims |
92
+ | `--theme-color-alt` | `#be2f00` | `#36731d` | `.link:hover`, `a.primary` hover |
93
+ | `--theme-color-sub` | `#feb18a` | *(undefined in dark — inherits)* | `button[data-variant="soft"]` |
94
+ | `--success` / `--success-hover` | `#10B981` / `#059669` | `#34D399` / `#6EE7B7` | `.text-success`, `.bg-success` |
95
+ | `--warning` / `--warning-hover` | `#F59E0B` / `#D97706` | `#FBBF24` / `#FCD34D` | `.text-warning`, `.bg-warning` |
96
+ | `--danger` / `--danger-hover` | `#bf2a2a` / `#DC2626` | `#b21a1a` / `#FCA5A5` | `.text-danger`, `.bg-danger`, `button[data-variant="destructive"]` |
97
+ | `--info` / `--info-hover` | `#3B82F6` / `#2563EB` | `#60A5FA` / `#93C5FD` | `.text-info`, `.bg-info` |
98
+ | `--ring` | `rgba(0, 127, 78, 0.35)` | `rgba(16, 185, 129, 0.4)` | `.input`/`.select` focus, `.accordion-trigger` focus |
99
+ | `--shadow-color` | `15 13 42` | `0 0 0` | every `--shadow-*` channel (RGB triplet) |
100
+ | `--shadow-rim` | `transparent` | `inset 0 1px 0 rgba(255,255,255,0.09)` | prefixed onto `--shade-*` tokens |
101
+ | `--shadow-umbra` | *(undefined in light)* | `rgb(0 0 0 / 0.50)` | dark-only umbra channel |
102
+ | `--shadow-xs` … `--shadow-3xl` | two-layer key+ambient, identical values in both mixins | same | `.shadow-xs` … `.shadow-3xl` |
103
+
104
+ ### 2.2 Mode-independent `:root` tokens
105
+
106
+ | Group | Tokens | Notes |
107
+ |---|---|---|
108
+ | Fonts | `--font-sans` (`"Timeless Sans", sans-serif`), `--timeless-sans`, `--timeless-grotesk` | `@font-face` families declared in `_00_fonts.sass`; `--font-mono` is *not* defined — `.mono` reads it and falls back to the browser stack until a mono face is added |
109
+ | Type scale | `--text-scaling: 1.25`; `--text-xs` `0.67rem`, `--text-sm` `0.75rem`, `--text-md` `0.875rem`, `--text-bs` `1rem`; `--text-lg`+ each `calc(previous * var(--text-scaling))` up to `--text-5xl` | Fixed rem rungs to base, then compounded — not fluid interpolation |
110
+ | Space scale | `--unit-space: 0.25rem`; `--space-xs` = unit, `sm` ×2, `md` ×3, `bs` ×4, `lg` ×6, `xl` ×8, `2xl` ×12, `3xl` ×16 | The rung set must mirror `$steps` — see §1 |
111
+ | Radius | `--radius-xs: 2px`, `sm: 4px`, `md: 8px`, `bs: 16px`, `lg: 32px`, `full: 9999px` | Six channels; no literal-radius ladder exists |
112
+ | Control metrics | `--control-h-sm: 21px`, `--control-h-bs: 32px`, `--control-h-lg: 38px`; `--height-sm: 16px`, `md: 20px`, `bs: 27px`, `lg: 32px` | `.h-*` classes read `--height-*`; `.input`/`.select` read `--height-lg` |
113
+ | Avatar / switch | `--avatar-size: 32px`, `--switch-w: 40px`, `--switch-h: 22px`, `--switch-thumb: 16px` | Load-bearing pairings documented in the source |
114
+ | Motion | `--transin1..3`, `--transout1..3` (cubic-beziers); `--speed0: 50ms`, `--speed1: 90ms`, `--speed2: 140ms`, `--speed3: 220ms`; `--motionin1..3`/`--motionout1..3` = duration + easing | Every `transition:` in the system reads these |
115
+ | Page chrome | `--header-height: 64px`, `--footer-height: 32px`, `--fit-height` (their sum), `--prose-clamp: 65ch`, `--content-clamp: 1600px`, `--app-inline: var(--space-lg)`, `--card-min: 240px`, `--sidebar-width`/`-left`/`-right: 280px`, `--z-sticky: 100`, `--z-raised: 200`, `--z-modal: 300`, `--z-toast: 500` | Shell geometry the L4 classes read |
116
+ | Pop colors | `--greenpop1..4`, `--orangepop1..6` | Named accent literals; `.orange1`…`.orange6` ink classes read the orange set |
117
+ | Elevation (from `_00_config.sass`) | `--shade-xs` … `--shade-3xl` | Loop-generated from `$shadow-specs` — rim + key layer + ambient layer |
118
+
119
+ ---
120
+
121
+ ## 3. B — `data-*` attribute API
122
+
123
+ | Attribute | Where | Values | Effect |
124
+ |---|---|---|---|
125
+ | `data-mode` | `<html>` (mandatory) | `light` \| `dark` | Applies `=light-theme-tokens` / `=dark-theme-tokens` — the entire colour-role set |
126
+ | `data-shape` | `.card` | `modern` | `border-radius: var(--radius-sm)` |
127
+ | `data-shape` | `button` | `modern` \| `curved` \| `round` | Corner treatment: `--radius-sm` / `--radius-md` / `--radius-full` |
128
+ | `data-variant` | `button` | `primary` \| `secondary` \| `soft` \| `outline` \| `ghost` \| `link` \| `destructive` | Paint variant of the button quartet (`destructive` also runs the `vibrating` keyframes on hover) |
129
+ | `data-size` | `button` | `sm` \| `md` \| `bs` \| `lg` | Metrics variant: height + font-size channels |
130
+
131
+ Class-attribute states (not `data-*`, but part of the same runtime surface):
132
+ `.app-shell.open` (left rail becomes a fixed drawer below 1025px),
133
+ `.accordion-item.open` (expands `.accordion-content`),
134
+ `.ta-c.mleft`/`.ta-r.mleft` (de-centers below 1024px),
135
+ `.content-section.null|narrow-half|narrow-wide|narrow-full`,
136
+ `.input.small`/`.select.small`, `.hide-mobile`/`.hide-desktop`.
137
+
138
+ ---
139
+
140
+ ## 4. C — class registry (summary)
141
+
142
+ 414 concrete classes across the layers; the full inventory with properties,
143
+ kinds and examples is [`../REGISTRY.api.md`](../REGISTRY.api.md) (generated),
144
+ and the grep cheatsheet is [`../../REGISTRY.md`](../../REGISTRY.md).
145
+
146
+ | Layer | Partial | Count | Contents |
147
+ |---|---|---|---|
148
+ | L0 | `_01_base.sass` | 2 | `.bdr` debug outline, `.mode-wipe` view-transition kill switch |
149
+ | L1 | `_02_dimensions.sass` | 154 | 17 space families × 8 steps (136), 6 radius channels, `.wfull`/`.hfull`/`.full`, `.minw0`/`.minh0`/`.min0`, `.h-sm`…`.h-lg`, `.hfull-vh`, `.hfull-vh-fitted` |
150
+ | L2 | `_04_containers.sass` | 54 | `.box` + 10 alignment modifiers, `.row` + 9, `.grid` + 16, `.wrap`, `.grow`, `.shrink-0`, position set, `.scroll-y`/`.scroll-x`, `.card` (+`data-shape`), `.chip`, `.prose-section`, `.nav-section`, `.page-header`, `.inputs-wrapper`, `.checkbox-wrapper` |
151
+ | L3 | `_05_layouts.sass` | 23 | `.grid-1/2/3/4/6` (divisor stepping), `.cspan-1`…`.cspan-8`, `.card-grid`, `.prose`, 7 `.frame-*` (from `$frame-ratios`), `.reel` |
152
+ | L4 | `_06_shells.sass` | 16 | `.app-shell` (+`.open` drawer compound), `.app-header` (+`.clamped`), `.app-main` (+`.clamped`), `.main-section`, `.sidebar-left`, `.sidebar-right`, `.app-footer`, `.content-section` (+`.null`/`.narrow-half`/`.narrow-wide`/`.narrow-full`) |
153
+ | L5 | `_03_typography.sass` | 55 | `.text-xs`…`.text-5xl`, `.text-body*`, `.page-*`/`.card-*`/`.breadcrumb-*`/`.nav-*`/`.header-nav`, `.lh11`–`.lh16`, `.ls-*`, `.weight-300`–`.weight-800`, `.tt-*`, `.ta-*` (+`.mleft`), `.mono`, `.sans`, `.truncate` |
154
+ | L5 | `_07_interactions.sass` | 31 | `button` element base, `button.icon/modern/primary`, 7 `button[data-variant]`, 3 `button[data-shape]`, 4 `button[data-size]`, `.input`(+`.small`), `.select`(+`.small`), `.link`, `.link-plain`, `.link-parent` (+`.link-title` child), `a.nav-link`, `a.primary`(+`.modern`), `a.outline`(+`.modern`) |
155
+ | L5 | `_08_visuals.sass` | 52 | 6 bare backgrounds, 11 ink, 4 status fills, `.border`/`.border-subtle`/`.border-strong`, 12 partition borders (`.bt`/`.br`/`.bb`/`.bl` × 3 weights), 8 `.shadow-*`, `.hide-mobile`/`.hide-desktop`, `.orange1`–`.orange6` |
156
+ | Custom | `_09_own.sass` | 27 | `.site-logomotif`, `.logotype`, `.project-circle`, the `.mascot`/`.mascotgl` families, `.hero-name`, the `.accordion` family |
157
+
158
+ Every class entry in `registry.json` carries: `class`, `kind`
159
+ (`family`/`class`/`modifier`/`child`/`variant`/`compound`/`element`),
160
+ `layer`, `property` (declarations, with `@media` steps inline), `file`,
161
+ `description`, `example`.
162
+
163
+ ---
164
+
165
+ ## 5. Fonts (`_00_fonts.sass`)
166
+
167
+ `@font-face` declarations only — no classes, no tokens:
168
+
169
+ | Family | File | Weight | Style |
170
+ |---|---|---|---|
171
+ | `Google Sans Flex` | `/fonts/GoogleSansFlex.woff2` | 300 800 | normal, italic |
172
+ | `Google Sans` | `/fonts/GoogleSans.woff2` | 400 700 | normal |
173
+ | `Google Sans` (italic) | `/fonts/GoogleSans-Italic.woff2` | 400 700 | italic |
174
+ | `Google Sans Code` | `/fonts/googlesanscode.woff2` | 300 800 | normal |
175
+ | `Timeless Sans` | `/fonts/TimelessSansVF.woff2` | 300 800 | normal, italic |
176
+ | `Timeless Grotesk` | `/fonts/TimelessSansVF.woff2` | 300 800 | normal, italic |
177
+ | `General Sans` | `/fonts/generalsans.woff2` | 300 700 | normal |
178
+ | `Satoshi` | `/fonts/satoshi.woff2` | 300 800 | normal |
179
+ | `Stack Sans` | `/fonts/stacksans.woff2` | 300 800 | normal |
180
+
181
+ `--font-sans` resolves to `Timeless Sans`; `Google Sans`/`General Sans`/
182
+ `Satoshi`/`Stack Sans` are alternate stacks a consumer can swap in.
183
+
184
+ ---
185
+
186
+ ## 6. Token → defined-in → read-by index
187
+
188
+ | Token group | Defined in | Read by |
189
+ |---|---|---|
190
+ | `--shade-*` | `_00_config.sass` `:root` loop | (reserved elevation channel; `.shadow-*` classes read `--shadow-*` instead) |
191
+ | Colour roles (§2.1) | `_00_tokens.sass` mode mixins | L5 dress classes, interactions, body |
192
+ | `--font-sans`, `--timeless-*` | `_00_tokens.sass` `:root` | `body`, `.sans` |
193
+ | `--text-*` | `_00_tokens.sass` `:root` | `.text-*`, `.text-body*`, semantic type roles |
194
+ | `--space-*`, `--unit-space` | `_00_tokens.sass` `:root` | all 17 L1 space families |
195
+ | `--radius-*` | `_00_tokens.sass` `:root` | `.radius-*`, `.chip`, `.input`, `.select`, `.card`, `button[data-shape]`, `a.nav-link` |
196
+ | `--height-*`, `--control-h-*` | `_00_tokens.sass` `:root` | `.h-*`, `.input`/`.select`, `.accordion-trigger` |
197
+ | `--motion*`, `--speed*`, `--trans*` | `_00_tokens.sass` `:root` | every `transition:` in the system |
198
+ | `--header-height`, `--footer-height`, `--fit-height` | `_00_tokens.sass` `:root` | `.app-header`, `.app-main`, `.hfull-vh-fitted`, rails |
199
+ | `--prose-clamp`, `--content-clamp`, `--app-inline`, `--card-min` | `_00_tokens.sass` `:root` | `.prose`, `.app-header.clamped`, `.app-main.clamped`, shells, `.card-grid` |
200
+ | `--sidebar-width*`, `--z-*` | `_00_tokens.sass` `:root` | rails, `.app-header`, drawer |
201
+ | `--shadow-*`, `--shadow-color`, `--shadow-rim` | `_00_tokens.sass` mode mixins | `.shadow-*` classes, `--shade-*` composition |
202
+ | `--greenpop*`, `--orangepop*` | `_00_tokens.sass` `:root` | `.orange1`…`.orange6` (green set reserved) |
203
+
204
+ ---
205
+
206
+ ## 7. Regeneration pipeline
207
+
208
+ | Script | Emits |
209
+ |---|---|
210
+ | `scripts/update-registry.js` | `registry.json`, `REGISTRY.md`, `docs/REGISTRY.md`, `skills/fractal-styler/references/fractals.md`, `skills/fractal-styler/references/tokens.md` |
211
+ | `scripts/class-vocab.js` | `docs/REGISTRY.api.md` (reads `registry.json`) |
212
+ | `lint/browser.mjs --out docs/browser.html` | `docs/browser.html` (searchable registry browser) |
213
+ | `scripts/build-css.js` | `dist/css/fractalstyler.css` + `.min.css` (plain-CSS distribution) |
214
+ | `scripts/validate-deck.js` | exit 0/1 — every class named in docs/components resolves against the compiled stylesheet |
215
+ | `lint/cli.mjs` | exit 0/1 — token purity and contract lint |
216
+ | `cli/main.mjs` (`fractalstyler` bin) | consumer scaffolds: `npx @fractaldesign/fractalstyler init\|eject\|lint` — track or eject the styles, place these docs into a project, wire the VS Code extension |
217
+
218
+ `pnpm registry` runs the first two; `pnpm browser` regenerates the browser
219
+ page; `pnpm check` runs the validator and the contract linter. Consumers get
220
+ the same registry builder after `eject` as `scripts/build-registry.mjs`.
@@ -0,0 +1,258 @@
1
+ ---
2
+ title: Configurations
3
+ description: How the styling system is configured — the Sass source variables, the token mixins, and the loop-generated class families, file by file, with edit recipes and pitfalls.
4
+ type: fractalstyler
5
+ ---
6
+
7
+ # Configurations — how `src/lib/styles` is wired
8
+
9
+ > Companion to [`configurations-api.md`](configurations-api.md) (the *what* —
10
+ > every variable, token, attribute and class, verbatim).
11
+ > This document is the *why* and *how to edit*: what each partial owns, what
12
+ > each `$` variable in `_00_config.sass` drives, and what you must touch
13
+ > together when you change one of them.
14
+
15
+ ## Golden rule for this whole document
16
+
17
+ **The `.sass` partials are the configuration.** There is no settings file, no
18
+ preset registry, no theme JSON — a "config change" is an edit to
19
+ `_00_config.sass` (loops and maps), `_00_tokens.sass` (tokens and mode
20
+ mixins), or a layer partial (classes), followed by regeneration:
21
+
22
+ ```bash
23
+ pnpm registry # update-registry.js + class-vocab.js → registry.json, REGISTRY.md, docs, skill refs
24
+ pnpm lint # token purity + contract lint
25
+ pnpm check # validate-deck.js + svelte-check
26
+ ```
27
+
28
+ Never edit the generated surfaces (`registry.json`, `REGISTRY.md`,
29
+ `docs/REGISTRY.api.md`, `docs/browser.html`, `dist/css/*`,
30
+ `skills/fractal-styler/references/*.md`) — they are overwritten on the next
31
+ run, by design.
32
+
33
+ ## Overview
34
+
35
+ ### Three moving parts
36
+
37
+ 1. **`_00_config.sass`** — Sass variables (`$steps`, the family maps,
38
+ `$frame-ratios`, `$breakpoints`, `$shadow-specs`). The loop half of the
39
+ class system is *generated* from these.
40
+ 2. **`_00_tokens.sass`** — CSS custom properties: two colour-mode mixins
41
+ (`=light-theme-tokens` / `=dark-theme-tokens`, applied by
42
+ `[data-mode='light']` / `[data-mode='dark']`) plus one mode-independent
43
+ `:root` block (type, space, radius, motion, control metrics, page chrome).
44
+ 3. **The layer partials** (`_01`…`_09`) — hand-authored classes. Every class
45
+ rides a token; literals exist only where a token cannot (1px hairlines,
46
+ aspect ratios, breakpoint px).
47
+
48
+ `index.sass` composes them in cascade order: config and tokens first, then
49
+ `_01`…`_09`. `colorpacks.sass` and `themeplates.sass` are raw Sass colour
50
+ palettes consumed by hand-picked tokens; they emit no classes and no tokens
51
+ and are never part of the class API.
52
+
53
+ ### Where to look while reading this doc
54
+
55
+ | Task | File to open |
56
+ |---|---|
57
+ | Change what the loops generate (steps, families, frames, shadow specs) | `_00_config.sass` |
58
+ | Change colours, add a colour role, dark mode | `_00_tokens.sass` |
59
+ | Add a font face | `_00_fonts.sass` |
60
+ | Add a spacing/radius/size utility | `_02_dimensions.sass` |
61
+ | Add type roles/modifiers | `_03_typography.sass` |
62
+ | Add a container or alignment rule | `_04_containers.sass` |
63
+ | Add a grid/layout/frame/reel rule | `_05_layouts.sass` |
64
+ | Add shell scaffolding | `_06_shells.sass` |
65
+ | Add inputs/links/buttons | `_07_interactions.sass` |
66
+ | Add backgrounds/ink/borders/shadows/visibility | `_08_visuals.sass` |
67
+ | Project-specific extensions | `_09_own.sass` |
68
+
69
+ ---
70
+
71
+ ## 1. `_00_config.sass` — the loop generator
72
+
73
+ ### 1.1. `$steps` — the step vocabulary
74
+
75
+ `('xs' 'sm' 'md' 'bs' 'lg' 'xl' '2xl' '3xl')` — eight steps. Every space
76
+ family (gap, pad, mar and their sides) is crossed with this list by the
77
+ `@each` loops in `_02_dimensions.sass`, so the entire L1 ladder is
78
+ `17 families × 8 steps = 136 classes`.
79
+
80
+ Editing it is a **two-file change**: a step without a matching
81
+ `--space-<step>` token in `_00_tokens.sass` compiles, but every class for it
82
+ points at an undefined `var()` — the declaration is dropped silently at
83
+ computed-value time. A token without the step is dead weight (no classes).
84
+ Change both, then run `pnpm registry`.
85
+
86
+ ### 1.2. The family maps — `$gap-families`, `$pad-families`, `$mar-families`
87
+
88
+ Each map is `class prefix → CSS property`:
89
+
90
+ ```sass
91
+ $gap-families: (gap: gap, rgap: row-gap, cgap: column-gap)
92
+ $pad-families: (pad: padding, px: padding-inline, py: padding-block, pt: padding-top, pr: padding-right, pb: padding-bottom, pl: padding-left)
93
+ $mar-families: (mar: margin, mx: margin-inline, my: margin-block, mt: margin-top, mr: margin-right, mb: margin-bottom, ml: margin-left)
94
+ ```
95
+
96
+ `_02_dimensions.sass` does nothing but loop these maps over `$steps`. The
97
+ `px`/`py`/`mx`/`my` keys are logical (inline/block — RTL-safe); the named
98
+ sides (`pt`, `ml`, …) are physical. To add a family: add the map entry, and
99
+ the classes compile on the next build — no other edit needed. To retire one:
100
+ delete the entry and every class it generated disappears from the compiled
101
+ CSS (grep your markup first).
102
+
103
+ Note the naming drift this system retired: `.pad-x-*`/`.marg-*`/`.square-*`
104
+ literal ladders and `-mob`/`-desk` responsive bands **do not exist** — the
105
+ current families are the ones above, in one base band, all token-routed.
106
+
107
+ ### 1.3. `$frame-ratios` — aspect-ratio media boxes
108
+
109
+ `('16-9': '16 / 9', …)` — key becomes the `.frame-<key>` suffix, value the
110
+ `aspect-ratio` literal. `_05_layouts.sass` loops it: each `.frame-*` gets
111
+ `aspect-ratio`, `overflow: hidden`, `position: relative`, and
112
+ `img, video, iframe, svg` fill-and-cover rules. Add a ratio, get a class;
113
+ that is the whole mechanism.
114
+
115
+ ### 1.4. `$breakpoints` — the named seams
116
+
117
+ `(md: 768px, bs: 1024px, lg: 1280px)`. Consumed via `map.get` by
118
+ `_06_shells.sass` for the rail visibility seams. **This is not the complete
119
+ seam inventory**: `_05_layouts.sass` (grid stepping) and the `narrow-*`
120
+ widths in `_06_shells.sass` hardcode `769px`/`1025px`/`1281px`/`1441px`
121
+ literals. If you move a seam, move both the map entry and the literals —
122
+ grep `min-width` / `max-width` across the partials before assuming the map
123
+ is the only place a seam lives.
124
+
125
+ ### 1.5. `$shadow-specs` — the elevation ladder
126
+
127
+ Eight steps of `(y1, b1, a1, y2, b2, a2)` pairs. The `:root` loop at the
128
+ bottom of `_00_config.sass` composes each into a `--shade-<step>` token:
129
+ `var(--shadow-rim)` + a tight key layer + a wide ambient layer. The header
130
+ comment table (key alpha, ambient alpha, key blur, ambient blur per step) is
131
+ the design intent — keep it in sync with the numbers.
132
+
133
+ The `.shadow-*` **classes** do not read these — they read the
134
+ `--shadow-<step>` channel tokens defined in the mode mixins of
135
+ `_00_tokens.sass`. Two ladders, two jobs: `--shade-*` is the composed
136
+ rim+key+ambient recipe, `--shadow-*` is the plain channel the utilities
137
+ apply.
138
+
139
+ ---
140
+
141
+ ## 2. `_00_tokens.sass` — the token contract
142
+
143
+ ### 2.1. Colour modes are mixins, and `data-mode` is mandatory
144
+
145
+ ```sass
146
+ =light-theme-tokens
147
+ --bg: #ffffff
148
+ …
149
+ =dark-theme-tokens
150
+ --bg: #101010
151
+ …
152
+
153
+ [data-mode='dark']
154
+ +dark-theme-tokens
155
+ [data-mode='light']
156
+ +light-theme-tokens
157
+ ```
158
+
159
+ The colour roles live **only** inside these mixins. Unlike the old system
160
+ there is no marker-free light default: a page without a `data-mode`
161
+ attribute on `<html>` has no colour tokens defined at all. Whatever renders
162
+ the page (Svelte layout, static template) must stamp
163
+ `data-mode="light"` or `data-mode="dark"`.
164
+
165
+ ### 2.2. The mode-independent `:root` block
166
+
167
+ Everything else: `--font-sans` and the timeless stacks, `--text-scaling` plus
168
+ the type rungs, `--unit-space` plus the space rungs, the six `--radius-*`
169
+ channels, control/avatar/switch metrics, the motion language
170
+ (`--speed*`, `--trans*`, `--motion*`), page chrome (`--header-height`,
171
+ `--footer-height`, `--fit-height`, `--prose-clamp`, `--content-clamp`,
172
+ `--app-inline`, `--card-min`, `--sidebar-width*`, `--z-*`), control heights
173
+ and the pop colors.
174
+
175
+ Known gap, kept honest: `.mono` reads `--font-mono`, which no partial defines
176
+ yet — it falls back to the browser mono stack until a mono face lands in
177
+ `_00_fonts.sass` and a `--font-mono` token is added here.
178
+
179
+ ### 2.3. Editing rules
180
+
181
+ - **Add a colour role**: add it to *both* mixins (or deliberately to one —
182
+ see `--theme-color-sub`, light-only, and `--shadow-umbra`, dark-only — and
183
+ say so in a comment).
184
+ - **Add a step token**: mirror `$steps` (see §1.1).
185
+ - **Never** hardcode a hex or pixel in a layer partial that a token covers —
186
+ the lint (`lint/cli.mjs`) enforces token purity.
187
+
188
+ ---
189
+
190
+ ## 3. The layer partials (`_01`–`_09`) — what each owns
191
+
192
+ | Partial | Layer | Owns | Conventions to keep |
193
+ |---|---|---|---|
194
+ | `_01_base.sass` | L0 | Element resets, view-transition defaults, `.bdr` | No classes beyond the debug outline and `.mode-wipe` |
195
+ | `_02_dimensions.sass` | L1 | The three `@each` family loops, radius channels, full/zero sizing, `.h-*` control heights, viewport heights | Classes here are generated or one-liners; no component logic |
196
+ | `_03_typography.sass` | L5 | `.text-*` primitives, semantic roles (`.page-title`, `.nav-link`…), `.lh*`, `.ls-*`, `.weight-*`, `.tt-*`, `.ta-*` (+`.mleft`), `.mono`, `.sans`, `.truncate` | Roles compose tokens; `.mleft` is the only media-query modifier |
197
+ | `_04_containers.sass` | L2 | `.box`/`.row`/`.grid` + nested alignment modifiers, `.wrap`, `.grow`, `.shrink-0`, positions, `.scroll-y`/`.scroll-x`, `.card`, `.chip`, section wrappers | Alignment modifiers **nest under their base** — never standalone; x* is always the horizontal/inline axis, y* the vertical/block axis |
198
+ | `_05_layouts.sass` | L3 | `.grid-1/2/3/4/6` (divisor stepping), `.cspan-*`, `.card-grid`, `.prose`, `.frame-*` loop, `.reel` | Grids are pure stepping — no default gap; compose `.gap-*`. The reel's `> *` is the system's one sanctioned child combinator (matched literally in `scripts/validate-deck.js`) |
199
+ | `_06_shells.sass` | L4 | `.app-shell` canon, header/main/footer, role-bound rails, `.content-section` widths | Rails are display:none below their seam and resurrected by media queries — left rail becomes an off-canvas drawer via `.app-shell.open` below 1025px |
200
+ | `_07_interactions.sass` | L5 | `.input`/`.select`, the link family, the `button` element base + its `data-variant`/`data-shape`/`data-size` axes, `a.primary`/`a.outline` | Buttons are styled via the bare `button` element plus attributes — there is no `.button` class |
201
+ | `_08_visuals.sass` | L5 | Bare backgrounds, ink, status fills, the partition-law border set, `.shadow-*`, `.hide-mobile`/`.hide-desktop`, `.orange1`–`.orange6` | Bare dress before compositions; components own their own hover states — there is no standalone `.hover`/`.transition` |
202
+ | `_09_own.sass` | Custom | The mascot families, `.hero-name`, the accordion, logotype bits | The sanctioned escape hatch; anything project-specific that cannot be expressed canonically |
203
+
204
+ ---
205
+
206
+ ## 4. Quick-edit cookbook
207
+
208
+ **Add a spacing step** (e.g. a hypothetical `4xl`):
209
+ 1. `$steps` in `_00_config.sass` → append `'4xl'`.
210
+ 2. `--space-4xl` in `_00_tokens.sass` `:root` → define the value.
211
+ 3. `pnpm registry` — 17 new classes appear per family map.
212
+
213
+ **Add a space family** (e.g. `.igap-*` for inline gaps):
214
+ 1. Add `igap: column-gap` to the right family map in `_00_config.sass`.
215
+ 2. Nothing else — the loop and the registry pick it up.
216
+
217
+ **Add a frame ratio**:
218
+ 1. Add `'21-9': '21 / 9'` to `$frame-ratios`.
219
+ 2. `pnpm registry` — `.frame-21-9` compiles and is indexed.
220
+
221
+ **Move a breakpoint seam**:
222
+ 1. Edit `$breakpoints` in `_00_config.sass`.
223
+ 2. Grep `min-width|max-width` in `_05_layouts.sass` and `_06_shells.sass` for
224
+ hardcoded literals (769/1025/1281/1441) and move them together.
225
+
226
+ **Add a colour role**:
227
+ 1. Add the token to `=light-theme-tokens` **and** `=dark-theme-tokens` in
228
+ `_00_tokens.sass`.
229
+ 2. If it needs a class: bare one-liner in `_08_visuals.sass`
230
+ (`.text-<role>` / `.bg-<role>`), following the existing rows.
231
+
232
+ **Add any other class**:
233
+ 1. Put it in the partial that owns its layer (see §3) — never invent a new
234
+ partial without approval.
235
+ 2. Document it with a trailing `// …` on the selector line — that comment is
236
+ what `scripts/update-registry.js` lifts into the registry description.
237
+ 3. `pnpm registry && pnpm lint && pnpm check`.
238
+
239
+ ---
240
+
241
+ ## 5. Pitfalls that have bitten before
242
+
243
+ - **A `$steps` entry without its token** compiles clean and silently emits
244
+ classes that do nothing (undefined `var()`). Always change both files.
245
+ - **Editing generated files** (`registry.json`, `REGISTRY.md`,
246
+ `docs/REGISTRY.api.md`) — overwritten on the next `pnpm registry`.
247
+ - **Assuming `$breakpoints` is the whole seam story** — the grid and
248
+ narrow-width literals live outside the map.
249
+ - **Expecting a marker-free light mode** — colour tokens only exist under a
250
+ `data-mode` attribute; no attribute means no colours.
251
+ - **Naming drift**: `.pad-x-*`, `.marg-*`, `.square-*`, `.radius-0`,
252
+ `-mob`/`-desk` bands and negative-margin classes are gone. The living
253
+ vocabulary is `.pad`/`.px`/`.py`/`.pt`/`.pr`/`.pb`/`.pl`,
254
+ `.mar`/`.mx`/`.my`/`.mt`/`.mr`/`.mb`/`.ml`, the six radius channels — all
255
+ across the eight steps.
256
+ - **A child combinator** (`> *`) anywhere but the reel trips
257
+ `scripts/validate-deck.js`. That guard is deliberate; a second exception
258
+ should be a conscious edit there.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // fractalstyler browser — interactive styling classes registry browser.
4
+ //
5
+ // Opens the class registry as a searchable, zero-dependency browser page.
6
+ // Usage:
7
+ // node lint/browser.mjs # opens in default browser
8
+ // node lint/browser.mjs --out <f> # writes HTML to specific file
9
+ // =============================================================================
10
+
11
+ import { execFileSync } from 'node:child_process';
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { renderBrowserHtml } from '../scripts/class-vocab.js';
17
+
18
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+ const REGISTRY_PATH = path.join(PKG_ROOT, 'registry.json');
20
+
21
+ function usage() {
22
+ console.log(`Usage:
23
+ fractalstyler browser [dir] # open registry browser
24
+ Options:
25
+ --registry <f> registry.json override
26
+ --out <file> write HTML to file instead of opening`);
27
+ }
28
+
29
+ function openInBrowser(file) {
30
+ const opener =
31
+ process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
32
+ const args = process.platform === 'win32' ? ['/c', 'start', '', file] : [file];
33
+ try {
34
+ execFileSync(opener, args, { stdio: 'ignore' });
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ async function main() {
42
+ const args = process.argv.slice(2);
43
+ let out;
44
+ let registryOverride;
45
+
46
+ for (let i = 0; i < args.length; i++) {
47
+ if (args[i] === '--out') out = args[++i];
48
+ else if (args[i] === '--registry') registryOverride = args[++i];
49
+ else if (args[i] === '--help' || args[i] === '-h') {
50
+ usage();
51
+ process.exit(0);
52
+ }
53
+ }
54
+
55
+ const regFile = registryOverride ? path.resolve(registryOverride) : REGISTRY_PATH;
56
+ if (!fs.existsSync(regFile)) {
57
+ console.error(`Registry file not found at: ${regFile}. Run "pnpm registry" first.`);
58
+ process.exit(1);
59
+ }
60
+
61
+ const data = JSON.parse(fs.readFileSync(regFile, 'utf8'));
62
+ const html = renderBrowserHtml(data);
63
+
64
+ const target = out ? path.resolve(out) : path.join(os.tmpdir(), `fractalstyler-browser-${process.pid}.html`);
65
+ fs.writeFileSync(target, html, 'utf8');
66
+
67
+ if (out) {
68
+ console.log(`✔ Registry browser page written to: ${target}`);
69
+ return;
70
+ }
71
+
72
+ console.log(`✔ Registry: ${regFile}`);
73
+ console.log(`✔ Opening browser page: ${target}`);
74
+ if (!openInBrowser(target)) {
75
+ console.log(`Open file in your browser: file://${target}`);
76
+ }
77
+ }
78
+
79
+ main();