fertig 1.0.2 → 1.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 +168 -0
- package/README.md +163 -44
- package/fertig.core.min.css +1 -1
- package/fertig.css +303 -84
- package/fertig.min.css +1 -1
- package/package.json +4 -3
package/fertig.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! fertig v1.0
|
|
1
|
+
/*! fertig v1.1.0 — a classless CSS stylesheet. MIT */
|
|
2
2
|
|
|
3
3
|
/* Opinions, so you don't have to have them:
|
|
4
4
|
system type · one accent · one measure · a window on a desktop.
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
Override the tokens marked "yours"; the rest is the point of view. */
|
|
8
8
|
|
|
9
9
|
/* typed so they interpolate; untyped custom properties cannot animate */
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/* Registrations are global: they cannot be layered, scoped or overridden.
|
|
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
14
|
|
|
13
15
|
/* Two layers, in this order. Your own unlayered CSS wins over both regardless
|
|
14
16
|
of specificity, so overriding never needs !important — and neither does this
|
|
@@ -30,8 +32,9 @@
|
|
|
30
32
|
--fm: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
|
31
33
|
"Liberation Mono", monospace; /* code */
|
|
32
34
|
--w: 38rem; /* ~70 characters — yours */
|
|
33
|
-
--r:
|
|
34
|
-
--
|
|
35
|
+
--r: 9px; /* control radius */
|
|
36
|
+
--rs: 5px; /* small radius: inline chips and code */
|
|
37
|
+
--rw: 14px; /* window radius */
|
|
35
38
|
|
|
36
39
|
/* ---- palette ------------------------------------------------
|
|
37
40
|
Low chroma on purpose. Vivid palettes shout; these are meant to sit
|
|
@@ -65,32 +68,77 @@
|
|
|
65
68
|
--el: light-dark(oklch(99.4% .002 75), oklch(21% .006 75)); /* paper */
|
|
66
69
|
--face:light-dark(oklch(97.2% .005 75), oklch(25% .007 75)); /* chrome */
|
|
67
70
|
--fg: light-dark(oklch(22% .008 75), oklch(95% .004 75));
|
|
68
|
-
|
|
71
|
+
/* lifted from 50%/70%: the weakest pairing (muted on the desktop ground in
|
|
72
|
+
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));
|
|
69
77
|
--bd: light-dark(oklch(22% .008 75 / .13), oklch(95% .004 75 / .13));
|
|
70
78
|
--tb: light-dark(oklch(97.2% .005 75 / .85), oklch(25% .007 75 / .85));
|
|
71
|
-
|
|
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 */
|
|
72
83
|
--on-ac: light-dark(#fff, #06121f); /* text on the accent, both themes */
|
|
73
84
|
|
|
74
85
|
/* depth: hairline + soft shadow, macOS weight.
|
|
75
|
-
--sh is an ordinary colour — override it with any notation you like
|
|
76
|
-
the
|
|
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. */
|
|
77
90
|
--sh: oklch(23.1% .007 286);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
/* The prefixed names are the API; the bare ones are read as a fallback so a
|
|
92
|
+
sheet that set --a1 before 1.1.0 keeps working. --a1 itself stays
|
|
93
|
+
unregistered, which is the point of the rename — an @property
|
|
94
|
+
registration is global and cannot be scoped, so a two-character name was
|
|
95
|
+
silently retyping a consumer's own property. */
|
|
96
|
+
--fertig-a1: var(--a1, .08); --fertig-a2: var(--a2, .06);
|
|
97
|
+
--sh1: color-mix(in srgb, var(--sh) calc(var(--fertig-a1) * 100%), transparent);
|
|
98
|
+
--sh2: color-mix(in srgb, var(--sh) calc(var(--fertig-a2) * 100%), transparent);
|
|
81
99
|
--up: 0 1px 1px var(--sh1);
|
|
82
100
|
--up2: 0 1px 2px var(--sh1), 0 6px 16px -6px var(--sh2);
|
|
83
|
-
--dn: inset 0 1px 2px
|
|
101
|
+
--dn: inset 0 1px 2px color-mix(in srgb, var(--sh) 8%, transparent);
|
|
84
102
|
--caps: .74em/1.4 var(--f);
|
|
103
|
+
|
|
104
|
+
/* motion. One decelerating curve for things arriving, one accelerating for
|
|
105
|
+
things leaving, and a symmetric one for state that toggles in place. */
|
|
106
|
+
--ease-out: cubic-bezier(.22, 1, .36, 1);
|
|
107
|
+
--ease-in: cubic-bezier(.55, 0, 1, .45);
|
|
108
|
+
--ease: cubic-bezier(.4, 0, .2, 1);
|
|
109
|
+
--dur-1: .12s; /* a control reacting under the pointer */
|
|
110
|
+
--dur-2: .2s; /* something appearing or leaving */
|
|
85
111
|
}
|
|
86
112
|
@media (prefers-color-scheme: dark) {
|
|
87
|
-
:root:not([data-theme=light]) { --a1: .5; --a2: .45 }
|
|
113
|
+
:root:not([data-theme=light]) { --fertig-a1: var(--a1, .5); --fertig-a2: var(--a2, .45) }
|
|
114
|
+
}
|
|
115
|
+
/* --on-ac has to survive any --ac override, or a custom accent ships an
|
|
116
|
+
unreadable label. contrast-color() does that properly but has almost no
|
|
117
|
+
support; relative colour reaches the same black-or-white decision from the
|
|
118
|
+
accent's own lightness, and has been cross-engine since 2024. Both are
|
|
119
|
+
additive — the measured light-dark() pair above stands if neither exists. */
|
|
120
|
+
@supports (color: oklch(from red l c h)) {
|
|
121
|
+
:root { --on-ac: oklch(from var(--ac) clamp(0, (l - .62) * -100, 1) 0 h) }
|
|
88
122
|
}
|
|
89
123
|
@supports (color: contrast-color(red)) {
|
|
90
|
-
:root { --on-ac: contrast-color(var(--ac)) }
|
|
124
|
+
:root { --on-ac: contrast-color(var(--ac)) }
|
|
91
125
|
}
|
|
92
|
-
[data-theme=light] { color-scheme: light; --a1: .08; --a2: .06 }
|
|
93
|
-
[data-theme=dark] { color-scheme: dark; --a1: .5; --a2: .45 }
|
|
126
|
+
[data-theme=light] { color-scheme: light; --fertig-a1: var(--a1, .08); --fertig-a2: var(--a2, .06) }
|
|
127
|
+
[data-theme=dark] { color-scheme: dark; --fertig-a1: var(--a1, .5); --fertig-a2: var(--a2, .45) }
|
|
128
|
+
|
|
129
|
+
/* Named accents. The ramps already exist, so this is a mapping table: put
|
|
130
|
+
data-accent on any element and everything inside it — links, focus rings,
|
|
131
|
+
filled buttons, the caret, the selection — follows. Scoped rather than
|
|
132
|
+
global, so one section of a page can differ from the rest.
|
|
133
|
+
Each pair is measured: the light tone clears AA as link text on paper, the
|
|
134
|
+
dark tone clears it on the dark ground. */
|
|
135
|
+
[data-accent=indigo] { --ac: light-dark(oklch(50% .19 275), oklch(78% .11 275)) }
|
|
136
|
+
[data-accent=sky] { --ac: light-dark(var(--sky-700), oklch(78% .085 245)) }
|
|
137
|
+
[data-accent=sage] { --ac: light-dark(var(--sage-700), oklch(80% .075 155)) }
|
|
138
|
+
[data-accent=clay] { --ac: light-dark(var(--clay-700), oklch(80% .085 45)) }
|
|
139
|
+
[data-accent=plum] { --ac: light-dark(var(--plum-700), oklch(80% .065 320)) }
|
|
140
|
+
[data-accent=gold] { --ac: light-dark(oklch(55% .085 80), oklch(84% .09 85)) }
|
|
141
|
+
[data-accent=slate] { --ac: light-dark(oklch(35% .01 250), oklch(86% .006 250)) }
|
|
94
142
|
|
|
95
143
|
/* ---- base --------------------------------------------------- */
|
|
96
144
|
*, ::before, ::after { box-sizing: border-box }
|
|
@@ -105,30 +153,50 @@ body {
|
|
|
105
153
|
font-variant-numeric: slashed-zero tabular-nums;
|
|
106
154
|
}
|
|
107
155
|
::selection { background: color-mix(in srgb, var(--ac) 28%, transparent) }
|
|
108
|
-
|
|
156
|
+
/* The macOS ring. No border-radius here: an outline already follows the
|
|
157
|
+
element's own corners, and setting one would reshape whatever takes focus —
|
|
158
|
+
a focused dialog would snap from --rw to --r for as long as it is focused. */
|
|
159
|
+
:focus-visible {
|
|
109
160
|
outline: 3px solid color-mix(in srgb, var(--ac) 45%, transparent);
|
|
110
|
-
outline-offset: 1px;
|
|
161
|
+
outline-offset: 1px;
|
|
111
162
|
}
|
|
112
|
-
img,
|
|
163
|
+
img, video, iframe { display: block; max-width: 100%; height: auto }
|
|
164
|
+
/* svg is left inline: it is as often an icon inside a sentence or a button as
|
|
165
|
+
it is a standalone figure, and display:block drops the icon onto its own row */
|
|
166
|
+
svg { max-width: 100%; vertical-align: middle }
|
|
113
167
|
img, video { border-radius: var(--r); box-shadow: var(--up2) }
|
|
114
168
|
|
|
115
169
|
/* ---- layout: a window on a desktop --------------------------- */
|
|
116
|
-
body
|
|
117
|
-
|
|
170
|
+
/* The shell is body's own children — or the children of the single wrapper a
|
|
171
|
+
framework insists on (#root, #app, #__next, or your own [data-fertig]).
|
|
172
|
+
Without this a React page silently loses the measure, the paper and the
|
|
173
|
+
gutters, and nothing on screen says why. The wrapper list sits inside
|
|
174
|
+
:where() so every selector keeps the specificity it had as `body > *`. */
|
|
175
|
+
body > *,
|
|
176
|
+
body > :where(#root, #app, #__next, [data-fertig]) > * {
|
|
177
|
+
max-width: var(--w); margin-inline: auto; padding-inline: 1.9rem;
|
|
178
|
+
}
|
|
179
|
+
/* the wrapper is not itself a shell element; it just gets out of the way */
|
|
180
|
+
body > :where(#root, #app, #__next, [data-fertig]) { max-width: none; padding-inline: 0 }
|
|
181
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > :is(header, main, footer) {
|
|
118
182
|
background: var(--el); border-inline: 1px solid var(--bd);
|
|
119
183
|
}
|
|
120
|
-
body > header { padding-block: 2.4rem .8rem }
|
|
121
|
-
body > main { padding-bottom: 2.8rem }
|
|
122
|
-
body > footer {
|
|
184
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > header { padding-block: 2.4rem .8rem }
|
|
185
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > main { padding-bottom: 2.8rem }
|
|
186
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > footer {
|
|
123
187
|
padding-block: 1.2rem 2rem; color: var(--mut); font-size: .85em;
|
|
124
188
|
border-top: 1px solid var(--bd); border-bottom: 1px solid var(--bd);
|
|
125
189
|
border-radius: 0 0 var(--rw) var(--rw); margin-bottom: 2.5rem;
|
|
126
|
-
box-shadow: 0 18px 30px -22px
|
|
190
|
+
box-shadow: 0 18px 30px -22px color-mix(in srgb, var(--sh) 50%, transparent);
|
|
127
191
|
}
|
|
128
192
|
/* nav = unified toolbar: full-bleed, vibrant, contents on the measure */
|
|
129
|
-
body > nav {
|
|
193
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav {
|
|
130
194
|
position: sticky; top: 0; z-index: 9; max-width: none;
|
|
131
|
-
|
|
195
|
+
/* the toolbar is full-bleed but its contents line up with the page column.
|
|
196
|
+
--nw is that column: it follows --w, so overriding the measure moves the
|
|
197
|
+
toolbar with it, and .wide re-points it at the wide column instead. */
|
|
198
|
+
--nw: var(--w);
|
|
199
|
+
padding-inline: max(1.9rem, calc(50% - var(--nw) / 2 + 1.9rem),
|
|
132
200
|
env(safe-area-inset-left), env(safe-area-inset-right));
|
|
133
201
|
display: flex; flex-wrap: wrap; gap: 1.1rem; align-items: center;
|
|
134
202
|
padding-block: calc(.6rem + env(safe-area-inset-top, 0px)) .6rem;
|
|
@@ -136,10 +204,10 @@ body > nav {
|
|
|
136
204
|
backdrop-filter: saturate(180%) blur(20px);
|
|
137
205
|
border-bottom: 1px solid var(--bd); font-size: .92em;
|
|
138
206
|
}
|
|
139
|
-
body > nav a { color: var(--fg); text-decoration: none; opacity: .75 }
|
|
140
|
-
body > nav a:hover { opacity: 1 }
|
|
141
|
-
body > nav :is(strong, b) a { opacity: 1 } /* the wordmark is not a link among links */
|
|
142
|
-
body > nav ul {
|
|
207
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav a { color: var(--fg); text-decoration: none; opacity: .75 }
|
|
208
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav a:hover { opacity: 1 }
|
|
209
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav :is(strong, b) a { opacity: 1 } /* the wordmark is not a link among links */
|
|
210
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav ul {
|
|
143
211
|
display: flex; flex-wrap: wrap; gap: 1.1rem; list-style: none; padding: 0; margin: 0;
|
|
144
212
|
flex: 1; /* takes the slack, so a trailing control sits at the end */
|
|
145
213
|
}
|
|
@@ -172,7 +240,9 @@ blockquote {
|
|
|
172
240
|
}
|
|
173
241
|
ul, ol { padding-inline-start: 1.5rem }
|
|
174
242
|
li::marker { color: var(--mut) }
|
|
175
|
-
|
|
243
|
+
/* minmax(0, 1fr), not 1fr: a bare 1fr track has min-width:auto, so a long
|
|
244
|
+
value cannot shrink and pushes the page sideways on a narrow screen. */
|
|
245
|
+
dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .35rem 1rem }
|
|
176
246
|
dt { color: var(--mut) }
|
|
177
247
|
dd { margin: 0 }
|
|
178
248
|
figcaption { font-size: .85em; color: var(--mut); text-align: center }
|
|
@@ -181,11 +251,11 @@ figcaption { font-size: .85em; color: var(--mut); text-align: center }
|
|
|
181
251
|
code, kbd, samp, pre { font-family: var(--fm); font-size: .9em }
|
|
182
252
|
:not(pre) > code, samp {
|
|
183
253
|
background: var(--face); border: 1px solid var(--bd);
|
|
184
|
-
border-radius: var(--
|
|
254
|
+
border-radius: var(--rs); padding: .08em .35em;
|
|
185
255
|
}
|
|
186
256
|
kbd {
|
|
187
257
|
background: var(--face); border: 1px solid var(--bd); border-bottom-width: 2px;
|
|
188
|
-
border-radius: var(--
|
|
258
|
+
border-radius: var(--rs); padding: .08em .4em; box-shadow: var(--up);
|
|
189
259
|
}
|
|
190
260
|
pre {
|
|
191
261
|
background: var(--face); border: 1px solid var(--bd); border-radius: var(--r);
|
|
@@ -268,9 +338,9 @@ textarea { width: 100%; min-height: 5rem; resize: vertical }
|
|
|
268
338
|
@supports (field-sizing: content) {
|
|
269
339
|
textarea { field-sizing: content; max-height: 24rem }
|
|
270
340
|
}
|
|
271
|
-
input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file],[type=submit],[type=button],[type=reset]),
|
|
341
|
+
input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file],[type=image],[type=submit],[type=button],[type=reset]),
|
|
272
342
|
select, textarea {
|
|
273
|
-
display: block; width: 100%; padding: .
|
|
343
|
+
display: block; width: 100%; padding: .52em .7em; background: var(--el);
|
|
274
344
|
border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--dn);
|
|
275
345
|
}
|
|
276
346
|
select {
|
|
@@ -294,14 +364,14 @@ select[multiple] { background-image: none; padding-inline-end: .6em }
|
|
|
294
364
|
content: ""; inline-size: .45em; block-size: .45em; flex: none;
|
|
295
365
|
border: solid currentColor; border-width: 0 1px 1px 0;
|
|
296
366
|
rotate: 45deg; translate: 0 -.15em; opacity: .7;
|
|
297
|
-
transition: rotate
|
|
367
|
+
transition: rotate var(--dur-1) var(--ease), translate var(--dur-1) var(--ease);
|
|
298
368
|
}
|
|
299
369
|
select:open::picker-icon { rotate: 225deg; translate: 0 .1em }
|
|
300
370
|
::picker(select) {
|
|
301
371
|
min-width: anchor-size(width); /* never narrower than the field */
|
|
302
372
|
border: 1px solid var(--bd); border-radius: var(--r); padding: .3rem;
|
|
303
373
|
background: var(--el); margin-block-start: .35rem;
|
|
304
|
-
box-shadow: var(--up2), 0 20px 40px -14px
|
|
374
|
+
box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
|
|
305
375
|
}
|
|
306
376
|
option {
|
|
307
377
|
display: flex; align-items: center; gap: .5em; padding: .4em .6em;
|
|
@@ -352,7 +422,7 @@ legend + * { clear: left }
|
|
|
352
422
|
button, [type=submit], [type=button], [type=reset],
|
|
353
423
|
:where(a.primary, a[role=button]), ::file-selector-button {
|
|
354
424
|
display: inline-block; text-decoration: none; text-align: center;
|
|
355
|
-
padding: .
|
|
425
|
+
padding: .48em 1.1em; background: var(--el); color: var(--fg); cursor: pointer;
|
|
356
426
|
border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--up);
|
|
357
427
|
font: inherit; min-width: 5rem;
|
|
358
428
|
}
|
|
@@ -361,22 +431,34 @@ button, [type=submit], [type=button], [type=reset],
|
|
|
361
431
|
box-shadow: var(--dn); translate: 0 .5px;
|
|
362
432
|
}
|
|
363
433
|
[type=submit], .primary { background: var(--ac); border-color: transparent; color: var(--on-ac) }
|
|
434
|
+
/* The filled button lightens on hover. color-mix does that in oklab, which is
|
|
435
|
+
close enough and is Baseline; relative colour does it by adding to L
|
|
436
|
+
directly, which holds the hue exactly when --ac is a wide-gamut override.
|
|
437
|
+
Both derive from --ac, so a custom accent never needs a second declaration. */
|
|
364
438
|
:is([type=submit], .primary, a.primary):hover:not(:disabled) {
|
|
365
|
-
background:
|
|
439
|
+
background: color-mix(in oklab, var(--ac), #fff 9%);
|
|
440
|
+
}
|
|
441
|
+
@supports (color: oklch(from red l c h)) {
|
|
442
|
+
:is([type=submit], .primary, a.primary):hover:not(:disabled) {
|
|
443
|
+
background: oklch(from var(--ac) calc(l + .05) c h);
|
|
444
|
+
}
|
|
366
445
|
}
|
|
367
446
|
|
|
368
447
|
/* button detail: sizes, variants, toggles, icon and block forms */
|
|
369
|
-
|
|
370
|
-
|
|
448
|
+
/* Scoped to controls. data-size, data-block and data-variant are names other
|
|
449
|
+
component libraries and framework wrappers use too, and a bare attribute
|
|
450
|
+
selector would restyle a stranger's <span data-size="sm"> on contact. */
|
|
451
|
+
:is(button, a, input, select, label)[data-size=sm] { padding: .2em .7em; font-size: .85em; min-width: 0 }
|
|
452
|
+
:is(button, a, input, select, label)[data-size=lg] { padding: .55em 1.5em; font-size: 1.05em }
|
|
371
453
|
button[data-icon] { min-width: 0; padding: .38em .62em; line-height: 1 }
|
|
372
|
-
[data-block] { display: block; width: 100%; min-width: 0 }
|
|
373
|
-
[data-variant=ghost] { background: none; border-color: transparent; box-shadow: none }
|
|
374
|
-
[data-variant=ghost]:hover:not(:disabled) { background: var(--face); border-color: var(--bd) }
|
|
375
|
-
[data-variant=link] {
|
|
454
|
+
:is(button, a, input, select, label)[data-block] { display: block; width: 100%; min-width: 0 }
|
|
455
|
+
:is(button, a, input)[data-variant=ghost] { background: none; border-color: transparent; box-shadow: none }
|
|
456
|
+
:is(button, a, input)[data-variant=ghost]:hover:not(:disabled) { background: var(--face); border-color: var(--bd) }
|
|
457
|
+
:is(button, a, input)[data-variant=link] {
|
|
376
458
|
background: none; border: 0; box-shadow: none; min-width: 0; padding-inline: .2em;
|
|
377
459
|
color: var(--ac); text-decoration: underline; text-underline-offset: 3px;
|
|
378
460
|
}
|
|
379
|
-
[data-variant=link]:hover:not(:disabled) { background: none; text-decoration-thickness: 2px }
|
|
461
|
+
:is(button, a, input)[data-variant=link]:hover:not(:disabled) { background: none; text-decoration-thickness: 2px }
|
|
380
462
|
button[aria-pressed=true] { background: var(--face); color: var(--ac); font-weight: 700 }
|
|
381
463
|
button[aria-pressed=true]:hover:not(:disabled) {
|
|
382
464
|
background: color-mix(in srgb, var(--fg) 7%, var(--face));
|
|
@@ -386,7 +468,13 @@ button[aria-pressed=true]:hover:not(:disabled) {
|
|
|
386
468
|
:is([type=submit], .primary):active:not(:disabled) { box-shadow: inset 0 1px 3px #0006 }
|
|
387
469
|
|
|
388
470
|
@supports (corner-shape: squircle) {
|
|
389
|
-
|
|
471
|
+
/* the surfaces this sheet rounds, not `*` — a universal rule would reshape
|
|
472
|
+
your components too, and this one is a taste, not a repair */
|
|
473
|
+
:is(button, [type=submit], [type=button], [type=reset], input, select, textarea,
|
|
474
|
+
fieldset, details, dialog, [popover], pre, kbd, code, samp, img, video,
|
|
475
|
+
menu, aside, [data-avatar], [data-skeleton], .card, .badge) {
|
|
476
|
+
corner-shape: squircle;
|
|
477
|
+
}
|
|
390
478
|
}
|
|
391
479
|
|
|
392
480
|
/* ---- disclosure / misc -------------------------------------- */
|
|
@@ -401,11 +489,11 @@ details[open] > summary { margin-bottom: .6rem }
|
|
|
401
489
|
dialog {
|
|
402
490
|
background: var(--el); color: var(--fg); border: 1px solid var(--bd);
|
|
403
491
|
border-radius: var(--rw); padding: 1.4rem; max-width: min(30rem, 92dvw);
|
|
404
|
-
box-shadow: 0 32px 64px -16px
|
|
492
|
+
box-shadow: 0 32px 64px -16px color-mix(in srgb, var(--sh) 45%, transparent);
|
|
405
493
|
}
|
|
406
494
|
dialog > :last-child:not(footer) { margin-bottom: 0 }
|
|
407
495
|
dialog, [popover] { overscroll-behavior: contain }
|
|
408
|
-
dialog::backdrop { background:
|
|
496
|
+
dialog::backdrop { background: color-mix(in srgb, var(--sh) 35%, transparent); backdrop-filter: blur(3px) }
|
|
409
497
|
|
|
410
498
|
/* dialog anatomy: an optional header and footer, pinned while the body scrolls.
|
|
411
499
|
Sticky rather than a grid, because a classless sheet has no wrapper around
|
|
@@ -434,7 +522,8 @@ dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto;
|
|
|
434
522
|
/* sheets come up, they do not appear */
|
|
435
523
|
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
436
524
|
dialog, dialog::backdrop {
|
|
437
|
-
transition: opacity
|
|
525
|
+
transition: opacity var(--dur-2) var(--ease-out), translate var(--dur-2) var(--ease-out),
|
|
526
|
+
display var(--dur-2) allow-discrete, overlay var(--dur-2) allow-discrete;
|
|
438
527
|
}
|
|
439
528
|
dialog { opacity: 0; translate: 0 10px }
|
|
440
529
|
dialog[open] { opacity: 1; translate: 0 }
|
|
@@ -444,17 +533,19 @@ dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto;
|
|
|
444
533
|
@starting-style { dialog[open]::backdrop { opacity: 0 } }
|
|
445
534
|
[popover] {
|
|
446
535
|
opacity: 0; translate: 0 -4px;
|
|
447
|
-
transition: opacity .13s, translate .13s
|
|
536
|
+
transition: opacity .13s var(--ease-out), translate .13s var(--ease-out),
|
|
537
|
+
display .13s allow-discrete, overlay .13s allow-discrete;
|
|
448
538
|
}
|
|
449
539
|
[popover]:popover-open { opacity: 1; translate: 0 }
|
|
450
540
|
@starting-style { [popover]:popover-open { opacity: 0; translate: 0 -4px } }
|
|
451
541
|
}
|
|
452
|
-
search { display: block }
|
|
542
|
+
search { display: block; margin-block: 1.1rem } /* a landmark needs its own rhythm */
|
|
453
543
|
::target-text { background: color-mix(in srgb, var(--ac) 28%, transparent) }
|
|
454
544
|
aside {
|
|
455
545
|
border: 1px solid var(--bd); border-inline-start: 3px solid var(--ac);
|
|
456
546
|
border-radius: var(--r); border-start-start-radius: 0; border-end-start-radius: 0;
|
|
457
547
|
background: var(--el); padding: .7rem 1.1rem;
|
|
548
|
+
margin-block: 1.1rem; /* or the next block sits flush against it */
|
|
458
549
|
}
|
|
459
550
|
progress { width: 100%; height: .5rem; border: 0; border-radius: calc(infinity * 1px); background: var(--face) }
|
|
460
551
|
progress::-webkit-progress-bar { background: var(--face); border-radius: calc(infinity * 1px) }
|
|
@@ -464,10 +555,13 @@ progress::-moz-progress-bar { background: var(--ac); border-radius: calc(infinit
|
|
|
464
555
|
/* details can only animate to auto height once keywords interpolate */
|
|
465
556
|
@supports selector(::details-content) {
|
|
466
557
|
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
467
|
-
|
|
558
|
+
/* on details, not :root: allow-keywords is inherited, and setting it
|
|
559
|
+
document-wide would quietly change how your own height:auto
|
|
560
|
+
transitions behave */
|
|
561
|
+
details { interpolate-size: allow-keywords }
|
|
468
562
|
details::details-content {
|
|
469
563
|
block-size: 0; overflow: clip;
|
|
470
|
-
transition: block-size .28s ease, content-visibility .28s allow-discrete;
|
|
564
|
+
transition: block-size .28s var(--ease), content-visibility .28s allow-discrete;
|
|
471
565
|
}
|
|
472
566
|
details[open]::details-content { block-size: auto }
|
|
473
567
|
}
|
|
@@ -477,7 +571,11 @@ progress::-moz-progress-bar { background: var(--ac); border-radius: calc(infinit
|
|
|
477
571
|
/* ---- components, addressed by ARIA rather than by class ----- */
|
|
478
572
|
|
|
479
573
|
/* segmented control: <div role=group> of buttons */
|
|
480
|
-
|
|
574
|
+
/* max-inline-size + scroll, or a group of four or more buttons pushes the
|
|
575
|
+
whole page sideways on a narrow screen. It scrolls rather than wraps:
|
|
576
|
+
wrapping would break the joined-pill run of radii into separate rows. */
|
|
577
|
+
[role=group] { display: inline-flex; vertical-align: middle;
|
|
578
|
+
max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain }
|
|
481
579
|
[role=group] > * { border-radius: 0; margin-inline-start: -1px; box-shadow: none; min-width: 0 }
|
|
482
580
|
[role=group] > :first-child {
|
|
483
581
|
margin-inline-start: 0;
|
|
@@ -507,7 +605,7 @@ input[type=checkbox][role=switch] {
|
|
|
507
605
|
input[switch]::after, input[role=switch]::after {
|
|
508
606
|
content: ""; position: absolute; inset-block-start: .14em; inset-inline-start: .14em;
|
|
509
607
|
width: 1.02em; height: 1.02em; border-radius: 50%; background: #fff; box-shadow: var(--up);
|
|
510
|
-
transition: translate .16s ease;
|
|
608
|
+
transition: translate .16s var(--ease-out);
|
|
511
609
|
}
|
|
512
610
|
input[switch]:checked, input[role=switch]:checked { background: var(--ac) }
|
|
513
611
|
/* translate is physical, so the knob is told which way to travel */
|
|
@@ -519,7 +617,7 @@ input[switch]:checked::after, input[role=switch]:checked::after { translate: .9e
|
|
|
519
617
|
background: var(--el); color: var(--fg); border: 1px solid var(--bd);
|
|
520
618
|
border-radius: var(--r); padding: .3rem; min-width: 12rem;
|
|
521
619
|
max-block-size: 60dvh; overflow: auto; font-size: .95em;
|
|
522
|
-
box-shadow: var(--up2), 0 20px 40px -14px
|
|
620
|
+
box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
|
|
523
621
|
}
|
|
524
622
|
/* anchor to the invoking button; without support it centres, which is fine.
|
|
525
623
|
Deliberately NOT scoped to :popover-open — the close is transitioned, so a
|
|
@@ -553,7 +651,7 @@ input[switch]:checked::after, input[role=switch]:checked::after { translate: .9e
|
|
|
553
651
|
|
|
554
652
|
/* breadcrumbs: <nav aria-label="Breadcrumb"><ol> */
|
|
555
653
|
nav[aria-label] ol { display: flex; flex-wrap: wrap; gap: .5em; list-style: none; padding: 0; font-size: .9em }
|
|
556
|
-
nav[aria-label] li + li::before { content: "/"; margin-inline-end: .5em; color: var(--mut) }
|
|
654
|
+
nav[aria-label] ol li + li::before { content: "/"; margin-inline-end: .5em; color: var(--mut) }
|
|
557
655
|
|
|
558
656
|
/* busy: [aria-busy=true] */
|
|
559
657
|
[aria-busy=true] { cursor: progress }
|
|
@@ -567,14 +665,21 @@ nav[aria-label] li + li::before { content: "/"; margin-inline-end: .5em; color:
|
|
|
567
665
|
}
|
|
568
666
|
@keyframes fertig-spin { to { rotate: 360deg } }
|
|
569
667
|
|
|
570
|
-
/* tooltip: [data-tooltip]
|
|
668
|
+
/* tooltip: [data-tooltip]. Decorative only, and deliberately so: generated
|
|
669
|
+
content is not reliably exposed to assistive tech and there is no way to
|
|
670
|
+
dismiss it without script (WCAG 1.4.13), so put the same words in aria-label
|
|
671
|
+
or aria-describedby and never say anything here that is said nowhere else.
|
|
672
|
+
A scroll container clips it — [role=group] is one. */
|
|
571
673
|
[data-tooltip] { position: relative }
|
|
572
|
-
[data-tooltip]:is(:hover, :focus-visible)::after {
|
|
573
|
-
content: attr(data-tooltip); position: absolute;
|
|
674
|
+
[data-tooltip]:is(:hover, :focus-visible, :active)::after {
|
|
675
|
+
content: attr(data-tooltip); position: absolute;
|
|
676
|
+
inset-block-end: 100%; inset-inline-start: 50%;
|
|
574
677
|
translate: -50% -.45em; z-index: 10; white-space: nowrap; pointer-events: none;
|
|
575
678
|
background: light-dark(#1d1d1f, #3a3a40); color: #fff; font-size: .8em;
|
|
576
679
|
padding: .25em .55em; border-radius: var(--r); box-shadow: var(--up2);
|
|
577
680
|
}
|
|
681
|
+
/* translate is physical; the offset has to be told which way to go */
|
|
682
|
+
[data-tooltip]:dir(rtl)::after { translate: 50% -.45em }
|
|
578
683
|
|
|
579
684
|
/* tones: retint any component by redefining its accent */
|
|
580
685
|
[data-tone=ok] { --ac: light-dark(var(--sage-700), oklch(76% .085 155)) }
|
|
@@ -646,6 +751,27 @@ dialog[data-side=bottom] {
|
|
|
646
751
|
position-area: none; inset: auto 1rem 1rem auto; min-width: 16rem;
|
|
647
752
|
}
|
|
648
753
|
}
|
|
754
|
+
/* Two open toasts land on the same corner coordinates and sit on top of each
|
|
755
|
+
other, and an open popover is in the top layer so no ordinary wrapper can
|
|
756
|
+
stack them. Make the region the popover instead and the toasts plain
|
|
757
|
+
children of it: <div popover=manual data-toast-region> with a
|
|
758
|
+
<div data-toast> per message. */
|
|
759
|
+
[popover][data-toast-region] {
|
|
760
|
+
position: fixed; inset: auto 1rem 1rem auto; margin: 0;
|
|
761
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: .6rem;
|
|
762
|
+
background: none; border: 0; box-shadow: none; padding: 0;
|
|
763
|
+
max-block-size: none; overflow: visible; pointer-events: none;
|
|
764
|
+
}
|
|
765
|
+
[popover][data-toast-region] > [data-toast] {
|
|
766
|
+
pointer-events: auto; position: static; inset: auto;
|
|
767
|
+
min-width: 16rem; max-width: min(24rem, 92dvw); padding: .8rem 1rem;
|
|
768
|
+
background: var(--el); color: var(--fg); border: 1px solid var(--bd);
|
|
769
|
+
border-radius: var(--r); font-size: .95em;
|
|
770
|
+
box-shadow: var(--up2), 0 20px 40px -14px color-mix(in srgb, var(--sh) 40%, transparent);
|
|
771
|
+
}
|
|
772
|
+
@supports (position-area: block-end span-inline-end) {
|
|
773
|
+
[popover][data-toast-region] { position-area: none; inset: auto 1rem 1rem auto }
|
|
774
|
+
}
|
|
649
775
|
|
|
650
776
|
/* list group: a plain <menu> outside a popover is a bordered list */
|
|
651
777
|
menu {
|
|
@@ -658,6 +784,26 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
658
784
|
[popover] menu > li { padding: 0 }
|
|
659
785
|
[popover] menu > li + li { border-top: 0 }
|
|
660
786
|
|
|
787
|
+
/* navigation lists: a <menu> inside a <nav> is navigation, not a list group.
|
|
788
|
+
No rules between items, no bullets, and the link fills the row so the whole
|
|
789
|
+
band is the hit target. This is what [data-layout=sidebar] below is for —
|
|
790
|
+
the layout shipped without it, which left the obvious markup looking boxed. */
|
|
791
|
+
nav menu {
|
|
792
|
+
border: 0; background: none; padding: 0; margin-block: 0 1.2rem;
|
|
793
|
+
}
|
|
794
|
+
nav menu > li { padding: 0 }
|
|
795
|
+
nav menu > li + li { border-top: 0 }
|
|
796
|
+
nav menu :is(a, button) {
|
|
797
|
+
display: block; inline-size: 100%; text-align: start;
|
|
798
|
+
padding: .38rem .6rem; border: 0; border-radius: var(--r);
|
|
799
|
+
background: none; box-shadow: none; font: inherit;
|
|
800
|
+
color: var(--fg); text-decoration: none; opacity: .72;
|
|
801
|
+
}
|
|
802
|
+
nav menu :is(a, button):hover { background: var(--face); opacity: 1 }
|
|
803
|
+
nav menu :is(a, button)[aria-current] {
|
|
804
|
+
background: var(--face); color: var(--ac); font-weight: 600; opacity: 1;
|
|
805
|
+
}
|
|
806
|
+
|
|
661
807
|
/* app layout: a sticky sidebar beside the content */
|
|
662
808
|
[data-layout=sidebar] { display: grid; gap: 1.5rem; align-items: start }
|
|
663
809
|
@media (width >= 56rem) {
|
|
@@ -679,6 +825,10 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
679
825
|
.muted { color: var(--mut) }
|
|
680
826
|
.center { text-align: center }
|
|
681
827
|
.wide { max-width: 66rem }
|
|
828
|
+
/* on the toolbar .wide cannot mean "be 66rem wide" — the bar stays full-bleed
|
|
829
|
+
and its contents move out to the wide column, so an app screen's wordmark
|
|
830
|
+
lines up with the app's own content rather than floating mid-screen. */
|
|
831
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav.wide { max-width: none; --nw: 66rem }
|
|
682
832
|
.row { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center }
|
|
683
833
|
/* Form controls are full-width blocks by default, which is right in a form and
|
|
684
834
|
wrong in a toolbar. Inside a .row they size to the row instead. */
|
|
@@ -689,9 +839,14 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
689
839
|
.row :is(progress, meter) { width: auto; flex: 1 1 6rem }
|
|
690
840
|
.row > label { flex: none; margin-top: 0 }
|
|
691
841
|
.grid { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)) }
|
|
842
|
+
/* a .grid is a block-level container that nothing else gives vertical rhythm
|
|
843
|
+
to, so whatever follows one collides with it. Spacing the next sibling
|
|
844
|
+
rather than the grid itself: a margin on .grid would collapse out through
|
|
845
|
+
the top of a padding-less container and open a seam above it. */
|
|
846
|
+
.grid + * { margin-top: 1.1rem }
|
|
692
847
|
.card {
|
|
693
848
|
container-type: inline-size; /* query it: @container (min-width: …) */
|
|
694
|
-
border: 1px solid var(--bd); border-radius: var(--r); padding: .
|
|
849
|
+
border: 1px solid var(--bd); border-radius: var(--r); padding: 1.1rem;
|
|
695
850
|
background: var(--el); box-shadow: var(--up2);
|
|
696
851
|
display: flex; flex-direction: column; /* so footers line up across a row */
|
|
697
852
|
gap: .85rem; /* spacing is the container's job */
|
|
@@ -758,27 +913,33 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
758
913
|
}
|
|
759
914
|
|
|
760
915
|
/* card anatomy: an optional header, footer, and full-bleed media */
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
916
|
+
/* No fill on the header and footer. A tinted strip across the top of a card is
|
|
917
|
+
the old panel-heading pattern and it is what dates a sheet on sight; a
|
|
918
|
+
hairline does the same separating job and lets the card read as one surface. */
|
|
919
|
+
.card > :is(header, footer) { margin-inline: -1.1rem; padding-inline: 1.1rem }
|
|
764
920
|
.card > header {
|
|
765
|
-
margin-top: -.
|
|
766
|
-
border-bottom: 1px solid var(--bd);
|
|
921
|
+
margin-top: -.25rem; padding-bottom: .7rem; font-weight: 600;
|
|
922
|
+
border-bottom: 1px solid var(--bd);
|
|
767
923
|
}
|
|
768
924
|
.card > footer {
|
|
769
|
-
margin-top: auto; margin-bottom: -.
|
|
925
|
+
margin-top: auto; margin-bottom: -.25rem; padding-top: .7rem;
|
|
770
926
|
color: var(--mut); font-size: .85em;
|
|
771
|
-
border-top: 1px solid var(--bd);
|
|
927
|
+
border-top: 1px solid var(--bd);
|
|
772
928
|
}
|
|
773
929
|
.card > :is(img, video):first-child {
|
|
774
|
-
align-self: stretch; width: auto; max-width: none; margin:
|
|
930
|
+
align-self: stretch; width: auto; max-width: none; margin: -1.1rem -1.1rem 0;
|
|
775
931
|
aspect-ratio: 16 / 9; object-fit: cover;
|
|
776
932
|
border: 0; border-radius: var(--r) var(--r) 0 0; box-shadow: none;
|
|
777
933
|
}
|
|
778
|
-
/*
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
.
|
|
934
|
+
/* A background layer rather than a border or an inset shadow: a border would
|
|
935
|
+
shift the card's geometry, and an inset shadow does not follow the corner
|
|
936
|
+
radius cleanly at this size — it reads as a detached bar with gaps at the
|
|
937
|
+
top corners. A gradient is clipped by the radius, so the tone hugs the curve. */
|
|
938
|
+
.card[data-tone] {
|
|
939
|
+
background: linear-gradient(var(--ac), var(--ac)) top / 100% 3px no-repeat,
|
|
940
|
+
var(--el);
|
|
941
|
+
}
|
|
942
|
+
|
|
782
943
|
/* a card that is itself a link or button reacts like one */
|
|
783
944
|
:is(a, button).card {
|
|
784
945
|
display: flex; width: 100%; /* keep the column from .card */
|
|
@@ -787,7 +948,7 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
787
948
|
}
|
|
788
949
|
:is(a, button).card:hover:not(:disabled) {
|
|
789
950
|
border-color: color-mix(in srgb, var(--ac) 45%, var(--bd));
|
|
790
|
-
box-shadow: var(--up2), 0 12px 26px -14px
|
|
951
|
+
box-shadow: var(--up2), 0 12px 26px -14px color-mix(in srgb, var(--sh) 40%, transparent);
|
|
791
952
|
translate: 0 -2px;
|
|
792
953
|
}
|
|
793
954
|
:is(a, button).card:active:not(:disabled) { translate: 0 0 }
|
|
@@ -799,7 +960,7 @@ menu > li + li { border-top: 1px solid var(--bd) }
|
|
|
799
960
|
@media (prefers-reduced-motion: no-preference) and (update: fast) { html { scroll-behavior: smooth } }
|
|
800
961
|
:target { scroll-margin-top: 4rem } /* clears the toolbar */
|
|
801
962
|
:is(h1,h2,h3,h4):target { background: color-mix(in srgb, var(--ac) 12%, transparent) }
|
|
802
|
-
a[target=_blank]::after { content: " ↗"; font-size: .85em; opacity: .6 }
|
|
963
|
+
a[target=_blank]:not(:has(> img, > svg))::after { content: " ↗"; font-size: .85em; opacity: .6 }
|
|
803
964
|
a:has(> img), a:has(> code) { text-decoration: none }
|
|
804
965
|
sup, sub { line-height: 0; font-size: .75em } /* stop them stretching lines */
|
|
805
966
|
q { quotes: "“" "”" "‘" "’" }
|
|
@@ -818,13 +979,28 @@ input:user-invalid, textarea:user-invalid { border-color: light-dark(#c0392b, #f
|
|
|
818
979
|
input:user-invalid + small { color: light-dark(#c0392b, #ff6b6b) }
|
|
819
980
|
summary:hover { color: var(--ac) }
|
|
820
981
|
/* thin scrollbars, macOS weight */
|
|
821
|
-
|
|
982
|
+
/* scrollbar-width is Safari 18.2, scrollbar-color Safari 26.2 — both younger
|
|
983
|
+
than the floor, and both left ungated on purpose: there is no fallback to
|
|
984
|
+
write. An engine without them paints its own scrollbar, which is exactly
|
|
985
|
+
what a scrollbar should look like there anyway. Same reasoning for
|
|
986
|
+
accent-color at the top of the sheet. */
|
|
987
|
+
/* Not `*`: the page's own scrollbar keeps the width the OS gave it. Thinning
|
|
988
|
+
every scroller on the page narrows hit targets the sheet never made, which
|
|
989
|
+
is a target-size problem for anyone using a pointer imprecisely. */
|
|
990
|
+
:is(pre, table, textarea, dialog, [popover], menu, fieldset,
|
|
991
|
+
[role=group], [data-carousel], [data-layout=sidebar] > *) {
|
|
992
|
+
scrollbar-width: thin;
|
|
993
|
+
scrollbar-color: color-mix(in srgb, var(--fg) 25%, transparent) transparent;
|
|
994
|
+
}
|
|
822
995
|
|
|
823
996
|
@page { margin: 2cm }
|
|
824
997
|
@media print {
|
|
825
998
|
:root { --bg: #fff; --el: #fff; --fg: #000; --bd: #999 }
|
|
826
|
-
body >
|
|
827
|
-
|
|
999
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > nav { display: none }
|
|
1000
|
+
/* the footer stays: the legal line, the contact and the citations live there,
|
|
1001
|
+
and a printout without them is missing the part people keep */
|
|
1002
|
+
:is(body, body > :where(#root, #app, #__next, [data-fertig])) > footer { box-shadow: none; margin-bottom: 0 }
|
|
1003
|
+
body > *, body > :where(#root, #app, #__next, [data-fertig]) > * { border: 0; max-width: none }
|
|
828
1004
|
p, blockquote, li { orphans: 3; widows: 3 } /* no stranded single lines */
|
|
829
1005
|
a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555 }
|
|
830
1006
|
pre, table, figure, details, tr, img { break-inside: avoid }
|
|
@@ -839,6 +1015,9 @@ summary:hover { color: var(--ac) }
|
|
|
839
1015
|
min-height: 44px;
|
|
840
1016
|
}
|
|
841
1017
|
input[type=checkbox], input[type=radio] { width: 1.4em; height: 1.4em }
|
|
1018
|
+
/* 44px is the AAA target (2.5.5). The small variant opts down to the 24px
|
|
1019
|
+
AA floor (2.5.8) rather than stopping being small on a phone. */
|
|
1020
|
+
:is(button, a, input, select, label)[data-size=sm] { min-height: 32px }
|
|
842
1021
|
}
|
|
843
1022
|
|
|
844
1023
|
/* the OS is already inverting; stop images being inverted twice */
|
|
@@ -871,8 +1050,45 @@ summary:hover { color: var(--ac) }
|
|
|
871
1050
|
|
|
872
1051
|
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
873
1052
|
a, button, input, select, textarea, summary, tr,
|
|
874
|
-
[type=submit], [type=button], [type=reset]
|
|
875
|
-
|
|
1053
|
+
[type=submit], [type=button], [type=reset], .card, .badge,
|
|
1054
|
+
[role=tab], [role=group] > *, [popover] :is(a, button) {
|
|
1055
|
+
transition: box-shadow var(--dur-1) var(--ease),
|
|
1056
|
+
background-color var(--dur-1) var(--ease),
|
|
1057
|
+
border-color var(--dur-1) var(--ease),
|
|
1058
|
+
color var(--dur-1) var(--ease),
|
|
1059
|
+
translate .08s var(--ease-out);
|
|
1060
|
+
}
|
|
1061
|
+
/* the ring must never be animated — it has to be there the instant focus
|
|
1062
|
+
lands, not fade in after it */
|
|
1063
|
+
:focus-visible { transition: none }
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
/* ---- below the floor ---------------------------------------
|
|
1068
|
+
Everything above assumes light-dark(). An engine without it does not
|
|
1069
|
+
degrade to "unstyled but readable" — every colour token becomes invalid at
|
|
1070
|
+
computed-value time, so backgrounds go transparent and colour is inherited,
|
|
1071
|
+
and the page can land unreadable rather than plain. These are the same
|
|
1072
|
+
colours flattened to sRGB. Last in the layer, so it wins on order over the
|
|
1073
|
+
token block at the top. Nothing else needs a twin: an invalid --ac on a
|
|
1074
|
+
[data-tone] or [data-accent] element just inherits the one from :root. */
|
|
1075
|
+
@supports not (color: light-dark(#000, #fff)) {
|
|
1076
|
+
:root {
|
|
1077
|
+
--bg: #ece9e5; --el: #fefdfc; --face: #f8f5f2; --fg: #1d1a17;
|
|
1078
|
+
--mut: #5e5a55; --bd: #1d1a1721; --tb: #f8f5f2d9;
|
|
1079
|
+
--ac: #494fcc; --on-ac: #fff;
|
|
1080
|
+
}
|
|
1081
|
+
@media (prefers-color-scheme: dark) {
|
|
1082
|
+
:root:not([data-theme=light]) {
|
|
1083
|
+
--bg: #0f0d0a; --el: #1a1815; --face: #24211e; --fg: #f0eeec;
|
|
1084
|
+
--mut: #aba7a2; --bd: #f0eeec21; --tb: #24211ed9;
|
|
1085
|
+
--ac: #a3b2fe; --on-ac: #06121f;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
[data-theme=dark] {
|
|
1089
|
+
--bg: #0f0d0a; --el: #1a1815; --face: #24211e; --fg: #f0eeec;
|
|
1090
|
+
--mut: #aba7a2; --bd: #f0eeec21; --tb: #24211ed9;
|
|
1091
|
+
--ac: #a3b2fe; --on-ac: #06121f;
|
|
876
1092
|
}
|
|
877
1093
|
}
|
|
878
1094
|
|
|
@@ -887,6 +1103,9 @@ summary:hover { color: var(--ac) }
|
|
|
887
1103
|
.card, .badge, details, dialog, [popover], fieldset, table {
|
|
888
1104
|
border: 1px solid CanvasText;
|
|
889
1105
|
}
|
|
890
|
-
[role=tab]
|
|
1106
|
+
/* [role=tab] sets border:0, so there is nothing for border-color to land
|
|
1107
|
+
on, and the rule above just removed the box-shadow that marked it */
|
|
1108
|
+
[role=tab][aria-selected=true] { border-bottom: 2px solid Highlight }
|
|
1109
|
+
button[aria-pressed=true] { border-color: Highlight }
|
|
891
1110
|
}
|
|
892
1111
|
}
|