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/fertig.css CHANGED
@@ -1,7 +1,7 @@
1
- /*! fertig v2.0.0 — a classless CSS stylesheet. MIT */
1
+ /*! fertig v3.1.0 — a classless CSS stylesheet. MIT */
2
2
 
3
3
  /* Opinions, so you don't have to have them:
4
- system type · one accent · one measure · a window on a desktop.
4
+ system type · one accent · one page width · a window on a desktop.
5
5
  Structure is classic (title bar, sunken fields, raised buttons); the finish
6
6
  is macOS (vibrancy, hairlines, soft radii, the blue focus ring).
7
7
  Override the tokens marked "yours"; the rest is the point of view. */
@@ -9,8 +9,8 @@
9
9
  /* typed so they interpolate; untyped custom properties cannot animate */
10
10
  /* Registrations are global: they cannot be layered, scoped or overridden.
11
11
  Hence the prefix — a bare --a1 would silently retype a property of yours. */
12
- @property --fertig-a1 { syntax: "<number>"; inherits: true; initial-value: .08 }
13
- @property --fertig-a2 { syntax: "<number>"; inherits: true; initial-value: .06 }
12
+ @property --fertig-a1 { syntax: "<number>"; inherits: true; initial-value: .06 }
13
+ @property --fertig-a2 { syntax: "<number>"; inherits: true; initial-value: .05 }
14
14
 
15
15
  /* Two layers, in this order. Your own unlayered CSS wins over both regardless
16
16
  of specificity, so overriding never needs !important — and neither does this
@@ -24,102 +24,119 @@
24
24
  /* ---- tokens ------------------------------------------------- */
