fertig 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,113 @@ is the token names, the class names (the seven, plus the layout utilities),
6
6
  and the ARIA attributes the component layer reads — changes to any of those
7
7
  are breaking.
8
8
 
9
+ ## Unreleased
10
+
11
+ ### Changed
12
+
13
+ - **The 72rem default is now a page shell, not a prose measure.**
14
+ `.max-w-prose` keeps long-form copy at 38rem, while `.wide` provides an 80rem
15
+ application column. The documentation and customiser now use the same
16
+ defaults and terminology as the stylesheet.
17
+ - **The documentation toolbar stays compact on phones.** Its primary controls
18
+ occupy the first row and its links scroll horizontally on the second instead
19
+ of wrapping into a tall stack. The Components page is now present in the
20
+ shared navigation.
21
+
22
+ ### Fixed
23
+
24
+ - **The minified build preserves descendant selectors.** The old punctuation
25
+ regex changed selectors such as `nav :is(…)` into `nav:is(…)`, silently
26
+ breaking row controls and popover menus. The minifier now understands CSS
27
+ strings and comments, with focused regression tests for both failure modes.
28
+ - **Reduced-transparency preferences reach framework wrappers.** A toolbar
29
+ inside `#root`, `#app`, `#__next` or `[data-fertig]` now drops its backdrop
30
+ blur just like a toolbar that is a direct child of `body`.
31
+
32
+ ## [3.0.0] — 2026-08-31
33
+
34
+ The breaking change is one mechanical line: **every public token now carries a
35
+ `--fertig-` prefix, and the old names are gone** — no aliases, no shims. The
36
+ classes, the layout utilities and the ARIA attributes the component layer reads
37
+ are all untouched. The sheet does exactly what it did; the migration is a
38
+ rename.
39
+
40
+ `--fertig-a1` / `--fertig-a2` already carried the prefix for the reason the rest
41
+ receive it now: a short, unprefixed custom property is a bet you only lose
42
+ once. `--ac` collided with your own `--ac`; the prefix makes every token yours
43
+ to override, and no future rename is ever needed again.
44
+
45
+ **To migrate:** prefix whatever you override. `--w` → `--fertig-w`,
46
+ `--ac` / `--on-ac` → `--fertig-ac` / `--fertig-on-ac`, the radii
47
+ `--r` / `--rs` / `--rw` → `--fertig-*`, the surfaces (`--bg`, `--el`, `--face`,
48
+ `--fg`, `--mut`, `--bd`, `--tb`, `--nw`), the depth tokens (`--up`, `--up2`,
49
+ `--dn`, `--sh`, `--sh1`, `--sh2`, and the `@property` alphas `--a1` / `--a2`),
50
+ the motion tokens (`--ease`, `--ease-in`, `--ease-out`, `--dur-1`, `--dur-2`),
51
+ `--caps`, and the colour ramps (`--stone-*`, `--sage-*`, `--sky-*`, `--clay-*`,
52
+ `--plum-*`, `--gold-*`). The rule is one line: **any `--name` you set becomes
53
+ `--fertig-name`.** Nothing changed meaning.
54
+
55
+ ### Changed
56
+
57
+ - **The palette moves to a cooler voice.** Neutrals are a cool graphite
58
+ (hue ≈ 258) with a faint violet cast, replacing the warm stone; the accent is
59
+ a violet-blue (hue 266), replacing indigo. Measured: the accent clears 6.21:1
60
+ as link text on paper and 6.39:1 for a white label on the fill in light,
61
+ 8.62:1 in dark — both sit inside sRGB. The
62
+ `@supports not (color: light-dark(…))` flat-sRGB fallback is restated to match,
63
+ and every pair still clears WCAG AA in both schemes.
64
+ - **Buttons are fully rounded.** The shared button rule takes a new
65
+ `--fertig-rp` (pill) token instead of the control radius. It matches the
66
+ badge, the switch and the progress bar, which were already pills. Set
67
+ `--fertig-rp: var(--fertig-r)` for square-shouldered buttons.
68
+ - **One radius, and the rest derived from it.** `--fertig-r` (12px) is the base;
69
+ `--fertig-rs` is `calc(var(--fertig-r) * .5)` and `--fertig-rw` is
70
+ `calc(var(--fertig-r) * 1.33)`, so setting the base on `:root` rescales the
71
+ whole sheet instead of desynchronising it. Windows — cards, dialogs, popover
72
+ menus, the select picker, `pre`, `details`, `fieldset`, `aside`, toasts —
73
+ now take `--fertig-rw` rather than the control radius, and full-bleed card
74
+ media follows the card it bleeds into.
75
+ - **Badges are uppercase.** They already used the caps *size*; they never got
76
+ the transform that `h5`, `h6` and `th` get.
77
+ - **The dialog lost its banded rules.** No hairline under the header or over
78
+ the footer by default; the separation is spacing. The rule now arrives as
79
+ scroll state — a scroll-driven animation fades it in under the header once the
80
+ body has scrolled and out of the footer at the end — so a dialog that fits
81
+ shows no rules at all. Title takes `--fertig-f`, the interface face, at
82
+ 600/1.1rem: a monospaced heading font reads as a terminal on a dialog.
83
+ - **The `err` tone is red, not clay.** `[data-tone=err]` borrowed
84
+ `--fertig-clay-700`, which at 50% lightness reads brown on a destructive
85
+ button. Now `oklch(52% .16 27)` / `oklch(74% .13 25)` — 5.97:1 with the white
86
+ label, and `--fertig-on-ac` still derives correctly in both themes.
87
+ - **The select picker animates open and closed**, with `display` and `overlay`
88
+ transitioned discretely so the close is not a pop.
89
+
90
+ ### Fixed
91
+
92
+ - **`accent-color` and `caret-color` ignored every local accent.** Both were
93
+ declared once on `:root`. They are inherited properties, so they resolved
94
+ there against the root accent and every descendant inherited that *computed
95
+ colour* — a `[data-accent]` or `[data-tone]` subtree repainted its buttons and
96
+ links but left its checkboxes, radios, ranges and caret on the root's tint.
97
+ Both are now re-declared on the elements that consume them.
98
+ - **The segmented control showed a vertical scrollbar.** `overflow-x: auto`
99
+ makes `overflow-y` compute to `auto` as well, and Safari paints a scrollbar
100
+ for a sub-pixel of overflow. The block axis is now clipped (`overflow: auto
101
+ clip`), and group children take their focus ring inside the edge
102
+ (`outline-offset: -3px`) so the clip cannot slice it.
103
+ - **The `<select>` marker was a different shape per engine.** Engines without
104
+ `appearance: base-select` fell back to a solid filled triangle while the
105
+ styleable picker drew a thin chevron. The fallback now draws the same
106
+ stroked chevron.
107
+ - **The docs site is built with Eleventy.** The pages moved out of hand-edited
108
+ HTML at the repo root into `src/` templates that build to `dist/`, with the
109
+ version flowing from `package.json` as the single source of truth. This is how
110
+ the sheet's own site is maintained; it is not part of the npm package, which
111
+ ships the two CSS files plus the README and licence.
112
+ - **The demo template pages are gone.** `app.html`, `app-invoice.html` and the
113
+ whole `templates/` set (`dashboard`, `article`, `pricing`, `signin`) are
114
+ removed from the site and repo; the docs no longer point at them.
115
+
9
116
  ## [2.0.0] — 2026-08-31
10
117
 
11
118
  ### Breaking
package/README.md CHANGED
@@ -7,7 +7,7 @@ switched off.
7
7
 
8
8
  Link it, write ordinary HTML, and the page is finished.
9
9
 
10
- **32.7 KB raw · 8.4 KB gzipped · no build step · no dependencies · no JavaScript.**
10
+ **36.9 KB raw · 8.8 KB gzipped · no build step · no dependencies · no JavaScript.**
11
11
 
12
12
  ### What makes it different
13
13
 
@@ -32,7 +32,7 @@ Surveyed across the eleven most-used classless stylesheets on 2026-08-31
32
32
  It is *not* the smallest — Concrete.css is 1.2 KB gzipped, and most of the
33
33
  field is a fraction of this because it styles elements and stops there. Against
34
34
  the two classless sheets that also ship a component layer, fertig is the
35
- lightest: 8.4 KB against matcha's 8.6 KB and Pico classless at 10.2 KB, and the
35
+ lightest: 8.8 KB against matcha's 8.8 KB and Pico classless at 10.2 KB, and the
36
36
  only one of the three whose components work without JavaScript. Size is a
37
37
  constraint here, not the pitch.
38
38
 
@@ -56,17 +56,40 @@ attributes. The snippet is in the docs.
56
56
  That is the whole integration. Write semantic HTML; it looks finished.