25
25
  :root {
26
26
  color-scheme: light dark; /* powers light-dark() below */
27
- accent-color: var(--ac);
28
- caret-color: var(--ac);
29
-
30
- --f: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
27
+ /* These two are also re-declared on the elements that consume them, further
28
+ down. They are inherited properties: declared only here, they resolve once
29
+ against the root's accent and every descendant inherits that *computed
30
+ colour* — so a [data-accent] or [data-tone] subtree, or any element that
31
+ sets --fertig-ac, got the root's tint on its checkboxes and its caret
32
+ while everything else in it followed the override. */
33
+ accent-color: var(--fertig-ac);
34
+ caret-color: var(--fertig-ac);
35
+
36
+ --fertig-f: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
31
37
  Roboto, "Helvetica Neue", Arial, sans-serif; /* yours */
32
- --fm: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
38
+ --fertig-fm: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
33
39
  "Liberation Mono", monospace; /* code */
34
- --w: 38rem; /* ~70 characters — yours */
35
- --r: 9px; /* control radius */
36
- --rs: 5px; /* small radius: inline chips and code */
37
- --rw: 14px; /* window radius */
40
+ --fertig-w: 72rem; /* page shell width — yours */
41
+ /* One radius, and the rest derived from it: set --fertig-r and the whole
42
+ sheet rescales together. Four independent numbers meant --fertig-r: 4px
43
+ left tight fields sitting inside cards that were still round. */
44
+ --fertig-r: 12px; /* control radius: fields, menu rows */
45
+ --fertig-rs: calc(var(--fertig-r) * .5); /* small: inline chips and code */
46
+ --fertig-rw: calc(var(--fertig-r) * 1.33); /* window: cards, dialogs, menus */
47
+ --fertig-rp: calc(var(--fertig-r) * 1000); /* pill: buttons, badges, switches, meters */
48
+ /* --fertig-rp multiplies rather than naming infinity: any non-zero radius
49
+ still clamps to a true pill, and --fertig-r: 0 squares the buttons with
50
+ it instead of leaving them the one round thing on a sharp-cornered page. */
38
51
 
39
52
  /* ---- palette ------------------------------------------------
40
53
  Low chroma on purpose. Vivid palettes shout; these are meant to sit
41
- under text all day. OKLCH, so a step means the same thing in every hue. */
42
- --stone-100: oklch(97.5% .004 75); --stone-300: oklch(91% .006 75);
43
- --stone-500: oklch(72% .008 75); --stone-700: oklch(47% .009 75);
44
- --stone-900: oklch(26% .008 75);
54
+ under text all day. OKLCH, so a step means the same thing in every hue.
55
+ The neutrals are a cool graphite (hue ≈ 258) with a faint violet cast —
56
+ crisper and less dated than a warm stone, and better under the accent. */
57
+ --fertig-stone-100: oklch(97.5% .006 258); --fertig-stone-300: oklch(91% .009 258);
58
+ --fertig-stone-500: oklch(72% .012 258); --fertig-stone-700: oklch(47% .014 258);
59
+ --fertig-stone-900: oklch(26% .016 258);
45
60
 
46
- --sage-100: oklch(96% .022 155); --sage-300: oklch(87% .042 155);
47
- --sage-500: oklch(66% .058 155); --sage-700: oklch(47% .055 155);
48
- --sage-900: oklch(29% .038 155);
61
+ --fertig-sage-100: oklch(96% .024 160); --fertig-sage-300: oklch(87% .046 160);
62
+ --fertig-sage-500: oklch(66% .064 160); --fertig-sage-700: oklch(47% .06 160);
63
+ --fertig-sage-900: oklch(29% .042 160);
49
64
 
50
- --sky-100: oklch(96% .022 240); --sky-300: oklch(87% .045 240);
51
- --sky-500: oklch(64% .085 245); --sky-700: oklch(48% .095 250);
52
- --sky-900: oklch(30% .06 250);
65
+ --fertig-sky-100: oklch(96% .024 242); --fertig-sky-300: oklch(87% .05 244);
66
+ --fertig-sky-500: oklch(64% .095 246); --fertig-sky-700: oklch(48% .1 250);
67
+ --fertig-sky-900: oklch(30% .064 250);
53
68
 
54
- --clay-100: oklch(96% .022 45); --clay-300: oklch(88% .045 45);
55
- --clay-500: oklch(68% .085 45); --clay-700: oklch(50% .09 40);
56
- --clay-900: oklch(31% .055 40);
69
+ --fertig-clay-100: oklch(96% .024 45); --fertig-clay-300: oklch(88% .05 45);
70
+ --fertig-clay-500: oklch(68% .095 45); --fertig-clay-700: oklch(50% .098 40);
71
+ --fertig-clay-900: oklch(31% .06 40);
57
72
 
58
- --plum-100: oklch(96% .02 320); --plum-300: oklch(88% .038 320);
59
- --plum-500: oklch(66% .06 320); --plum-700: oklch(47% .065 320);
60
- --plum-900: oklch(29% .042 320);
73
+ --fertig-plum-100: oklch(96% .022 318); --fertig-plum-300: oklch(88% .042 318);
74
+ --fertig-plum-500: oklch(66% .066 318); --fertig-plum-700: oklch(47% .07 318);
75
+ --fertig-plum-900: oklch(29% .046 318);
61
76
 
62
- --gold-100: oklch(96.5% .028 90); --gold-300: oklch(89% .055 88);
63
- --gold-500: oklch(74% .085 85); --gold-700: oklch(55% .085 80);
64
- --gold-900: oklch(33% .05 80);
77
+ --fertig-gold-100: oklch(96.5% .03 92); --fertig-gold-300: oklch(89% .06 88);
78
+ --fertig-gold-500: oklch(74% .092 85); --fertig-gold-700: oklch(55% .092 80);
79
+ --fertig-gold-900: oklch(33% .056 80);
65
80
 
66
81
  /* ---- semantic ----------------------------------------------- */
67
- --bg: light-dark(oklch(93.5% .006 75), oklch(16% .006 75)); /* desktop */
68
- --el: light-dark(oklch(99.4% .002 75), oklch(21% .006 75)); /* paper */
69
- --face:light-dark(oklch(97.2% .005 75), oklch(25% .007 75)); /* chrome */
70
- --fg: light-dark(oklch(22% .008 75), oklch(95% .004 75));
82
+ --fertig-bg: light-dark(oklch(93.5% .008 258), oklch(15.5% .014 258)); /* desktop */
83
+ --fertig-el: light-dark(oklch(99% .004 258), oklch(21% .016 258)); /* paper */
84
+ --fertig-face:light-dark(oklch(97.2% .006 258), oklch(25% .018 258)); /* chrome */
85
+ --fertig-fg: light-dark(oklch(22% .02 258), oklch(95% .012 258));
71
86
  /* lifted from 50%/70%: the weakest pairing (muted on the desktop ground in
72
87
  light, muted on chrome in dark) was 4.96:1 and 5.99:1, a hair over AA.
73
- 47%/73% takes them to 5.65:1 and 6.70:1 while keeping a clear step down
74
- from --fg — AAA would need 41.5%, which is close enough to --fg that
75
- "muted" stops reading as secondary at all. */
76
- --mut: light-dark(oklch(47% .009 75), oklch(73% .008 75));
77
- --bd: light-dark(oklch(22% .008 75 / .13), oklch(95% .004 75 / .13));
78
- --tb: light-dark(oklch(97.2% .005 75 / .85), oklch(25% .007 75 / .85));
79
- /* indigo, not the azure every other classless sheet lands on. Measured:
80
- 6.28:1 as link text on paper and 6.39:1 for a white label on the fill in
81
- light; 8.72:1 and 10.26:1 in dark. Both sit inside sRGB. */
82
- --ac: light-dark(oklch(50% .19 275), oklch(78% .11 275)); /* yours */
83
- --on-ac: light-dark(#fff, #06121f); /* text on the accent, both themes */
88
+ 46%/72% takes them to 5.7:1 and 6.8:1 while keeping a clear step down
89
+ from --fertig-fg — AAA would need ~41%, which is close enough to
90
+ --fertig-fg that "muted" stops reading as secondary at all. */
91
+ --fertig-mut: light-dark(oklch(46% .018 258), oklch(72% .02 258));
92
+ --fertig-bd: light-dark(oklch(22% .02 258 / .13), oklch(95% .012 258 / .13));
93
+ --fertig-tb: light-dark(oklch(97.2% .006 258 / .85), oklch(25% .018 258 / .85));
94
+ /* a violet-indigo, not the azure every other classless sheet lands on.
95
+ Higher chroma than the old indigo so it reads electric without shouting.
96
+ Measured: 6.21:1 as link text on paper and 6.39:1 for a white label on
97
+ the fill in light; 8.62:1 in dark. Both sit inside sRGB. */
98
+ --fertig-ac: light-dark(oklch(50% .22 266), oklch(78% .13 266)); /* yours */
99
+ --fertig-on-ac: light-dark(#fff, #07071c); /* text on the accent, both themes */
84
100
 
85
101
  /* depth: hairline + soft shadow, macOS weight.
86
- --sh is an ordinary colour — override it with any notation you like, and
87
- every shadow in the sheet retints. The alphas are taken with color-mix
88
- against transparent rather than relative colour: same result, and it is
89
- three years older, which keeps Safari 17.5 inside the floor. */
90
- --sh: oklch(23.1% .007 286);
91
- --fertig-a1: .08; --fertig-a2: .06;
92
- --sh1: color-mix(in srgb, var(--sh) calc(var(--fertig-a1) * 100%), transparent);
93
- --sh2: color-mix(in srgb, var(--sh) calc(var(--fertig-a2) * 100%), transparent);
94
- --up: 0 1px 1px var(--sh1);
95
- --up2: 0 1px 2px var(--sh1), 0 6px 16px -6px var(--sh2);
96
- --dn: inset 0 1px 2px color-mix(in srgb, var(--sh) 8%, transparent);
97
- --caps: .74em/1.4 var(--f);
102
+ --fertig-sh is an ordinary colour — override it with any notation you like, and
103
+ every shadow in the sheet retints. Lighter than a pure black so the shadows
104
+ read as diffuse atmosphere rather than dark bars. The alphas are taken with
105
+ color-mix against transparent rather than relative colour: same result, and
106
+ it is three years older, which keeps Safari 17.5 inside the floor. */
107
+ --fertig-sh: oklch(30% .012 270);
108
+ --fertig-a1: .06; --fertig-a2: .05;
109
+ --fertig-sh1: color-mix(in srgb, var(--fertig-sh) calc(var(--fertig-a1) * 100%), transparent);
110
+ --fertig-sh2: color-mix(in srgb, var(--fertig-sh) calc(var(--fertig-a2) * 100%), transparent);
111
+ --fertig-up: 0 1px 1px var(--fertig-sh1);
112
+ --fertig-up2: 0 1px 1px var(--fertig-sh1), 0 8px 22px -8px var(--fertig-sh2);
113
+ --fertig-dn: inset 0 1px 2px color-mix(in srgb, var(--fertig-sh) 7%, transparent);
114
+ --fertig-caps: .74em/1.4 var(--fertig-f);
98
115
 
99
116
  /* motion. One decelerating curve for things arriving, one accelerating for
100
117
  things leaving, and a symmetric one for state that toggles in place. */
101
- --ease-out: cubic-bezier(.22, 1, .36, 1);
102
- --ease-in: cubic-bezier(.55, 0, 1, .45);
103
- --ease: cubic-bezier(.4, 0, .2, 1);
104
- --dur-1: .12s; /* a control reacting under the pointer */
105
- --dur-2: .2s; /* something appearing or leaving */
118
+ --fertig-ease-out: cubic-bezier(.22, 1, .36, 1);
119
+ --fertig-ease-in: cubic-bezier(.55, 0, 1, .45);
120
+ --fertig-ease: cubic-bezier(.4, 0, .2, 1);
121
+ --fertig-dur-1: .12s; /* a control reacting under the pointer */
122
+ --fertig-dur-2: .2s; /* something appearing or leaving */
106
123
  }
107
124
  @media (prefers-color-scheme: dark) {
108
- :root:not([data-theme=light]) { --fertig-a1: .5; --fertig-a2: .45 }
125
+ :root:not([data-theme=light]) { --fertig-a1: .42; --fertig-a2: .38 }
109
126
  }
110
- /* --on-ac has to survive any --ac override, or a custom accent ships an
127
+ /* --fertig-on-ac has to survive any --fertig-ac override, or a custom accent ships an
111
128
  unreadable label. contrast-color() does that properly but has almost no
112
129
  support; relative colour reaches the same black-or-white decision from the
113
130
  accent's own lightness, and has been cross-engine since 2024. Both are
114
131
  additive — the measured light-dark() pair above stands if neither exists. */
115
132
  @supports (color: oklch(from red l c h)) {
116
- :root { --on-ac: oklch(from var(--ac) clamp(0, (l - .62) * -100, 1) 0 h) }
133
+ :root { --fertig-on-ac: oklch(from var(--fertig-ac) clamp(0, (l - .62) * -100, 1) 0 h) }
117
134
  }
118
135
  @supports (color: contrast-color(red)) {
119
- :root { --on-ac: contrast-color(var(--ac)) }
136
+ :root { --fertig-on-ac: contrast-color(var(--fertig-ac)) }
120
137
  }
121
- [data-theme=light] { color-scheme: light; --fertig-a1: .08; --fertig-a2: .06 }
122
- [data-theme=dark] { color-scheme: dark; --fertig-a1: .5; --fertig-a2: .45 }
138
+ [data-theme=light] { color-scheme: light; --fertig-a1: .06; --fertig-a2: .05 }
139
+ [data-theme=dark] { color-scheme: dark; --fertig-a1: .42; --fertig-a2: .38 }
123
140
 
124
141
  /* Named accents. The ramps already exist, so this is a mapping table: put
125
142
  data-accent on any element and everything inside it — links, focus rings,
@@ -127,13 +144,13 @@
127
144
  global, so one section of a page can differ from the rest.
128
145
  Each pair is measured: the light tone clears AA as link text on paper, the
129
146
  dark tone clears it on the dark ground. */
130
- [data-accent=indigo] { --ac: light-dark(oklch(50% .19 275), oklch(78% .11 275)) }
131
- [data-accent=sky] { --ac: light-dark(var(--sky-700), oklch(78% .085 245)) }
132
- [data-accent=sage] { --ac: light-dark(var(--sage-700), oklch(80% .075 155)) }
133
- [data-accent=clay] { --ac: light-dark(var(--clay-700), oklch(80% .085 45)) }
134
- [data-accent=plum] { --ac: light-dark(var(--plum-700), oklch(80% .065 320)) }
135
- [data-accent=gold] { --ac: light-dark(oklch(55% .085 80), oklch(84% .09 85)) }
136
- [data-accent=slate] { --ac: light-dark(oklch(35% .01 250), oklch(86% .006 250)) }
147
+ [data-accent=indigo] { --fertig-ac: light-dark(oklch(50% .19 275), oklch(78% .11 275)) }
148
+ [data-accent=sky] { --fertig-ac: light-dark(var(--fertig-sky-700), oklch(78% .085 245)) }
149
+ [data-accent=sage] { --fertig-ac: light-dark(var(--fertig-sage-700), oklch(80% .075 155)) }
150
+ [data-accent=clay] { --fertig-ac: light-dark(var(--fertig-clay-700), oklch(80% .085 45)) }
151
+ [data-accent=plum] { --fertig-ac: light-dark(var(--fertig-plum-700), oklch(80% .065 320)) }
152
+ [data-accent=gold] { --fertig-ac: light-dark(oklch(55% .085 80), oklch(84% .09 85)) }
153
+ [data-accent=slate] { --fertig-ac: light-dark(oklch(35% .01 250), oklch(86% .006 250)) }
137
154
 
138
155
  /* ---- base --------------------------------------------------- */
139
156
  *, ::before, ::after { box-sizing: border-box }
@@ -141,25 +158,25 @@
141
158
  because no engine ships an alternative today: this one (Safari has no
142
159
  unprefixed text-size-adjust), the autofill repaint below, and the progress
143
160
  pseudo-elements. Everything else here is unprefixed modern CSS. */
144
- html { font: 16px/1.65 var(--f); -webkit-text-size-adjust: 100%; tab-size: 2 }
161
+ html { font: 16px/1.65 var(--fertig-f); -webkit-text-size-adjust: 100%; tab-size: 2 }
145
162
  body {
146
- margin: 0; background: var(--bg); color: var(--fg);
163
+ margin: 0; background: var(--fertig-bg); color: var(--fertig-fg);
147
164
  overflow-wrap: break-word; /* a pasted URL cannot widen the page */
148
165
  font-variant-numeric: slashed-zero tabular-nums;
149
166
  }
150
- ::selection { background: color-mix(in srgb, var(--ac) 28%, transparent) }
167
+ ::selection { background: color-mix(in srgb, var(--fertig-ac) 28%, transparent) }
151
168
  /* The macOS ring. No border-radius here: an outline already follows the
152
169
  element's own corners, and setting one would reshape whatever takes focus —
153
- a focused dialog would snap from --rw to --r for as long as it is focused. */
170
+ a focused dialog would snap from --fertig-rw to --fertig-r for as long as it is focused. */
154
171
  :focus-visible {
155
- outline: 3px solid color-mix(in srgb, var(--ac) 45%, transparent);
172
+ outline: 3px solid color-mix(in srgb, var(--fertig-ac) 45%, transparent);
156
173
  outline-offset: 1px;
157
174
  }
158
175
  img, video, iframe { display: block; max-width: 100%; height: auto }
159
176
  /* svg is left inline: it is as often an icon inside a sentence or a button as
160
177
  it is a standalone figure, and display:block drops the icon onto its own row */
161
178
  svg { max-width: 100%; vertical-align: middle }
162
- img, video { border-radius: var(--r); box-shadow: var(--up2) }
179
+ img, video { border-radius: var(--fertig-r); box-shadow: var(--fertig-up2) }
163
180
 
164
181
  /* ---- layout: a window on a desktop --------------------------- */
165
182
  /* The shell is body's own children — or the children of the single wrapper a
@@ -169,37 +186,37 @@ img, video { border-radius: var(--r); box-shadow: var(--up2) }
169
186
  :where() so every selector keeps the specificity it had as `body > *`. */
170
187
  body > *,
171
188
  body > :where(#root, #app, #__next, [data-fertig]) > * {
172
- max-width: var(--w); margin-inline: auto; padding-inline: 1.9rem;
189
+ max-width: var(--fertig-w); margin-inline: auto; padding-inline: 1.9rem;
173
190
  }
174
191
  /* the wrapper is not itself a shell element; it just gets out of the way */
175
192
  body > :where(#root, #app, #__next, [data-fertig]) { max-width: none; padding-inline: 0 }
176
193
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > :is(header, main, footer) {
177
- background: var(--el); border-inline: 1px solid var(--bd);
194
+ background: var(--fertig-el); border-inline: 1px solid var(--fertig-bd);
178
195
  }
179
196
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > header { padding-block: 2.4rem .8rem }
180
197
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > main { padding-bottom: 2.8rem }
181
198
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > footer {
182
- padding-block: 1.2rem 2rem; color: var(--mut); font-size: .85em;
183
- border-top: 1px solid var(--bd); border-bottom: 1px solid var(--bd);
184
- border-radius: 0 0 var(--rw) var(--rw); margin-bottom: 2.5rem;
185
- box-shadow: 0 18px 30px -22px color-mix(in srgb, var(--sh) 50%, transparent);
199
+ padding-block: 1.2rem 2rem; color: var(--fertig-mut); font-size: .85em;
200
+ border-top: 1px solid var(--fertig-bd); border-bottom: 1px solid var(--fertig-bd);
201
+ border-radius: 0 0 var(--fertig-rw) var(--fertig-rw); margin-bottom: 2.5rem;
202
+ box-shadow: 0 20px 32px -24px color-mix(in srgb, var(--fertig-sh) 38%, transparent);
186
203
  }
187
204
  /* nav = unified toolbar: full-bleed, vibrant, contents on the measure */
188
205
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav {
189
206
  position: sticky; top: 0; z-index: 9; max-width: none;
190
207
  /* the toolbar is full-bleed but its contents line up with the page column.
191
- --nw is that column: it follows --w, so overriding the measure moves the
208
+ --fertig-nw is that column: it follows --fertig-w, so overriding the measure moves the
192
209
  toolbar with it, and .wide re-points it at the wide column instead. */
193
- --nw: var(--w);
194
- padding-inline: max(1.9rem, calc(50% - var(--nw) / 2 + 1.9rem),
210
+ --fertig-nw: var(--fertig-w);
211
+ padding-inline: max(1.9rem, calc(50% - var(--fertig-nw) / 2 + 1.9rem),
195
212
  env(safe-area-inset-left), env(safe-area-inset-right));
196
213
  display: flex; flex-wrap: wrap; gap: 1.1rem; align-items: center;
197
214
  padding-block: calc(.6rem + env(safe-area-inset-top, 0px)) .6rem;
198
- background: var(--tb); color: var(--fg);
215
+ background: var(--fertig-tb); color: var(--fertig-fg);
199
216
  backdrop-filter: saturate(180%) blur(20px);
200
- border-bottom: 1px solid var(--bd); font-size: .92em;
217
+ border-bottom: 1px solid var(--fertig-bd); font-size: .92em;
201
218
  }
202
- :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav a { color: var(--fg); text-decoration: none; opacity: .75 }
219
+ :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav a { color: var(--fertig-fg); text-decoration: none; opacity: .75 }
203
220
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav a:hover { opacity: 1 }
204
221
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav :is(strong, b) a { opacity: 1 } /* the wordmark is not a link among links */
205
222
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav ul {
@@ -213,7 +230,7 @@ h1 { font-size: 1.55rem; letter-spacing: -.015em }
213
230
  h2 { font-size: 1.25rem; letter-spacing: -.01em; margin-top: 2.4rem }
214
231
  h3 { font-size: 1.08rem; margin-top: 1.9rem }
215
232
  h4 { font-size: 1rem }
216
- h5, h6 { font: 700 var(--caps); text-transform: uppercase; letter-spacing: .1em; color: var(--mut) }
233
+ h5, h6 { font: 700 var(--fertig-caps); text-transform: uppercase; letter-spacing: .1em; color: var(--fertig-mut) }
217
234
  :is(h1,h2,h3,h4,h5,h6):first-child { margin-top: 0 }
218
235
  @supports (text-box: trim-both cap alphabetic) {
219
236
  h1, h2, h3, h4 { text-box: trim-both cap alphabetic } /* optical, not metric */
@@ -223,37 +240,37 @@ h5, h6 { font: 700 var(--caps); text-transform: uppercase; letter-spacing: .1em;
223
240
 
224
241
  p, ul, ol, dl, pre, blockquote, table, figure, details, fieldset, hr { margin: 1.1rem 0 }
225
242
  p { text-wrap: pretty }
226
- a { color: var(--ac); text-underline-offset: 3px; text-decoration-thickness: 1px }
243
+ a { color: var(--fertig-ac); text-underline-offset: 3px; text-decoration-thickness: 1px }
227
244
  a:hover { text-decoration-thickness: 2px } /* quiet — no invert mid-paragraph */
228
- small { font-size: .85em; color: var(--mut) }
229
- hr { border: 0; border-top: 1px solid var(--bd); margin: 2.2rem 0 }
245
+ small { font-size: .85em; color: var(--fertig-mut) }
246
+ hr { border: 0; border-top: 1px solid var(--fertig-bd); margin: 2.2rem 0 }
230
247
  mark { background: light-dark(#ffe58f, #5c4a15); color: inherit; border-radius: 3px; padding: 0 .15em }
231
248
  abbr[title] { text-decoration: underline dotted; cursor: help }
232
249
  blockquote {
233
- border-inline-start: 2px solid var(--bd);
234
- padding-block: .15rem; padding-inline: 1.2rem 0; color: var(--mut);
250
+ border-inline-start: 2px solid var(--fertig-bd);
251
+ padding-block: .15rem; padding-inline: 1.2rem 0; color: var(--fertig-mut);
235
252
  }
236
253
  ul, ol { padding-inline-start: 1.5rem }
237
- li::marker { color: var(--mut) }
254
+ li::marker { color: var(--fertig-mut) }
238
255
  /* minmax(0, 1fr), not 1fr: a bare 1fr track has min-width:auto, so a long
239
256
  value cannot shrink and pushes the page sideways on a narrow screen. */
240
257
  dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .35rem 1rem }
241
- dt { color: var(--mut) }
258
+ dt { color: var(--fertig-mut) }
242
259
  dd { margin: 0 }
243
- figcaption { font-size: .85em; color: var(--mut); text-align: center }
260
+ figcaption { font-size: .85em; color: var(--fertig-mut); text-align: center }
244
261
 
245
262
  /* ---- code --------------------------------------------------- */
246
- code, kbd, samp, pre { font-family: var(--fm); font-size: .9em }
263
+ code, kbd, samp, pre { font-family: var(--fertig-fm); font-size: .9em }
247
264
  :not(pre) > code, samp {
248
- background: var(--face); border: 1px solid var(--bd);
249
- border-radius: var(--rs); padding: .08em .35em;
265
+ background: var(--fertig-face); border: 1px solid var(--fertig-bd);
266
+ border-radius: var(--fertig-rs); padding: .08em .35em;
250
267
  }
251
268
  kbd {
252
- background: var(--face); border: 1px solid var(--bd); border-bottom-width: 2px;
253
- border-radius: var(--rs); padding: .08em .4em; box-shadow: var(--up);
269
+ background: var(--fertig-face); border: 1px solid var(--fertig-bd); border-bottom-width: 2px;
270
+ border-radius: var(--fertig-rs); padding: .08em .4em; box-shadow: var(--fertig-up);
254
271
  }
255
272
  pre {
256
- background: var(--face); border: 1px solid var(--bd); border-radius: var(--r);
273
+ background: var(--fertig-face); border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw);
257
274
  padding: 1rem; overflow-x: auto;
258
275
  }
259
276
  pre code { background: none; border: 0; padding: 0; font-size: 1em }
@@ -261,37 +278,37 @@ pre code { background: none; border: 0; padding: 0; font-size: 1em }
261
278
  /* Syntax theme. The sheet ships no highlighter — bring highlight.js or Prism
262
279
  and the tokens land in this palette, in both themes. */
263
280
  :root {
264
- --syn-comment: light-dark(#6b7280, #8b949e);
265
- --syn-key: light-dark(#7c3aed, #c4a7fb);
266
- --syn-str: light-dark(#15803d, #7ee787);
267
- --syn-num: light-dark(#b45309, #f0b866);
268
- --syn-fn: light-dark(#1d4ed8, #93c5fd);
269
- --syn-attr: light-dark(#b91c1c, #ff9492);
281
+ --fertig-syn-comment: light-dark(#6b7280, #8b949e);
282
+ --fertig-syn-key: light-dark(#7c3aed, #c4a7fb);
283
+ --fertig-syn-str: light-dark(#15803d, #7ee787);
284
+ --fertig-syn-num: light-dark(#b45309, #f0b866);
285
+ --fertig-syn-fn: light-dark(#1d4ed8, #93c5fd);
286
+ --fertig-syn-attr: light-dark(#b91c1c, #ff9492);
270
287
  }
271
288
  :is(.hljs-comment, .hljs-quote, .token.comment, .token.prolog, .token.doctype) {
272
- color: var(--syn-comment); font-style: italic;
289
+ color: var(--fertig-syn-comment); font-style: italic;
273
290
  }
274
291
  :is(.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type, .hljs-built_in,
275
292
  .token.keyword, .token.atrule, .token.rule, .token.important, .token.selector) {
276
- color: var(--syn-key);
293
+ color: var(--fertig-syn-key);
277
294
  }
278
295
  :is(.hljs-string, .hljs-meta-string, .token.string, .token.attr-value, .token.char) {
279
- color: var(--syn-str);
296
+ color: var(--fertig-syn-str);
280
297
  }
281
298
  :is(.hljs-number, .hljs-symbol, .hljs-bullet, .token.number, .token.boolean,
282
299
  .token.constant, .token.unit) {
283
- color: var(--syn-num);
300
+ color: var(--fertig-syn-num);
284
301
  }
285
302
  :is(.hljs-title, .hljs-name, .hljs-section, .hljs-function,
286
303
  .token.tag, .token.function, .token.class-name) {
287
- color: var(--syn-fn);
304
+ color: var(--fertig-syn-fn);
288
305
  }
289
306
  :is(.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable,
290
307
  .token.attr-name, .token.property, .token.variable) {
291
- color: var(--syn-attr);
308
+ color: var(--fertig-syn-attr);
292
309
  }
293
310
  :is(.hljs-punctuation, .hljs-operator, .token.punctuation, .token.operator) {
294
- color: var(--mut);
311
+ color: var(--fertig-mut);
295
312
  }
296
313
  :is(.hljs-emphasis, .token.italic) { font-style: italic }
297
314
  :is(.hljs-strong, .token.bold) { font-weight: 700 }
@@ -301,31 +318,31 @@ pre code { background: none; border: 0; padding: 0; font-size: 1em }
301
318
  /* ---- table -------------------------------------------------- */
302
319
  table { width: 100%; border-collapse: collapse; font-size: .92em }
303
320
  th, td {
304
- border-bottom: 1px solid var(--bd); padding-block: .55rem;
321
+ border-bottom: 1px solid var(--fertig-bd); padding-block: .55rem;
305
322
  padding-inline: 0 .7rem; text-align: start;
306
323
  }
307
324
  th:last-child, td:last-child { padding-inline-end: 0 }
308
325
  thead th {
309
- border-bottom: 1px solid color-mix(in srgb, var(--fg) 45%, transparent);
310
- font: 700 var(--caps); text-transform: uppercase; letter-spacing: .09em; color: var(--mut);
326
+ border-bottom: 1px solid color-mix(in srgb, var(--fertig-fg) 45%, transparent);
327
+ font: 700 var(--fertig-caps); text-transform: uppercase; letter-spacing: .09em; color: var(--fertig-mut);
311
328
  padding-bottom: .4rem;
312
329
  }
313
330
  tbody tr:last-child td { border-bottom: 0 }
314
331
  /* a totals row is not just another row */
315
- tfoot td { font-weight: 700; border-top: 1px solid var(--bd); border-bottom: 0 }
332
+ tfoot td { font-weight: 700; border-top: 1px solid var(--fertig-bd); border-bottom: 0 }
316
333
  /* our text-align was overriding HTML own alignment; honour it for numbers */
317
334
  :is(th, td)[align=right] { text-align: right }
318
335
  :is(th, td)[align=center] { text-align: center }
319
- tbody tr:hover { background: var(--face) }
336
+ tbody tr:hover { background: var(--fertig-face) }
320
337
  @media (width <= 40rem) {
321
338
  table { display: block; overflow-x: auto; overscroll-behavior-x: contain }
322
339
  }
323
- caption { text-align: start; color: var(--mut); font-size: .85em; padding-bottom: .35rem }
340
+ caption { text-align: start; color: var(--fertig-mut); font-size: .85em; padding-bottom: .35rem }
324
341
 
325
342
  /* ---- forms: fields sink, buttons rise ------------------------ */
326
343
  button, input, select, textarea { font: inherit; color: inherit }
327
- label { display: block; font-size: .85em; color: var(--mut) }
328
- label:has(input) { font-size: 1rem; color: var(--fg) }
344
+ label { display: block; font-size: .85em; color: var(--fertig-mut) }
345
+ label:has(input) { font-size: 1rem; color: var(--fertig-fg) }
329
346
  /* a label following a field belongs to the next one: give it air, except in a
330
347
  .row, where the toolbar is horizontal and the margin would knock it askew */
331
348
  :is(input, select, textarea) + label { margin-top: .9rem }
@@ -335,17 +352,22 @@ textarea { width: 100%; min-height: 5rem; resize: vertical }
335
352
  }
336
353
  input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file],[type=image],[type=submit],[type=button],[type=reset]),
337
354
  select, textarea {
338
- display: block; width: 100%; padding: .52em .7em; background: var(--el);
339
- border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--dn);
355
+ display: block; width: 100%; padding: .52em .7em; background: var(--fertig-el);
356
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-r); box-shadow: var(--fertig-dn);
340
357
  }
341
358
  select {
342
359
  appearance: none; padding-inline-end: 2.2rem;
343
- background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
344
- linear-gradient(135deg, currentColor 50%, transparent 50%);
345
- background-position: calc(100% - 17px) 55%, calc(100% - 12.5px) 55%;
346
- background-size: 5px 5px; background-repeat: no-repeat;
347
- }
348
- select:dir(rtl) { background-position: 12.5px 55%, 17px 55% }
360
+ /* a stroked chevron, drawn as two 1.5px bars, so the engines without
361
+ appearance: base-select show the same mark as the ones with it */
362
+ background-image:
363
+ linear-gradient(45deg, transparent calc(50% - 1px), currentColor calc(50% - 1px),
364
+ currentColor calc(50% + .5px), transparent calc(50% + .5px)),
365
+ linear-gradient(-45deg, transparent calc(50% - 1px), currentColor calc(50% - 1px),
366
+ currentColor calc(50% + .5px), transparent calc(50% + .5px));
367
+ background-position: calc(100% - 17px) 54%, calc(100% - 12px) 54%;
368
+ background-size: 6px 6px; background-repeat: no-repeat;
369
+ }
370
+ select:dir(rtl) { background-position: 12px 54%, 17px 54% }
349
371
  select[multiple] { background-image: none; padding-inline-end: .6em }
350
372
 
351
373
  /* a <select> whose dropdown can finally be styled like the rest of the sheet */
@@ -359,83 +381,101 @@ select[multiple] { background-image: none; padding-inline-end: .6em }
359
381
  content: ""; inline-size: .45em; block-size: .45em; flex: none;
360
382
  border: solid currentColor; border-width: 0 1px 1px 0;
361
383
  rotate: 45deg; translate: 0 -.15em; opacity: .7;
362
- transition: rotate var(--dur-1) var(--ease), translate var(--dur-1) var(--ease);
384
+ transition: rotate var(--fertig-dur-1) var(--fertig-ease), translate var(--fertig-dur-1) var(--fertig-ease);
363
385
  }
364
386
  select:open::picker-icon { rotate: 225deg; translate: 0 .1em }
365
387
  ::picker(select) {
366
388
  min-width: anchor-size(width); /* never narrower than the field */
367
- border: 1px solid var(--bd); border-radius: var(--r); padding: .3rem;
368
- background: var(--el); margin-block-start: .35rem;
369
- box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
389
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw); padding: .35rem;
390
+ background: var(--fertig-el); margin-block-start: .35rem;
391
+ box-shadow: var(--fertig-up2), 0 24px 44px -18px color-mix(in srgb, var(--fertig-sh) 28%, transparent);
370
392
  }
371
393
  option {
372
394
  display: flex; align-items: center; gap: .5em; padding: .4em .6em;
373
- border-radius: calc(var(--r) - 2px);
395
+ border-radius: calc(var(--fertig-r) - 2px);
374
396
  }
375
- option:hover { background: var(--face) }
376
- option:checked { background: var(--ac); color: var(--on-ac) }
397
+ option:hover { background: var(--fertig-face) }
398
+ option:checked { background: var(--fertig-ac); color: var(--fertig-on-ac) }
377
399
  option::checkmark { order: 1; margin-inline-start: auto; content: "✓" }
400
+ /* The picker drops out of the field the way the menu popover does. It lives
401
+ in the top layer, so display and overlay have to transition discretely or
402
+ it would pop out on close with the fade never running. */
403
+ @media (prefers-reduced-motion: no-preference) and (update: fast) {
404
+ ::picker(select) {
405
+ opacity: 0; translate: 0 -4px; transform-origin: top center;
406
+ transition: opacity .13s var(--fertig-ease-out), translate .13s var(--fertig-ease-out),
407
+ display .13s allow-discrete, overlay .13s allow-discrete;
408
+ }
409
+ select:open::picker(select) { opacity: 1; translate: 0 }
410
+ @starting-style { select:open::picker(select) { opacity: 0; translate: 0 -4px } }
411
+ }
378
412
  }
379
- :is(input,select,textarea):hover:not(:disabled) { border-color: color-mix(in srgb, var(--fg) 25%, transparent) }
380
- :is(input,select,textarea):focus-visible { border-color: var(--ac) }
413
+ :is(input,select,textarea):hover:not(:disabled) { border-color: color-mix(in srgb, var(--fertig-fg) 25%, transparent) }
414
+ :is(input,select,textarea):focus-visible { border-color: var(--fertig-ac) }
381
415
  input[type=checkbox], input[type=radio] {
382
- width: 1.15em; height: 1.15em; margin: 0 .45em 0 0; vertical-align: -.18em;
416
+ width: 1.15em; height: 1.15em; margin: 0; margin-inline-end: .45em; vertical-align: -.18em;
417
+ }
418
+ /* re-resolved here rather than inherited from :root, so a native control inside
419
+ a [data-accent] or [data-tone] block is tinted with that block's accent */
420
+ :is(input[type=checkbox], input[type=radio], input[type=range], progress, select[multiple]) {
421
+ accent-color: var(--fertig-ac);
383
422
  }
423
+ :is(input, textarea, [contenteditable]) { caret-color: var(--fertig-ac) }
384
424
  label:has(input[type=checkbox]), label:has(input[type=radio]) { line-height: 1.9 }
385
425
  input[type=range] { width: 100%; height: 1.15em }
386
- ::placeholder { color: var(--mut) }
426
+ ::placeholder { color: var(--fertig-mut) }
387
427
  @supports selector(::spelling-error) {
388
428
  ::spelling-error { text-decoration: wavy underline light-dark(#c0392b, #ff6b6b) }
389
429
  ::grammar-error { text-decoration: wavy underline light-dark(#9a6700, #d29922) }
390
430
  }
391
- :is(input, textarea):read-only:not(:disabled) { background: var(--face); cursor: default }
431
+ :is(input, textarea):read-only:not(:disabled) { background: var(--fertig-face); cursor: default }
392
432
  label:has(+ :required)::after, label:has(:required)::after {
393
- content: " *"; color: var(--ac); /* mark what is actually required */
433
+ content: " *"; color: var(--fertig-ac); /* mark what is actually required */
394
434
  }
395
435
  /* the browser paints autofilled fields yellow; repaint them ourselves */
396
436
  input:autofill, input:autofill:hover, input:autofill:focus {
397
- -webkit-text-fill-color: var(--fg);
398
- box-shadow: var(--dn), inset 0 0 0 100px var(--el);
399
- caret-color: var(--ac);
437
+ -webkit-text-fill-color: var(--fertig-fg);
438
+ box-shadow: var(--fertig-dn), inset 0 0 0 100px var(--fertig-el);
439
+ caret-color: var(--fertig-ac);
400
440
  }
401
- :disabled { color: var(--mut); cursor: not-allowed; box-shadow: none }
441
+ :disabled { color: var(--fertig-mut); cursor: not-allowed; box-shadow: none }
402
442
  /* No notched border: the legend becomes the panel title. The float is not
403
443
  nostalgia — a legend keeps its magic placement under block, grid and flex
404
444
  fieldsets alike, and floating it is still the only way out. */
405
445
  fieldset {
406
- border: 1px solid var(--bd); border-radius: var(--r); padding: .9rem 1rem 1rem;
407
- background: var(--face);
446
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw); padding: .9rem 1rem 1rem;
447
+ background: var(--fertig-face);
408
448
  }
409
- fieldset:focus-within { border-color: color-mix(in srgb, var(--ac) 45%, var(--bd)) }
449
+ fieldset:focus-within { border-color: color-mix(in srgb, var(--fertig-ac) 45%, var(--fertig-bd)) }
410
450
  legend {
411
451
  float: left; width: 100%; margin-bottom: .8rem; padding: 0 0 .6rem;
412
- border-bottom: 1px solid var(--bd);
413
- font-size: .95em; font-weight: 700; color: var(--fg);
452
+ border-bottom: 1px solid var(--fertig-bd);
453
+ font-size: .95em; font-weight: 700; color: var(--fertig-fg);
414
454
  }
415
455
  legend + * { clear: left }
416
456
 
417
457
  button, [type=submit], [type=button], [type=reset],
418
458
  :where(a.primary, a[role=button]), ::file-selector-button {
419
459
  display: inline-block; text-decoration: none; text-align: center;
420
- padding: .48em 1.1em; background: var(--el); color: var(--fg); cursor: pointer;
421
- border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--up);
460
+ padding: .48em 1.1em; background: var(--fertig-el); color: var(--fertig-fg); cursor: pointer;
461
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rp); box-shadow: var(--fertig-up);
422
462
  font: inherit; min-width: 5rem;
423
463
  }
424
- :is(button,[type=submit],[type=button],[type=reset],a.primary,a[role=button]):hover:not(:disabled) { background: var(--face) }
464
+ :is(button,[type=submit],[type=button],[type=reset],a.primary,a[role=button]):hover:not(:disabled) { background: var(--fertig-face) }
425
465
  :is(button,[type=submit],[type=button],[type=reset],a.primary,a[role=button]):active:not(:disabled) {
426
- box-shadow: var(--dn); translate: 0 .5px;
466
+ box-shadow: var(--fertig-dn); translate: 0 .5px;
427
467
  }
428
- [type=submit], .primary { background: var(--ac); border-color: transparent; color: var(--on-ac) }
468
+ [type=submit], .primary { background: var(--fertig-ac); border-color: transparent; color: var(--fertig-on-ac) }
429
469
  /* The filled button lightens on hover. color-mix does that in oklab, which is
430
470
  close enough and is Baseline; relative colour does it by adding to L
431
- directly, which holds the hue exactly when --ac is a wide-gamut override.
432
- Both derive from --ac, so a custom accent never needs a second declaration. */
471
+ directly, which holds the hue exactly when --fertig-ac is a wide-gamut override.
472
+ Both derive from --fertig-ac, so a custom accent never needs a second declaration. */
433
473
  :is([type=submit], .primary, a.primary):hover:not(:disabled) {
434
- background: color-mix(in oklab, var(--ac), #fff 9%);
474
+ background: color-mix(in oklab, var(--fertig-ac), #fff 9%);
435
475
  }
436
476
  @supports (color: oklch(from red l c h)) {
437
477
  :is([type=submit], .primary, a.primary):hover:not(:disabled) {
438
- background: oklch(from var(--ac) calc(l + .05) c h);
478
+ background: oklch(from var(--fertig-ac) calc(l + .05) c h);
439
479
  }
440
480
  }
441
481
 
@@ -448,18 +488,18 @@ button, [type=submit], [type=button], [type=reset],
448
488
  button[data-icon] { min-width: 0; padding: .38em .62em; line-height: 1 }
449
489
  :is(button, a, input, select, label)[data-block] { display: block; width: 100%; min-width: 0 }
450
490
  :is(button, a, input)[data-variant=ghost] { background: none; border-color: transparent; box-shadow: none }
451
- :is(button, a, input)[data-variant=ghost]:hover:not(:disabled) { background: var(--face); border-color: var(--bd) }
491
+ :is(button, a, input)[data-variant=ghost]:hover:not(:disabled) { background: var(--fertig-face); border-color: var(--fertig-bd) }
452
492
  :is(button, a, input)[data-variant=link] {
453
493
  background: none; border: 0; box-shadow: none; min-width: 0; padding-inline: .2em;
454
- color: var(--ac); text-decoration: underline; text-underline-offset: 3px;
494
+ color: var(--fertig-ac); text-decoration: underline; text-underline-offset: 3px;
455
495
  }
456
496
  :is(button, a, input)[data-variant=link]:hover:not(:disabled) { background: none; text-decoration-thickness: 2px }
457
- button[aria-pressed=true] { background: var(--face); color: var(--ac); font-weight: 700 }
497
+ button[aria-pressed=true] { background: var(--fertig-face); color: var(--fertig-ac); font-weight: 700 }
458
498
  button[aria-pressed=true]:hover:not(:disabled) {
459
- background: color-mix(in srgb, var(--fg) 7%, var(--face));
499
+ background: color-mix(in srgb, var(--fertig-fg) 7%, var(--fertig-face));
460
500
  }
461
501
  /* filled buttons get a lit top edge, the way a real control does */
462
- :is([type=submit], .primary) { box-shadow: var(--up), inset 0 1px 0 light-dark(#ffffff33, #ffffff26) }
502
+ :is([type=submit], .primary) { box-shadow: var(--fertig-up), inset 0 1px 0 light-dark(#ffffff33, #ffffff26) }
463
503
  :is([type=submit], .primary):active:not(:disabled) { box-shadow: inset 0 1px 3px #0006 }
464
504
 
465
505
  @supports (corner-shape: squircle) {
@@ -474,51 +514,79 @@ button[aria-pressed=true]:hover:not(:disabled) {
474
514
 
475
515
  /* ---- disclosure / misc -------------------------------------- */
476
516
  details {
477
- border: 1px solid var(--bd); border-radius: var(--r);
478
- padding: .7rem 1.1rem; background: var(--el);
517
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw);
518
+ padding: .7rem 1.1rem; background: var(--fertig-el);
479
519
  }
480
520
  summary { cursor: pointer; font-weight: 700; list-style: none }
481
- summary::before { content: "▸ "; color: var(--mut) }
521
+ summary::before { content: "▸ "; color: var(--fertig-mut) }
482
522
  details[open] > summary::before { content: "▾ " }
483
523
  details[open] > summary { margin-bottom: .6rem }
484
524
  dialog {
485
- background: var(--el); color: var(--fg); border: 1px solid var(--bd);
486
- border-radius: var(--rw); padding: 1.4rem; max-width: min(30rem, 92dvw);
487
- box-shadow: 0 32px 64px -16px color-mix(in srgb, var(--sh) 45%, transparent);
525
+ background: var(--fertig-el); color: var(--fertig-fg); border: 1px solid var(--fertig-bd);
526
+ border-radius: var(--fertig-rw); padding: 1.6rem; max-width: min(30rem, 92dvw);
527
+ box-shadow: 0 1px 1px color-mix(in srgb, var(--fertig-sh) 8%, transparent),
528
+ 0 28px 60px -22px color-mix(in srgb, var(--fertig-sh) 28%, transparent);
488
529
  }
489
530
  dialog > :last-child:not(footer) { margin-bottom: 0 }
490
531
  dialog, [popover] { overscroll-behavior: contain }
491
- dialog::backdrop { background: color-mix(in srgb, var(--sh) 35%, transparent); backdrop-filter: blur(3px) }
532
+ dialog::backdrop { background: color-mix(in srgb, var(--fertig-sh) 32%, transparent); backdrop-filter: blur(4px) }
492
533
 
493
534
  /* dialog anatomy: an optional header and footer, pinned while the body scrolls.
494
535
  Sticky rather than a grid, because a classless sheet has no wrapper around
495
- the loose middle children to make a grid row out of. */
536
+ the loose middle children to make a grid row out of.
537
+ No dividing rules: a short dialog cut into three banded strips is the look
538
+ of a decade-old modal. The separation is spacing, and the hairline only
539
+ arrives when there is actually something scrolling under it — see below. */
496
540
  dialog:has(> :is(header, footer)) { max-block-size: min(44rem, 85dvh); overflow: auto }
497
541
  dialog > :is(header, footer) {
498
542
  position: sticky; z-index: 1;
499
- margin-inline: -1.4rem; padding: .7rem 1.4rem;
500
- background: var(--el); /* the dialog's own surface, so it reads as one sheet */
543
+ margin-inline: -1.6rem;
544
+ background: var(--fertig-el); /* the dialog's own surface, so it reads as one sheet */
501
545
  }
502
546
  dialog > header {
503
- inset-block-start: -1.4rem; margin-block: -1.4rem 1rem;
504
- font-weight: 700; border-bottom: 1px solid var(--bd);
505
- border-radius: var(--rw) var(--rw) 0 0;
547
+ inset-block-start: -1.6rem; margin-block: -1.6rem .35rem;
548
+ padding: 1.6rem 1.6rem .5rem; font-weight: 700;
549
+ border-radius: var(--fertig-rw) var(--fertig-rw) 0 0;
550
+ }
551
+ /* The title carries the header's weight, so it needs no margins of its own,
552
+ and it is UI text: it takes --fertig-f, the interface face, not whatever
553
+ display font the page gave its headings — a monospaced heading face reads
554
+ as a terminal once it sits on a dialog. :is([open], :modal) is there for
555
+ weight — a page's `.prose h3` outranks a bare element chain, and the title
556
+ would come out sized and lettered for the article column it is nowhere near. */
557
+ dialog:is([open], :modal) > header > :is(h1, h2, h3, h4, p) {
558
+ margin: 0; font-family: var(--fertig-f); font-size: 1.1rem; font-weight: 600;
559
+ line-height: 1.3; letter-spacing: -.01em;
506
560
  }
507
- /* the title carries the header's weight; it does not need its own margins */
508
- dialog > header > :is(h1, h2, h3, h4, p) { margin: 0; font-size: 1.05rem }
509
561
  dialog > footer {
510
- inset-block-end: -1.4rem; margin-block: 1rem -1.4rem;
511
- border-top: 1px solid var(--bd); border-radius: 0 0 var(--rw) var(--rw);
562
+ inset-block-end: -1.6rem; margin-block: 1.35rem -1.6rem;
563
+ padding: .5rem 1.6rem 1.6rem; border-radius: 0 0 var(--fertig-rw) var(--fertig-rw);
512
564
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
513
565
  justify-content: flex-end; /* actions sit at the end edge */
514
566
  }
567
+ /* The hairline is scroll state, not decoration: it appears under the header
568
+ once the body has scrolled, and leaves the footer once you reach the end.
569
+ A scroll timeline on a container with nothing to scroll is inactive, so a
570
+ dialog that fits shows no rules at all and no script decides that. */
571
+ @supports (animation-timeline: scroll()) {
572
+ dialog > header {
573
+ animation: fertig-rule-under linear both;
574
+ animation-timeline: scroll(nearest); animation-range: 0 1.2rem;
575
+ }
576
+ dialog > footer {
577
+ animation: fertig-rule-over linear both;
578
+ animation-timeline: scroll(nearest); animation-range: calc(100% - 1.2rem) 100%;
579
+ }
580
+ @keyframes fertig-rule-under { from { box-shadow: none } to { box-shadow: 0 1px 0 var(--fertig-bd) } }
581
+ @keyframes fertig-rule-over { from { box-shadow: 0 -1px 0 var(--fertig-bd) } to { box-shadow: none } }
582
+ }
515
583
  /* a note beside the buttons pushes them over, rather than sitting under them */
516
- dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto; color: var(--mut) }
584
+ dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto; color: var(--fertig-mut) }
517
585
  /* sheets come up, they do not appear */
518
586
  @media (prefers-reduced-motion: no-preference) and (update: fast) {
519
587
  dialog, dialog::backdrop {
520
- transition: opacity var(--dur-2) var(--ease-out), translate var(--dur-2) var(--ease-out),
521
- display var(--dur-2) allow-discrete, overlay var(--dur-2) allow-discrete;
588
+ transition: opacity var(--fertig-dur-2) var(--fertig-ease-out), translate var(--fertig-dur-2) var(--fertig-ease-out),
589
+ display var(--fertig-dur-2) allow-discrete, overlay var(--fertig-dur-2) allow-discrete;
522
590
  }
523
591
  dialog { opacity: 0; translate: 0 10px }
524
592
  dialog[open] { opacity: 1; translate: 0 }
@@ -528,24 +596,24 @@ dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto;
528
596
  @starting-style { dialog[open]::backdrop { opacity: 0 } }
529
597
  [popover] {
530
598
  opacity: 0; translate: 0 -4px;
531
- transition: opacity .13s var(--ease-out), translate .13s var(--ease-out),
599
+ transition: opacity .13s var(--fertig-ease-out), translate .13s var(--fertig-ease-out),
532
600
  display .13s allow-discrete, overlay .13s allow-discrete;
533
601
  }
534
602
  [popover]:popover-open { opacity: 1; translate: 0 }
535
603
  @starting-style { [popover]:popover-open { opacity: 0; translate: 0 -4px } }
536
604
  }
537
605
  search { display: block; margin-block: 1.1rem } /* a landmark needs its own rhythm */
538
- ::target-text { background: color-mix(in srgb, var(--ac) 28%, transparent) }
606
+ ::target-text { background: color-mix(in srgb, var(--fertig-ac) 28%, transparent) }
539
607
  aside {
540
- border: 1px solid var(--bd); border-inline-start: 3px solid var(--ac);
541
- border-radius: var(--r); border-start-start-radius: 0; border-end-start-radius: 0;
542
- background: var(--el); padding: .7rem 1.1rem;
608
+ border: 1px solid var(--fertig-bd); border-inline-start: 3px solid var(--fertig-ac);
609
+ border-radius: var(--fertig-rw); border-start-start-radius: 0; border-end-start-radius: 0;
610
+ background: var(--fertig-el); padding: .7rem 1.1rem;
543
611
  margin-block: 1.1rem; /* or the next block sits flush against it */
544
612
  }
545
- progress { width: 100%; height: .5rem; border: 0; border-radius: calc(infinity * 1px); background: var(--face) }
546
- progress::-webkit-progress-bar { background: var(--face); border-radius: calc(infinity * 1px) }
547
- progress::-webkit-progress-value { background: var(--ac); border-radius: calc(infinity * 1px) }
548
- progress::-moz-progress-bar { background: var(--ac); border-radius: calc(infinity * 1px) }
613
+ progress { width: 100%; height: .5rem; border: 0; border-radius: var(--fertig-rp); background: var(--fertig-face) }
614
+ progress::-webkit-progress-bar { background: var(--fertig-face); border-radius: var(--fertig-rp) }
615
+ progress::-webkit-progress-value { background: var(--fertig-ac); border-radius: var(--fertig-rp) }
616
+ progress::-moz-progress-bar { background: var(--fertig-ac); border-radius: var(--fertig-rp) }
549
617
 
550
618
  /* details can only animate to auto height once keywords interpolate */
551
619
  @supports selector(::details-content) {
@@ -556,63 +624,71 @@ progress::-moz-progress-bar { background: var(--ac); border-radius: calc(infinit
556
624
  details { interpolate-size: allow-keywords }
557
625
  details::details-content {
558
626
  block-size: 0; overflow: clip;
559
- transition: block-size .28s var(--ease), content-visibility .28s allow-discrete;
627
+ transition: block-size .28s var(--fertig-ease), content-visibility .28s allow-discrete;
560
628
  }
561
629
  details[open]::details-content { block-size: auto }
562
630
  }
563
631
  }
564
632
 
565
- /* #core-omit-start */
566
633
  /* ---- components, addressed by ARIA rather than by class ----- */
567
634
 
568
635
  /* segmented control: <div role=group> of buttons */
569
636
  /* max-inline-size + scroll, or a group of four or more buttons pushes the
570
637
  whole page sideways on a narrow screen. It scrolls rather than wraps:
571
- wrapping would break the joined-pill run of radii into separate rows. */
572
- [role=group] { display: inline-flex; vertical-align: middle;
573
- max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain }
638
+ wrapping would break the joined-pill run of radii into separate rows.
639
+ The block axis is clipped rather than left to compute to auto alongside it:
640
+ Safari paints a vertical scrollbar for a sub-pixel of overflow, and the row
641
+ has no vertical overflow worth scrolling to in the first place. */
642
+ [role=group] { display: inline-flex; vertical-align: middle; max-inline-size: 100%;
643
+ overflow: auto clip; overscroll-behavior-x: contain }
574
644
  [role=group] > * { border-radius: 0; margin-inline-start: -1px; box-shadow: none; min-width: 0 }
645
+ /* The buttons fill the group exactly and the group clips on the block axis,
646
+ so an outset ring would be sliced off. Inside the edge, it is all there. */
647
+ [role=group] > :focus-visible { outline-offset: -3px }
575
648
  [role=group] > :first-child {
576
649
  margin-inline-start: 0;
577
- border-start-start-radius: var(--r); border-end-start-radius: var(--r);
650
+ border-start-start-radius: var(--fertig-rp); border-end-start-radius: var(--fertig-rp);
578
651
  }
579
652
  [role=group] > :last-child {
580
- border-start-end-radius: var(--r); border-end-end-radius: var(--r);
653
+ border-start-end-radius: var(--fertig-rp); border-end-end-radius: var(--fertig-rp);
581
654
  }
582
- [role=group] > [aria-pressed=true] { background: var(--face); color: var(--ac); font-weight: 700 }
655
+ [role=group] > [aria-pressed=true] { background: var(--fertig-face); color: var(--fertig-ac); font-weight: 700 }
656
+ /* The press nudge would push a button half a pixel past the scroll port and
657
+ flash a scrollbar, so joined buttons stay put and read the press by shadow. */
658
+ [role=group] > :active:not(:disabled) { translate: none }
583
659
 
584
660
  /* tabs: <div role=tablist> of <button role=tab aria-selected> */
585
- [role=tablist] { display: flex; gap: .1rem; border-bottom: 1px solid var(--bd); margin: 1.1rem 0 }
661
+ [role=tablist] { display: flex; gap: .1rem; border-bottom: 1px solid var(--fertig-bd); margin: 1.1rem 0 }
586
662
  [role=tab] {
587
- background: none; border: 0; box-shadow: none; min-width: 0; color: var(--mut);
588
- padding: .45em .9em; border-radius: var(--r) var(--r) 0 0; margin-bottom: -1px;
663
+ background: none; border: 0; box-shadow: none; min-width: 0; color: var(--fertig-mut);
664
+ padding: .45em .9em; border-radius: var(--fertig-r) var(--fertig-r) 0 0; margin-bottom: -1px;
589
665
  }
590
- [role=tab]:hover:not(:disabled) { background: var(--face); color: var(--fg) }
591
- [role=tab][aria-selected=true] { color: var(--fg); box-shadow: inset 0 -2px 0 var(--ac) }
666
+ [role=tab]:hover:not(:disabled) { background: var(--fertig-face); color: var(--fertig-fg) }
667
+ [role=tab][aria-selected=true] { color: var(--fertig-fg); box-shadow: inset 0 -2px 0 var(--fertig-ac) }
592
668
 
593
669
  /* switch: <input type=checkbox role=switch> */
594
670
  input[type=checkbox][switch],
595
671
  input[type=checkbox][role=switch] {
596
- appearance: none; width: 2.2em; height: 1.3em; border-radius: calc(infinity * 1px); border: 0;
597
- background: color-mix(in srgb, var(--fg) 20%, transparent);
672
+ appearance: none; width: 2.2em; height: 1.3em; border-radius: var(--fertig-rp); border: 0;
673
+ background: color-mix(in srgb, var(--fertig-fg) 20%, transparent);
598
674
  position: relative; cursor: pointer; vertical-align: -.3em; margin-inline-end: .5em;
599
675
  }
600
676
  input[switch]::after, input[role=switch]::after {
601
677
  content: ""; position: absolute; inset-block-start: .14em; inset-inline-start: .14em;
602
- width: 1.02em; height: 1.02em; border-radius: 50%; background: #fff; box-shadow: var(--up);
603
- transition: translate .16s var(--ease-out);
678
+ width: 1.02em; height: 1.02em; border-radius: 50%; background: #fff; box-shadow: var(--fertig-up);
679
+ transition: translate .16s var(--fertig-ease-out);
604
680
  }
605
- input[switch]:checked, input[role=switch]:checked { background: var(--ac) }
681
+ input[switch]:checked, input[role=switch]:checked { background: var(--fertig-ac) }
606
682
  /* translate is physical, so the knob is told which way to travel */
607
683
  input[switch]:checked::after, input[role=switch]:checked::after { translate: .9em 0 }
608
684
  :is(input[switch], input[role=switch]):checked:dir(rtl)::after { translate: -.9em 0 }
609
685
 
610
686
  /* menu: any [popover] */
611
687
  [popover] {
612
- background: var(--el); color: var(--fg); border: 1px solid var(--bd);
613
- border-radius: var(--r); padding: .3rem; min-width: 12rem;
688
+ background: var(--fertig-el); color: var(--fertig-fg); border: 1px solid var(--fertig-bd);
689
+ border-radius: var(--fertig-rw); padding: .35rem; min-width: 12rem;
614
690
  max-block-size: 60dvh; overflow: auto; font-size: .95em;
615
- box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
691
+ box-shadow: var(--fertig-up2), 0 24px 44px -18px color-mix(in srgb, var(--fertig-sh) 28%, transparent);
616
692
  }
617
693
  /* anchor to the invoking button; without support it centres, which is fine.
618
694
  Deliberately NOT scoped to :popover-open — the close is transitioned, so a
@@ -639,14 +715,14 @@ input[switch]:checked::after, input[role=switch]:checked::after { translate: .9e
639
715
  [popover] :is(a, button):not(:disabled) {
640
716
  display: block; width: 100%; text-align: start; background: none; border: 0;
641
717
  box-shadow: none; min-width: 0; padding: .4em .6em; text-decoration: none;
642
- color: inherit; border-radius: calc(var(--r) - 2px); font-weight: 400;
718
+ color: inherit; border-radius: calc(var(--fertig-r) - 2px); font-weight: 400;
643
719
  }
644
- [popover] :is(a, button):hover:not(:disabled) { background: var(--ac); color: var(--on-ac) }
720
+ [popover] :is(a, button):hover:not(:disabled) { background: var(--fertig-ac); color: var(--fertig-on-ac) }
645
721
  [popover] hr { margin: .3rem 0 }
646
722
 
647
723
  /* breadcrumbs: <nav aria-label="Breadcrumb"><ol> */
648
724
  nav[aria-label] ol { display: flex; flex-wrap: wrap; gap: .5em; list-style: none; padding: 0; font-size: .9em }
649
- nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; color: var(--mut) }
725
+ nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; color: var(--fertig-mut) }
650
726
 
651
727
  /* busy: [aria-busy=true] */
652
728
  [aria-busy=true] { cursor: progress }
@@ -671,16 +747,18 @@ nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; colo
671
747
  inset-block-end: 100%; inset-inline-start: 50%;
672
748
  translate: -50% -.45em; z-index: 10; white-space: nowrap; pointer-events: none;
673
749
  background: light-dark(#1d1d1f, #3a3a40); color: #fff; font-size: .8em;
674
- padding: .25em .55em; border-radius: var(--r); box-shadow: var(--up2);
750
+ padding: .25em .55em; border-radius: var(--fertig-r); box-shadow: var(--fertig-up2);
675
751
  }
676
752
  /* translate is physical; the offset has to be told which way to go */
677
753
  [data-tooltip]:dir(rtl)::after { translate: 50% -.45em }
678
754
 
679
755
  /* tones: retint any component by redefining its accent */
680
- [data-tone=ok] { --ac: light-dark(var(--sage-700), oklch(76% .085 155)) }
681
- [data-tone=warn] { --ac: light-dark(var(--gold-700), oklch(80% .095 85)) }
682
- [data-tone=err] { --ac: light-dark(var(--clay-700), oklch(75% .105 35)) }
683
- [data-tone] .badge, .badge[data-tone] { color: var(--ac); border-color: var(--ac) }
756
+ [data-tone=ok] { --fertig-ac: light-dark(var(--fertig-sage-700), oklch(76% .085 155)) }
757
+ [data-tone=warn] { --fertig-ac: light-dark(var(--fertig-gold-700), oklch(80% .095 85)) }
758
+ /* err is the one tone that does not come from the palette: clay at this
759
+ lightness reads as brown, and a destructive button has to read as red. */
760
+ [data-tone=err] { --fertig-ac: light-dark(oklch(52% .16 27), oklch(74% .13 25)) }
761
+ [data-tone] .badge, .badge[data-tone] { color: var(--fertig-ac); border-color: var(--fertig-ac) }
684
762
 
685
763
  /* ---- parity with the component libraries --------------------- */
686
764
 
@@ -688,18 +766,18 @@ nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; colo
688
766
  [data-avatar] {
689
767
  display: inline-grid; place-items: center; flex: none; overflow: hidden;
690
768
  inline-size: 2.5rem; aspect-ratio: 1; border-radius: 50%;
691
- background: var(--face); border: 1px solid var(--bd);
692
- font-size: .8em; font-weight: 700; color: var(--mut); object-fit: cover;
769
+ background: var(--fertig-face); border: 1px solid var(--fertig-bd);
770
+ font-size: .8em; font-weight: 700; color: var(--fertig-mut); object-fit: cover;
693
771
  }
694
772
  [data-avatar=sm] { inline-size: 1.75rem; font-size: .7em }
695
773
  [data-avatar=lg] { inline-size: 3.5rem; font-size: 1em }
696
774
 
697
775
  /* skeleton: put it on the element that is still loading */
698
776
  [data-skeleton] {
699
- color: transparent; border-radius: var(--r); pointer-events: none;
777
+ color: transparent; border-radius: var(--fertig-r); pointer-events: none;
700
778
  user-select: none; min-height: 1em;
701
- background: linear-gradient(90deg, var(--face),
702
- color-mix(in srgb, var(--fg) 9%, var(--face)), var(--face));
779
+ background: linear-gradient(90deg, var(--fertig-face),
780
+ color-mix(in srgb, var(--fertig-fg) 9%, var(--fertig-face)), var(--fertig-face));
703
781
  background-size: 200% 100%;
704
782
  }
705
783
  [data-skeleton] * { visibility: hidden }
@@ -708,7 +786,13 @@ nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; colo
708
786
  @keyframes fertig-shimmer { to { background-position: -200% 0 } }
709
787
  }
710
788
 
711
- /* sheet / drawer: the same <dialog>, docked to an edge */
789
+ /* sheet / drawer: the same <dialog>, docked to an edge.
790
+ Docked to a physical edge on purpose — "right" means the right edge of the
791
+ screen, not the end of the writing direction. inset is physical, so a drawer
792
+ slides in from the edge its name promises no matter the direction. The
793
+ translate is physical for the same reason: 100% of the drawer's own inline
794
+ width moves it off the edge it came from. RTL readers still get the same
795
+ right/left drawer as anyone else; direction would make "right" ambiguous. */
712
796
  dialog[data-side] { max-width: none; margin: 0; border-radius: 0; padding: 1.4rem }
713
797
  dialog[data-side=right] {
714
798
  inset: 0 0 0 auto; block-size: 100dvh; inline-size: min(28rem, 92dvw);
@@ -720,7 +804,7 @@ dialog[data-side=left] {
720
804
  }
721
805
  dialog[data-side=bottom] {
722
806
  inset: auto 0 0 0; inline-size: 100%; max-block-size: 85dvh;
723
- border-radius: var(--rw) var(--rw) 0 0; border-bottom: 0;
807
+ border-radius: var(--fertig-rw) var(--fertig-rw) 0 0; border-bottom: 0;
724
808
  }
725
809
  @media (prefers-reduced-motion: no-preference) and (update: fast) {
726
810
  dialog[data-side=right] { translate: 100% 0 }
@@ -760,9 +844,9 @@ dialog[data-side=bottom] {
760
844
  [popover][data-toast-region] > [data-toast] {
761
845
  pointer-events: auto; position: static; inset: auto;
762
846
  min-width: 16rem; max-width: min(24rem, 92dvw); padding: .8rem 1rem;
763
- background: var(--el); color: var(--fg); border: 1px solid var(--bd);
764
- border-radius: var(--r); font-size: .95em;
765
- box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
847
+ background: var(--fertig-el); color: var(--fertig-fg); border: 1px solid var(--fertig-bd);
848
+ border-radius: var(--fertig-rw); font-size: .95em;
849
+ box-shadow: var(--fertig-up2), 0 24px 44px -18px color-mix(in srgb, var(--fertig-sh) 28%, transparent);
766
850
  }
767
851
  @supports (position-area: block-end span-inline-end) {
768
852
  [popover][data-toast-region] { position-area: none; inset: auto 1rem 1rem auto }
@@ -771,10 +855,10 @@ dialog[data-side=bottom] {
771
855
  /* list group: a plain <menu> outside a popover is a bordered list */
772
856
  menu {
773
857
  list-style: none; padding: 0; margin: 1.1rem 0;
774
- border: 1px solid var(--bd); border-radius: var(--r); background: var(--el);
858
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw); background: var(--fertig-el);
775
859
  }
776
860
  menu > li { padding: .55rem .85rem }
777
- menu > li + li { border-top: 1px solid var(--bd) }
861
+ menu > li + li { border-top: 1px solid var(--fertig-bd) }
778
862
  [popover] menu { border: 0; padding: 0; margin: 0; background: none }
779
863
  [popover] menu > li { padding: 0 }
780
864
  [popover] menu > li + li { border-top: 0 }
@@ -790,13 +874,13 @@ nav menu > li { padding: 0 }
790
874
  nav menu > li + li { border-top: 0 }
791
875
  nav menu :is(a, button) {
792
876
  display: block; inline-size: 100%; text-align: start;
793
- padding: .38rem .6rem; border: 0; border-radius: var(--r);
877
+ padding: .38rem .6rem; border: 0; border-radius: var(--fertig-r);
794
878
  background: none; box-shadow: none; font: inherit;
795
- color: var(--fg); text-decoration: none; opacity: .72;
879
+ color: var(--fertig-fg); text-decoration: none; opacity: .72;
796
880
  }
797
- nav menu :is(a, button):hover { background: var(--face); opacity: 1 }
881
+ nav menu :is(a, button):hover { background: var(--fertig-face); opacity: 1 }
798
882
  nav menu :is(a, button)[aria-current] {
799
- background: var(--face); color: var(--ac); font-weight: 600; opacity: 1;
883
+ background: var(--fertig-face); color: var(--fertig-ac); font-weight: 600; opacity: 1;
800
884
  }
801
885
 
802
886
  /* app layout: a sticky sidebar beside the content */
@@ -814,16 +898,14 @@ nav menu :is(a, button)[aria-current] {
814
898
  }
815
899
  [data-carousel] > * { scroll-snap-align: start; flex: 0 0 min(20rem, 82%) }
816
900
 
817
- /* #core-omit-end */
818
-
819
901
  /* ---- the only classes (all optional) ------------------------ */
820
- .muted { color: var(--mut) }
902
+ .muted { color: var(--fertig-mut) }
821
903
  .center { text-align: center }
822
- .wide { max-width: 66rem }
823
- /* on the toolbar .wide cannot mean "be 66rem wide" — the bar stays full-bleed
904
+ .wide { max-width: 80rem }
905
+ /* on the toolbar .wide cannot mean "be 80rem wide" — the bar stays full-bleed
824
906
  and its contents move out to the wide column, so an app screen's wordmark
825
907
  lines up with the app's own content rather than floating mid-screen. */
826
- :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav.wide { max-width: none; --nw: 66rem }
908
+ :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav.wide { max-width: none; --fertig-nw: 80rem }
827
909
  .row { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center }
828
910
  /* Form controls are full-width blocks by default, which is right in a form and
829
911
  wrong in a toolbar. Inside a .row they size to the row instead. */
@@ -841,8 +923,8 @@ nav menu :is(a, button)[aria-current] {
841
923
  .grid + * { margin-top: 1.1rem }
842
924
  .card {
843
925
  container-type: inline-size; /* query it: @container (min-width: …) */
844
- border: 1px solid var(--bd); border-radius: var(--r); padding: 1.1rem;
845
- background: var(--el); box-shadow: var(--up2);
926
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rw); padding: 1.1rem;
927
+ background: var(--fertig-el); box-shadow: var(--fertig-up2);
846
928
  display: flex; flex-direction: column; /* so footers line up across a row */
847
929
  gap: .85rem; /* spacing is the container's job */
848
930
  }
@@ -898,13 +980,14 @@ nav menu :is(a, button)[aria-current] {
898
980
  .max-w-7xl { max-inline-size: 80rem }
899
981
  .max-w-full { max-inline-size: 100% }
900
982
  .max-w-none { max-inline-size: none }
901
- /* the measure the sheet already sets prose on, as a class you can reach for */
902
- .max-w-prose { max-inline-size: var(--w) }
983
+ /* Keep the prose utility readable even when the page shell is app-wide. */
984
+ .max-w-prose { max-inline-size: 38rem }
903
985
  .hidden { display: none }
904
986
 
905
987
  .badge {
906
- border: 1px solid var(--bd); border-radius: calc(infinity * 1px); background: var(--face);
907
- padding: .1em .6em; font: 700 var(--caps); color: var(--mut);
988
+ border: 1px solid var(--fertig-bd); border-radius: var(--fertig-rp); background: var(--fertig-face);
989
+ padding: .1em .6em; font: 700 var(--fertig-caps); color: var(--fertig-mut);
990
+ text-transform: uppercase; letter-spacing: .07em; /* a label, like h5/h6 and th */
908
991
  }
909
992
 
910
993
  /* card anatomy: an optional header, footer, and full-bleed media */
@@ -914,25 +997,25 @@ nav menu :is(a, button)[aria-current] {
914
997
  .card > :is(header, footer) { margin-inline: -1.1rem; padding-inline: 1.1rem }
915
998
  .card > header {
916
999
  margin-top: -.25rem; padding-bottom: .7rem; font-weight: 600;
917
- border-bottom: 1px solid var(--bd);
1000
+ border-bottom: 1px solid var(--fertig-bd);
918
1001
  }
919
1002
  .card > footer {
920
1003
  margin-top: auto; margin-bottom: -.25rem; padding-top: .7rem;
921
- color: var(--mut); font-size: .85em;
922
- border-top: 1px solid var(--bd);
1004
+ color: var(--fertig-mut); font-size: .85em;
1005
+ border-top: 1px solid var(--fertig-bd);
923
1006
  }
924
1007
  .card > :is(img, video):first-child {
925
1008
  align-self: stretch; width: auto; max-width: none; margin: -1.1rem -1.1rem 0;
926
1009
  aspect-ratio: 16 / 9; object-fit: cover;
927
- border: 0; border-radius: var(--r) var(--r) 0 0; box-shadow: none;
1010
+ border: 0; border-radius: var(--fertig-rw) var(--fertig-rw) 0 0; box-shadow: none;
928
1011
  }
929
1012
  /* A background layer rather than a border or an inset shadow: a border would
930
1013
  shift the card's geometry, and an inset shadow does not follow the corner
931
1014
  radius cleanly at this size — it reads as a detached bar with gaps at the
932
1015
  top corners. A gradient is clipped by the radius, so the tone hugs the curve. */
933
1016
  .card[data-tone] {
934
- background: linear-gradient(var(--ac), var(--ac)) top / 100% 3px no-repeat,
935
- var(--el);
1017
+ background: linear-gradient(var(--fertig-ac), var(--fertig-ac)) top / 100% 3px no-repeat,
1018
+ var(--fertig-el);
936
1019
  }
937
1020
 
938
1021
  /* a card that is itself a link or button reacts like one */
@@ -942,8 +1025,8 @@ nav menu :is(a, button)[aria-current] {
942
1025
  text-decoration: none; font: inherit;
943
1026
  }
944
1027
  :is(a, button).card:hover:not(:disabled) {
945
- border-color: color-mix(in srgb, var(--ac) 45%, var(--bd));
946
- box-shadow: var(--up2), 0 12px 26px -14px color-mix(in srgb, var(--sh) 40%, transparent);
1028
+ border-color: color-mix(in srgb, var(--fertig-ac) 45%, var(--fertig-bd));
1029
+ box-shadow: var(--fertig-up2), 0 16px 30px -16px color-mix(in srgb, var(--fertig-sh) 28%, transparent);
947
1030
  translate: 0 -2px;
948
1031
  }
949
1032
  :is(a, button).card:active:not(:disabled) { translate: 0 0 }
@@ -953,26 +1036,26 @@ nav menu :is(a, button)[aria-current] {
953
1036
  win over your own sheet; hidden=until-found must stay findable */
954
1037
  [hidden]:not([hidden=until-found]) { display: none }
955
1038
  @media (prefers-reduced-motion: no-preference) and (update: fast) { html { scroll-behavior: smooth } }
956
- :target { scroll-margin-top: 4rem } /* clears the toolbar */
957
- :is(h1,h2,h3,h4):target { background: color-mix(in srgb, var(--ac) 12%, transparent) }
1039
+ :target { scroll-margin-block-start: 4rem } /* clears the toolbar */
1040
+ :is(h1,h2,h3,h4):target { background: color-mix(in srgb, var(--fertig-ac) 12%, transparent) }
958
1041
  a[target=_blank]:not(:has(> img, > svg))::after { content: " ↗"; font-size: .85em; opacity: .6 }
959
1042
  a:has(> img), a:has(> code) { text-decoration: none }
960
1043
  sup, sub { line-height: 0; font-size: .75em } /* stop them stretching lines */
961
1044
  q { quotes: "“" "”" "‘" "’" }
962
1045
  dfn { font-style: normal; font-weight: 700 }
963
- ins { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: var(--ac) }
964
- del { color: var(--mut) }
965
- address { font-style: normal; color: var(--mut) }
1046
+ ins { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: var(--fertig-ac) }
1047
+ del { color: var(--fertig-mut) }
1048
+ address { font-style: normal; color: var(--fertig-mut) }
966
1049
  hgroup > * { margin: 0 }
967
- hgroup > p { color: var(--mut) }
1050
+ hgroup > p { color: var(--fertig-mut) }
968
1051
  output { font-weight: 700 }
969
1052
  meter { width: 100%; height: .5rem }
970
- optgroup { font-weight: 700; color: var(--mut) }
1053
+ optgroup { font-weight: 700; color: var(--fertig-mut) }
971
1054
  ::file-selector-button { margin-inline-end: .8em }
972
- ::file-selector-button:hover { background: var(--face) }
1055
+ ::file-selector-button:hover { background: var(--fertig-face) }
973
1056
  input:user-invalid, textarea:user-invalid { border-color: light-dark(#c0392b, #ff6b6b) }
974
1057
  input:user-invalid + small { color: light-dark(#c0392b, #ff6b6b) }
975
- summary:hover { color: var(--ac) }
1058
+ summary:hover { color: var(--fertig-ac) }
976
1059
  /* thin scrollbars, macOS weight */
977
1060
  /* scrollbar-width is Safari 18.2, scrollbar-color Safari 26.2 — both younger
978
1061
  than the floor, and both left ungated on purpose: there is no fallback to
@@ -985,12 +1068,12 @@ summary:hover { color: var(--ac) }
985
1068
  :is(pre, table, textarea, dialog, [popover], menu, fieldset,
986
1069
  [role=group], [data-carousel], [data-layout=sidebar] > *) {
987
1070
  scrollbar-width: thin;
988
- scrollbar-color: color-mix(in srgb, var(--fg) 25%, transparent) transparent;
1071
+ scrollbar-color: color-mix(in srgb, var(--fertig-fg) 25%, transparent) transparent;
989
1072
  }
990
1073
 
991
1074
  @page { margin: 2cm }
992
1075
  @media print {
993
- :root { --bg: #fff; --el: #fff; --fg: #000; --bd: #999 }
1076
+ :root { --fertig-bg: #fff; --fertig-el: #fff; --fertig-fg: #000; --fertig-bd: #999 }
994
1077
  :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav { display: none }
995
1078
  /* the footer stays: the legal line, the contact and the citations live there,
996
1079
  and a printout without them is missing the part people keep */
@@ -1023,7 +1106,7 @@ summary:hover { color: var(--ac) }
1023
1106
  /* a wider gamut can hold a cleaner accent than sRGB can */
1024
1107
  @media (color-gamut: p3) {
1025
1108
  @supports (color: oklch(60% .2 250)) {
1026
- :root { --ac: light-dark(oklch(52% .21 258), oklch(76% .15 258)) }
1109
+ :root { --fertig-ac: light-dark(oklch(52% .21 258), oklch(76% .15 258)) }
1027
1110
  }
1028
1111
  }
1029
1112
 
@@ -1032,14 +1115,16 @@ summary:hover { color: var(--ac) }
1032
1115
 
1033
1116
  /* translucency is decorative; drop it when it is unwanted */
1034
1117
  @media (prefers-reduced-transparency: reduce) {
1035
- body > nav { background: var(--face); backdrop-filter: none }
1118
+ :is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav {
1119
+ background: var(--fertig-face); backdrop-filter: none;
1120
+ }
1036
1121
  dialog::backdrop { backdrop-filter: none }
1037
1122
  }
1038
1123
 
1039
1124
  @media (prefers-contrast: more) {
1040
1125
  :root {
1041
- --bd: light-dark(#00000059, #ffffff6b);
1042
- --mut: light-dark(#45454a, #c6c6ce);
1126
+ --fertig-bd: light-dark(#00000059, #ffffff6b);
1127
+ --fertig-mut: light-dark(#45454a, #c6c6ce);
1043
1128
  }
1044
1129
  }
1045
1130
 
@@ -1047,11 +1132,11 @@ summary:hover { color: var(--ac) }
1047
1132
  a, button, input, select, textarea, summary, tr,
1048
1133
  [type=submit], [type=button], [type=reset], .card, .badge,
1049
1134
  [role=tab], [role=group] > *, [popover] :is(a, button) {
1050
- transition: box-shadow var(--dur-1) var(--ease),
1051
- background-color var(--dur-1) var(--ease),
1052
- border-color var(--dur-1) var(--ease),
1053
- color var(--dur-1) var(--ease),
1054
- translate .08s var(--ease-out);
1135
+ transition: box-shadow var(--fertig-dur-1) var(--fertig-ease),
1136
+ background-color var(--fertig-dur-1) var(--fertig-ease),
1137
+ border-color var(--fertig-dur-1) var(--fertig-ease),
1138
+ color var(--fertig-dur-1) var(--fertig-ease),
1139
+ translate .08s var(--fertig-ease-out);
1055
1140
  }
1056
1141
  /* the ring must never be animated — it has to be there the instant focus
1057
1142
  lands, not fade in after it */
@@ -1065,25 +1150,25 @@ summary:hover { color: var(--ac) }
1065
1150
  computed-value time, so backgrounds go transparent and colour is inherited,
1066
1151
  and the page can land unreadable rather than plain. These are the same
1067
1152
  colours flattened to sRGB. Last in the layer, so it wins on order over the
1068
- token block at the top. Nothing else needs a twin: an invalid --ac on a
1153
+ token block at the top. Nothing else needs a twin: an invalid --fertig-ac on a
1069
1154
  [data-tone] or [data-accent] element just inherits the one from :root. */
1070
1155
  @supports not (color: light-dark(#000, #fff)) {
1071
1156
  :root {
1072
- --bg: #ece9e5; --el: #fefdfc; --face: #f8f5f2; --fg: #1d1a17;
1073
- --mut: #5e5a55; --bd: #1d1a1721; --tb: #f8f5f2d9;
1074
- --ac: #494fcc; --on-ac: #fff;
1157
+ --fertig-bg: #e6eaef; --fertig-el: #fafcfe; --fertig-face: #f3f6fa; --fertig-fg: #151b24;
1158
+ --fertig-mut: #525962; --fertig-bd: #151b2421; --fertig-tb: #f3f6fad9;
1159
+ --fertig-ac: #2850de; --fertig-on-ac: #fff;
1075
1160
  }
1076
1161
  @media (prefers-color-scheme: dark) {
1077
1162
  :root:not([data-theme=light]) {
1078
- --bg: #0f0d0a; --el: #1a1815; --face: #24211e; --fg: #f0eeec;
1079
- --mut: #aba7a2; --bd: #f0eeec21; --tb: #24211ed9;
1080
- --ac: #a3b2fe; --on-ac: #06121f;
1163
+ --fertig-bg: #090c12; --fertig-el: #141920; --fertig-face: #1c222a; --fertig-fg: #eaeff7;
1164
+ --fertig-mut: #9da5b1; --fertig-bd: #eaeff721; --fertig-tb: #1c222ad9;
1165
+ --fertig-ac: #90b5ff; --fertig-on-ac: #07071c;
1081
1166
  }
1082
1167
  }
1083
1168
  [data-theme=dark] {
1084
- --bg: #0f0d0a; --el: #1a1815; --face: #24211e; --fg: #f0eeec;
1085
- --mut: #aba7a2; --bd: #f0eeec21; --tb: #24211ed9;
1086
- --ac: #a3b2fe; --on-ac: #06121f;
1169
+ --fertig-bg: #090c12; --fertig-el: #141920; --fertig-face: #1c222a; --fertig-fg: #eaeff7;
1170
+ --fertig-mut: #9da5b1; --fertig-bd: #eaeff721; --fertig-tb: #1c222ad9;
1171
+ --fertig-ac: #90b5ff; --fertig-on-ac: #07071c;
1087
1172
  }
1088
1173
  }
1089
1174