57
57
  Open `index.html` for the full demo.
58
58
 
59
- ## Upgrading from 1.x
59
+ ## Upgrading from 2.x
60
60
 
61
- One rename: `--a1` / `--a2` are now `--fertig-a1` / `--fertig-a2`. They are
62
- `@property` registrations, and a registration is globala two-character name
63
- was silently retyping a `--a1` of your own. If you set them, rename them; the
64
- values mean the same thing. If you did not, there is nothing to do.
61
+ 3.0 is breaking for one reason: **every public token now carries a `--fertig-`
62
+ prefix**, and the old names are gone no aliases. The classes, the ARIA
63
+ attributes the components read, and the layout utilities are all unchanged.
64
+ The sheet's behaviour is the same; the migration is a rename.
65
65
 
66
- Every other token, class and attribute is unchanged. The default look does
67
- change visibly though — larger radii, no filled strip behind card headers, an
68
- indigo accent so read it as a redesign, not a drop-in bump. Pin `fertig@1`
69
- if you need the old appearance.
66
+ If you override tokens, prefix what you set. The common ones:
67
+
68
+ | 2.x | 3.0 |
69
+ |---|---|
70
+ | `--ac` | `--fertig-ac` |
71
+ | `--on-ac` | `--fertig-on-ac` |
72
+ | `--w` | `--fertig-w` |
73
+ | `--r` / `--rs` / `--rw` | `--fertig-r` / `--fertig-rs` / `--fertig-rw` |
74
+ | `--bg` / `--el` / `--face` / `--fg` / `--mut` / `--bd` / `--tb` | `--fertig-*` (same short name) |
75
+ | `--up` / `--up2` / `--dn` / `--sh` / `--sh1` / `--sh2` | `--fertig-up` / `--fertig-up2` / `--fertig-dn` / `--fertig-sh` / `--fertig-sh1` / `--fertig-sh2` |
76
+ | fonts `--f` / `--fm`, measure `--w`, caps `--caps`, `--nw` | `--fertig-*` |
77
+ | color ramps `--stone-*` / `--sage-*` / `--sky-*` / `--clay-*` / `--plum-*` / `--gold-*` | `--fertig-*` (same stop) |
78
+
79
+ The rule is simple: **any `--name` you set becomes `--fertig-name`.** Nothing
80
+ changed meaning; the prefix exists so the sheet never collides with a custom
81
+ property of yours (it is the same reason `--fertig-a1` / `--fertig-a2` were
82
+ already prefixed — those are `@property` registrations, which are global).
83
+
84
+ Two defaults change visually, so pin `fertig@2` if you want the old look:
85
+
86
+ - **Palette.** Accents and neutrals moved to a cooler voice — graphite
87
+ neutrals and a violet-blue accent — replacing the older indigo/warm scheme.
88
+ - **Buttons.** Fully rounded (`border-radius: 999px`) instead of the moderate
89
+ radius.
90
+
91
+ The docs site was also rebuilt and the two demo pages (`app.html` /
92
+ `app-invoice.html`) are gone. None of that touches the sheet.
70
93
 
71
94
  ## Install
72
95
 
@@ -76,7 +99,7 @@ npm install fertig
76
99
 
77
100
  ```html
78
101
  <!-- or from a CDN, pinned -->
79
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@2/fertig.min.css">
102
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@3/fertig.min.css">
80
103
  ```
81
104
 
82
105
  ```css
@@ -86,14 +109,16 @@ npm install fertig
86
109
 
87
110
  ## What it is
88
111
 
89
- Opinions, so you don't have to have them: system type, one accent, one
90
- measure, a window on a desktop. The structure is classic — a title bar, sunken
112
+ Opinions, so you don't have to have them: system type, one accent, one page
113
+ width, a window on a desktop. The structure is classic — a title bar, sunken
91
114
  fields, raised buttons — and the finish is macOS: vibrancy, hairlines, soft
92
- radii, the blue focus ring. None of it is a period costume, and none of it is
93
- a picture of an OS: it is all CSS that every current engine already ships.
115
+ radii, a violet-blue focus ring over cool graphite neutrals. None of it is a
116
+ period costume, and none of it is a picture of an OS: it is all CSS that every
117
+ current engine already ships.
94
118
 
95
- - **System type, set properly** — 16px on a 1.65 line height across roughly
96
- seventy characters. Code keeps a monospace face, where it earns its place.
119
+ - **System type, set properly** — 16px on a 1.65 line height. Long-form copy
120
+ gets a roughly seventy-character measure with `.max-w-prose`. Code keeps a
121
+ monospace face, where it earns its place.
97
122
  - **A window on a desktop.** `<nav>` becomes a full-bleed toolbar with a
98
123
  vibrancy blur; `header`/`main`/`footer` are the paper.
99
124
  - **Depth from hairlines and soft shadows.** Buttons and cards sit above the
@@ -109,13 +134,17 @@ Direct children of `<body>` are the containers:
109
134
 
110
135
  ```html
111
136
  <body>
112
- <nav>…</nav> <!-- toolbar: full-bleed, contents on the measure -->
137
+ <nav>…</nav> <!-- toolbar: full-bleed, contents on the page column -->
113
138
  <header>…</header>
114
139
  <main>…</main>
115
140
  <footer>…</footer>
116
141
  </body>
117
142
  ```
118
143
 
144
+ The default page shell is `72rem`, roomy enough for application layouts and
145
+ documentation. Add `.max-w-prose` to long-form content for a readable `38rem`
146
+ line length, or `.wide` to a shell that genuinely needs the `80rem` column.
147
+
119
148
  One wrapper is fine. React, Vue, Svelte and Next render into a mount node, so
120
149
  the shell matches `body`'s children *or* the children of a single `#root`,
121
150
  `#app`, `#__next`, or `[data-fertig]` wrapper:
@@ -203,30 +232,47 @@ where `.max-w-2xl` works and `.max-w-3xl` silently does not is worse than no
203
232
  scale at all. They are `max-inline-size`, so they still mean "along the text"
204
233
  in a vertical writing mode, and they outrank the sheet's own measure on
205
234
  `body > *`, so they retarget a page container as readily as anything inside
206
- one. `.max-w-prose` is that measure, as a class.
235
+ one. `.max-w-prose` is a fixed `38rem` reading measure, independent of the
236
+ `72rem` page shell.
207
237
 
208
238
  ## Customising
209
239
 
210
- Override the four tokens you'd actually want to change:
240
+ Override the tokens you'd actually want to change:
211
241
 
212
242
  ```css
213
243
  :root {
214
- --ac: light-dark(#c2410c, #fb923c); /* accent */
215
- --w: 48rem; /* measure */
216
- --r: 0px; /* control radius — go sharp */
217
- --f: "Inter", system-ui, sans-serif; /* text font */
244
+ --fertig-ac: light-dark(oklch(50% .22 266), oklch(78% .13 266)); /* violet-blue accent */
245
+ --fertig-w: 48rem; /* page shell width */
246
+ --fertig-r: 0px; /* control radius — go sharp */
247
+ --fertig-f: "Inter", system-ui, sans-serif; /* text font */
218
248
  }
219
249
  ```
220
250
 
221
- If you change `--ac`, check `--on-ac` too that is the text colour sitting on
222
- the accent, and a light accent needs dark text to stay legible.
223
- `--up` / `--up2` / `--dn` are the elevation scale, `--fertig-a1` /
224
- `--fertig-a2` the shadow alphas (these two are `@property` registrations, and
225
- a registration is global — hence the prefix), `--rw` the window radius, and `--nw` the column the toolbar's contents
226
- line up with. `--nw` follows `--w`, so nothing moves by default; `<nav
227
- class="wide">` re-points it at the wide column, which is what an app screen
228
- built on `.wide` wants so its wordmark doesn't float in the middle of the
229
- viewport.
251
+ The accent is the only colour most people touch. In 3.0 the default is a
252
+ violet-blue (`hue 266`) sitting on cool graphite neutrals (`hue 258`); the
253
+ `data-accent` attribute retints a whole region without touching tokens:
254
+
255
+ ```html
256
+ <section data-accent="sage">…</section> <!-- or sky, clay, plum, gold, slate -->
257
+ ```
258
+
259
+ Tones work the same way — `data-tone="ok|warn|err"` retints any component
260
+ without a round trip through the tokens:
261
+
262
+ ```html
263
+ <button data-tone="err">Delete</button>
264
+ ```
265
+
266
+ If you change `--fertig-ac`, check `--fertig-on-ac` too — that is the text
267
+ colour sitting on the accent, and a light accent needs dark text to stay
268
+ legible. `--fertig-up` / `--fertig-up2` / `--fertig-dn` are the elevation
269
+ scale, `--fertig-a1` / `--fertig-a2` the shadow alphas (these two are
270
+ `@property` registrations, and a registration is global — hence the prefix),
271
+ `--fertig-rw` the window radius, and `--fertig-nw` the column the toolbar's
272
+ contents line up with. `--fertig-nw` follows `--fertig-w`, so nothing moves by
273
+ default; `<nav class="wide">` re-points it at the wide column, which is what an
274
+ app screen built on `.wide` wants so its wordmark doesn't float in the middle
275
+ of the viewport.
230
276
 
231
277
  ## Also handled
232
278
 
@@ -280,7 +326,7 @@ assistive technology reads.
280
326
 
281
327
  | Sheet | Raw | Gzip |
282
328
  |---|---:|---:|
283
- | **fertig** | **32.7 KB** | **8.4 KB** |
329
+ | **fertig** | **36.9 KB** | **8.8 KB** |
284
330
  | Pico 2.1.1 classless | 69.4 KB | 10.1 KB |
285
331
 
286
332
  Measured with `gzip -9`, KB = 1024 bytes for every row. Most of the gap is Pico's full
@@ -364,19 +410,17 @@ and on the [docs site](https://moji2002.github.io/fertig/docs.html#support).
364
410
  ## Development
365
411
 
366
412
  ```sh
367
- npm run dev # serve on :8899
368
- npm run build # regenerate fertig.min.css and print sizes
413
+ npm run build # regenerate fertig.min.css and print sizes
414
+ npm run site # build the site into dist/ with Eleventy
415
+ npm run site:serve # build + live-reload on :8080 (the Eleventy dev server)
416
+ npm run sizes # sync the size claims in README + site copy
369
417
  ```
370
418
 
371
419
  ## Builds
372
420
 
373
421
  | Build | Raw | Gzip | |
374
422
  |---|---:|---:|---|
375
- | `fertig.min.css` | 32.7 KB | 8.4 KB | everything |
376
- | `fertig.core.min.css` | 25.0 KB | 6.9 KB | without the ARIA component layer |
377
-
378
- Dropping the component layer saves 1.4 KB gzipped — worth knowing, rarely worth
379
- doing. The weight is in the element coverage and forms, not the components.
423
+ | `fertig.min.css` | 36.9 KB | 8.8 KB | everything |
380
424
 
381
425
  ## Files
382
426
 
@@ -384,21 +428,19 @@ The sheet:
384
428
 
385
429
  - `fertig.css` — source, commented
386
430
  - `fertig.min.css` — minified, what you ship
387
- - `fertig.core.min.css` — the same without the ARIA component layer
388
431
  - `build.js` — minifier and size report
389
432
 
390
- The site:
433
+ The site (Eleventy, output to `dist/`):
391
434
 
392
- - `index.html` — landing page, with a live customiser that writes the token
393
- block for you
394
- - `docs.html` — documentation
395
- - `blocks.html` — ready-to-use markup blocks (16 of them); the code shown is
396
- generated from the live preview, so it cannot drift
397
- - `app.html`, `app-invoice.html` full product screens built with no page CSS
398
- at all
399
- - `templates/` — four whole pages: `dashboard`, `article`, `pricing`, `signin`
435
+ - `src/_includes/layout.njk` — shared head + nav
436
+ - `src/_data/site.js` — version pulled from `package.json` (the one source of truth)
437
+ - `src/{index,docs,blocks}.njk` — page templates, generated from the root
438
+ HTML by `tools/build-src.py`
439
+ - `index.html`, `docs.html`, `blocks.html` — the authored source for each page
440
+ (the live customiser on the landing page writes the token block for you)
400
441
  - `site.css`, `site.js`, `icons.svg`, `favicon.svg` — the site's own chrome,
401
442
  none of it part of the sheet
443
+ - `dist/` — the built site that GitHub Pages deploys
402
444
 
403
445
  Everything else:
404
446
 
@@ -406,6 +448,9 @@ Everything else:
406
448
  - `CHANGELOG.md` — what changed and why, per release
407
449
  - `docs/classless-css-research.md` — the research behind the decisions
408
450
 
451
+ The sheet itself ships without the site — the npm package holds the two CSS
452
+ files plus the README and licence, nothing else.
453
+
409
454
  ## License
410
455
 
411
456
  MIT © Mojtaba Beheshti