fertig 1.0.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 +99 -0
- package/LICENSE +21 -0
- package/README.md +273 -0
- package/fertig.core.min.css +1 -0
- package/fertig.css +887 -0
- package/fertig.min.css +1 -0
- package/package.json +60 -0
package/fertig.css
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
/*! fertig.css v1.0.0 — a classless CSS stylesheet. MIT */
|
|
2
|
+
|
|
3
|
+
/* Opinions, so you don't have to have them:
|
|
4
|
+
system type · one accent · one measure · a window on a desktop.
|
|
5
|
+
Structure is classic (title bar, sunken fields, raised buttons); the finish
|
|
6
|
+
is macOS (vibrancy, hairlines, soft radii, the blue focus ring).
|
|
7
|
+
Override the tokens marked "yours"; the rest is the point of view. */
|
|
8
|
+
|
|
9
|
+
/* typed so they interpolate; untyped custom properties cannot animate */
|
|
10
|
+
@property --a1 { syntax: "<number>"; inherits: true; initial-value: .08 }
|
|
11
|
+
@property --a2 { syntax: "<number>"; inherits: true; initial-value: .06 }
|
|
12
|
+
|
|
13
|
+
/* Two layers, in this order. Your own unlayered CSS wins over both regardless
|
|
14
|
+
of specificity, so overriding never needs !important — and neither does this
|
|
15
|
+
sheet: fertig-a11y comes last, which is how the forced-colors block below
|
|
16
|
+
beats a .card's box-shadow without one.
|
|
17
|
+
@property stays outside — registrations are global, not layered. */
|
|
18
|
+
@layer fertig, fertig-a11y;
|
|
19
|
+
|
|
20
|
+
@layer fertig {
|
|
21
|
+
|
|
22
|
+
/* ---- tokens ------------------------------------------------- */
|
|
23
|
+
:root {
|
|
24
|
+
color-scheme: light dark; /* powers light-dark() below */
|
|
25
|
+
accent-color: var(--ac);
|
|
26
|
+
caret-color: var(--ac);
|
|
27
|
+
|
|
28
|
+
--f: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
29
|
+
Roboto, "Helvetica Neue", Arial, sans-serif; /* yours */
|
|
30
|
+
--fm: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
|
31
|
+
"Liberation Mono", monospace; /* code */
|
|
32
|
+
--w: 38rem; /* ~70 characters — yours */
|
|
33
|
+
--r: 6px; /* control radius */
|
|
34
|
+
--rw: 10px; /* window radius */
|
|
35
|
+
|
|
36
|
+
/* ---- palette ------------------------------------------------
|
|
37
|
+
Low chroma on purpose. Vivid palettes shout; these are meant to sit
|
|
38
|
+
under text all day. OKLCH, so a step means the same thing in every hue. */
|
|
39
|
+
--stone-100: oklch(97.5% .004 75); --stone-300: oklch(91% .006 75);
|
|
40
|
+
--stone-500: oklch(72% .008 75); --stone-700: oklch(47% .009 75);
|
|
41
|
+
--stone-900: oklch(26% .008 75);
|
|
42
|
+
|
|
43
|
+
--sage-100: oklch(96% .022 155); --sage-300: oklch(87% .042 155);
|
|
44
|
+
--sage-500: oklch(66% .058 155); --sage-700: oklch(47% .055 155);
|
|
45
|
+
--sage-900: oklch(29% .038 155);
|
|
46
|
+
|
|
47
|
+
--sky-100: oklch(96% .022 240); --sky-300: oklch(87% .045 240);
|
|
48
|
+
--sky-500: oklch(64% .085 245); --sky-700: oklch(48% .095 250);
|
|
49
|
+
--sky-900: oklch(30% .06 250);
|
|
50
|
+
|
|
51
|
+
--clay-100: oklch(96% .022 45); --clay-300: oklch(88% .045 45);
|
|
52
|
+
--clay-500: oklch(68% .085 45); --clay-700: oklch(50% .09 40);
|
|
53
|
+
--clay-900: oklch(31% .055 40);
|
|
54
|
+
|
|
55
|
+
--plum-100: oklch(96% .02 320); --plum-300: oklch(88% .038 320);
|
|
56
|
+
--plum-500: oklch(66% .06 320); --plum-700: oklch(47% .065 320);
|
|
57
|
+
--plum-900: oklch(29% .042 320);
|
|
58
|
+
|
|
59
|
+
--gold-100: oklch(96.5% .028 90); --gold-300: oklch(89% .055 88);
|
|
60
|
+
--gold-500: oklch(74% .085 85); --gold-700: oklch(55% .085 80);
|
|
61
|
+
--gold-900: oklch(33% .05 80);
|
|
62
|
+
|
|
63
|
+
/* ---- semantic ----------------------------------------------- */
|
|
64
|
+
--bg: light-dark(oklch(93.5% .006 75), oklch(16% .006 75)); /* desktop */
|
|
65
|
+
--el: light-dark(oklch(99.4% .002 75), oklch(21% .006 75)); /* paper */
|
|
66
|
+
--face:light-dark(oklch(97.2% .005 75), oklch(25% .007 75)); /* chrome */
|
|
67
|
+
--fg: light-dark(oklch(22% .008 75), oklch(95% .004 75));
|
|
68
|
+
--mut: light-dark(oklch(50% .009 75), oklch(70% .008 75));
|
|
69
|
+
--bd: light-dark(oklch(22% .008 75 / .13), oklch(95% .004 75 / .13));
|
|
70
|
+
--tb: light-dark(oklch(97.2% .005 75 / .85), oklch(25% .007 75 / .85));
|
|
71
|
+
--ac: light-dark(var(--sky-700), oklch(78% .085 245)); /* yours */
|
|
72
|
+
--on-ac: light-dark(#fff, #06121f); /* text on the accent, both themes */
|
|
73
|
+
|
|
74
|
+
/* depth: hairline + soft shadow, macOS weight.
|
|
75
|
+
--sh is an ordinary colour — override it with any notation you like;
|
|
76
|
+
the alphas come off it through relative colour syntax. */
|
|
77
|
+
--sh: oklch(23.1% .007 286);
|
|
78
|
+
--a1: .08; --a2: .06;
|
|
79
|
+
--sh1: oklch(from var(--sh) l c h / var(--a1));
|
|
80
|
+
--sh2: oklch(from var(--sh) l c h / var(--a2));
|
|
81
|
+
--up: 0 1px 1px var(--sh1);
|
|
82
|
+
--up2: 0 1px 2px var(--sh1), 0 6px 16px -6px var(--sh2);
|
|
83
|
+
--dn: inset 0 1px 2px oklch(from var(--sh) l c h / .08);
|
|
84
|
+
--caps: .74em/1.4 var(--f);
|
|
85
|
+
}
|
|
86
|
+
@media (prefers-color-scheme: dark) {
|
|
87
|
+
:root:not([data-theme=light]) { --a1: .5; --a2: .45 }
|
|
88
|
+
}
|
|
89
|
+
@supports (color: contrast-color(red)) {
|
|
90
|
+
:root { --on-ac: contrast-color(var(--ac)) } /* survives any --ac override */
|
|
91
|
+
}
|
|
92
|
+
[data-theme=light] { color-scheme: light; --a1: .08; --a2: .06 }
|
|
93
|
+
[data-theme=dark] { color-scheme: dark; --a1: .5; --a2: .45 }
|
|
94
|
+
|
|
95
|
+
/* ---- base --------------------------------------------------- */
|
|
96
|
+
*, ::before, ::after { box-sizing: border-box }
|
|
97
|
+
/* three prefixed/legacy-looking declarations survive in this sheet, and only
|
|
98
|
+
because no engine ships an alternative today: this one (Safari has no
|
|
99
|
+
unprefixed text-size-adjust), the autofill repaint below, and the progress
|
|
100
|
+
pseudo-elements. Everything else here is unprefixed modern CSS. */
|
|
101
|
+
html { font: 16px/1.65 var(--f); -webkit-text-size-adjust: 100%; tab-size: 2 }
|
|
102
|
+
body {
|
|
103
|
+
margin: 0; background: var(--bg); color: var(--fg);
|
|
104
|
+
overflow-wrap: break-word; /* a pasted URL cannot widen the page */
|
|
105
|
+
font-variant-numeric: slashed-zero tabular-nums;
|
|
106
|
+
}
|
|
107
|
+
::selection { background: color-mix(in srgb, var(--ac) 28%, transparent) }
|
|
108
|
+
:focus-visible { /* the macOS ring */
|
|
109
|
+
outline: 3px solid color-mix(in srgb, var(--ac) 45%, transparent);
|
|
110
|
+
outline-offset: 1px; border-radius: var(--r);
|
|
111
|
+
}
|
|
112
|
+
img, svg, video, iframe { display: block; max-width: 100%; height: auto }
|
|
113
|
+
img, video { border-radius: var(--r); box-shadow: var(--up2) }
|
|
114
|
+
|
|
115
|
+
/* ---- layout: a window on a desktop --------------------------- */
|
|
116
|
+
body > * { max-width: var(--w); margin-inline: auto; padding-inline: 1.9rem }
|
|
117
|
+
body > header, body > main, body > footer {
|
|
118
|
+
background: var(--el); border-inline: 1px solid var(--bd);
|
|
119
|
+
}
|
|
120
|
+
body > header { padding-block: 2.4rem .8rem }
|
|
121
|
+
body > main { padding-bottom: 2.8rem }
|
|
122
|
+
body > footer {
|
|
123
|
+
padding-block: 1.2rem 2rem; color: var(--mut); font-size: .85em;
|
|
124
|
+
border-top: 1px solid var(--bd); border-bottom: 1px solid var(--bd);
|
|
125
|
+
border-radius: 0 0 var(--rw) var(--rw); margin-bottom: 2.5rem;
|
|
126
|
+
box-shadow: 0 18px 30px -22px oklch(from var(--sh) l c h / .5);
|
|
127
|
+
}
|
|
128
|
+
/* nav = unified toolbar: full-bleed, vibrant, contents on the measure */
|
|
129
|
+
body > nav {
|
|
130
|
+
position: sticky; top: 0; z-index: 9; max-width: none;
|
|
131
|
+
padding-inline: max(1.9rem, calc(50% - var(--w) / 2 + 1.9rem),
|
|
132
|
+
env(safe-area-inset-left), env(safe-area-inset-right));
|
|
133
|
+
display: flex; flex-wrap: wrap; gap: 1.1rem; align-items: center;
|
|
134
|
+
padding-block: calc(.6rem + env(safe-area-inset-top, 0px)) .6rem;
|
|
135
|
+
background: var(--tb); color: var(--fg);
|
|
136
|
+
backdrop-filter: saturate(180%) blur(20px);
|
|
137
|
+
border-bottom: 1px solid var(--bd); font-size: .92em;
|
|
138
|
+
}
|
|
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 {
|
|
143
|
+
display: flex; flex-wrap: wrap; gap: 1.1rem; list-style: none; padding: 0; margin: 0;
|
|
144
|
+
flex: 1; /* takes the slack, so a trailing control sits at the end */
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ---- type --------------------------------------------------- */
|
|
148
|
+
h1, h2, h3, h4, h5, h6 { line-height: 1.3; margin: 1.4rem 0 .7rem; text-wrap: balance }
|
|
149
|
+
h1 { font-size: 1.55rem; letter-spacing: -.015em }
|
|
150
|
+
h2 { font-size: 1.25rem; letter-spacing: -.01em; margin-top: 2.4rem }
|
|
151
|
+
h3 { font-size: 1.08rem; margin-top: 1.9rem }
|
|
152
|
+
h4 { font-size: 1rem }
|
|
153
|
+
h5, h6 { font: 700 var(--caps); text-transform: uppercase; letter-spacing: .1em; color: var(--mut) }
|
|
154
|
+
:is(h1,h2,h3,h4,h5,h6):first-child { margin-top: 0 }
|
|
155
|
+
@supports (text-box: trim-both cap alphabetic) {
|
|
156
|
+
h1, h2, h3, h4 { text-box: trim-both cap alphabetic } /* optical, not metric */
|
|
157
|
+
}
|
|
158
|
+
/* …but a new section still needs air above its heading */
|
|
159
|
+
:is(section, article) + :is(section, article) { margin-top: 2.8rem }
|
|
160
|
+
|
|
161
|
+
p, ul, ol, dl, pre, blockquote, table, figure, details, fieldset, hr { margin: 1.1rem 0 }
|
|
162
|
+
p { text-wrap: pretty }
|
|
163
|
+
a { color: var(--ac); text-underline-offset: 3px; text-decoration-thickness: 1px }
|
|
164
|
+
a:hover { text-decoration-thickness: 2px } /* quiet — no invert mid-paragraph */
|
|
165
|
+
small { font-size: .85em; color: var(--mut) }
|
|
166
|
+
hr { border: 0; border-top: 1px solid var(--bd); margin: 2.2rem 0 }
|
|
167
|
+
mark { background: light-dark(#ffe58f, #5c4a15); color: inherit; border-radius: 3px; padding: 0 .15em }
|
|
168
|
+
abbr[title] { text-decoration: underline dotted; cursor: help }
|
|
169
|
+
blockquote {
|
|
170
|
+
border-inline-start: 2px solid var(--bd);
|
|
171
|
+
padding-block: .15rem; padding-inline: 1.2rem 0; color: var(--mut);
|
|
172
|
+
}
|
|
173
|
+
ul, ol { padding-inline-start: 1.5rem }
|
|
174
|
+
li::marker { color: var(--mut) }
|
|
175
|
+
dl { display: grid; grid-template-columns: max-content 1fr; gap: .35rem 1rem }
|
|
176
|
+
dt { color: var(--mut) }
|
|
177
|
+
dd { margin: 0 }
|
|
178
|
+
figcaption { font-size: .85em; color: var(--mut); text-align: center }
|
|
179
|
+
|
|
180
|
+
/* ---- code --------------------------------------------------- */
|
|
181
|
+
code, kbd, samp, pre { font-family: var(--fm); font-size: .9em }
|
|
182
|
+
:not(pre) > code, samp {
|
|
183
|
+
background: var(--face); border: 1px solid var(--bd);
|
|
184
|
+
border-radius: var(--r); padding: .08em .35em;
|
|
185
|
+
}
|
|
186
|
+
kbd {
|
|
187
|
+
background: var(--face); border: 1px solid var(--bd); border-bottom-width: 2px;
|
|
188
|
+
border-radius: var(--r); padding: .08em .4em; box-shadow: var(--up);
|
|
189
|
+
}
|
|
190
|
+
pre {
|
|
191
|
+
background: var(--face); border: 1px solid var(--bd); border-radius: var(--r);
|
|
192
|
+
padding: 1rem; overflow-x: auto;
|
|
193
|
+
}
|
|
194
|
+
pre code { background: none; border: 0; padding: 0; font-size: 1em }
|
|
195
|
+
|
|
196
|
+
/* Syntax theme. The sheet ships no highlighter — bring highlight.js or Prism
|
|
197
|
+
and the tokens land in this palette, in both themes. */
|
|
198
|
+
:root {
|
|
199
|
+
--syn-comment: light-dark(#6b7280, #8b949e);
|
|
200
|
+
--syn-key: light-dark(#7c3aed, #c4a7fb);
|
|
201
|
+
--syn-str: light-dark(#15803d, #7ee787);
|
|
202
|
+
--syn-num: light-dark(#b45309, #f0b866);
|
|
203
|
+
--syn-fn: light-dark(#1d4ed8, #93c5fd);
|
|
204
|
+
--syn-attr: light-dark(#b91c1c, #ff9492);
|
|
205
|
+
}
|
|
206
|
+
:is(.hljs-comment, .hljs-quote, .token.comment, .token.prolog, .token.doctype) {
|
|
207
|
+
color: var(--syn-comment); font-style: italic;
|
|
208
|
+
}
|
|
209
|
+
:is(.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type, .hljs-built_in,
|
|
210
|
+
.token.keyword, .token.atrule, .token.rule, .token.important, .token.selector) {
|
|
211
|
+
color: var(--syn-key);
|
|
212
|
+
}
|
|
213
|
+
:is(.hljs-string, .hljs-meta-string, .token.string, .token.attr-value, .token.char) {
|
|
214
|
+
color: var(--syn-str);
|
|
215
|
+
}
|
|
216
|
+
:is(.hljs-number, .hljs-symbol, .hljs-bullet, .token.number, .token.boolean,
|
|
217
|
+
.token.constant, .token.unit) {
|
|
218
|
+
color: var(--syn-num);
|
|
219
|
+
}
|
|
220
|
+
:is(.hljs-title, .hljs-name, .hljs-section, .hljs-function,
|
|
221
|
+
.token.tag, .token.function, .token.class-name) {
|
|
222
|
+
color: var(--syn-fn);
|
|
223
|
+
}
|
|
224
|
+
:is(.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable,
|
|
225
|
+
.token.attr-name, .token.property, .token.variable) {
|
|
226
|
+
color: var(--syn-attr);
|
|
227
|
+
}
|
|
228
|
+
:is(.hljs-punctuation, .hljs-operator, .token.punctuation, .token.operator) {
|
|
229
|
+
color: var(--mut);
|
|
230
|
+
}
|
|
231
|
+
:is(.hljs-emphasis, .token.italic) { font-style: italic }
|
|
232
|
+
:is(.hljs-strong, .token.bold) { font-weight: 700 }
|
|
233
|
+
:is(.hljs-deletion, .token.deleted) { color: light-dark(#c0392b, #ff6b6b) }
|
|
234
|
+
:is(.hljs-addition, .token.inserted) { color: light-dark(#15803d, #7ee787) }
|
|
235
|
+
|
|
236
|
+
/* ---- table -------------------------------------------------- */
|
|
237
|
+
table { width: 100%; border-collapse: collapse; font-size: .92em }
|
|
238
|
+
th, td {
|
|
239
|
+
border-bottom: 1px solid var(--bd); padding-block: .55rem;
|
|
240
|
+
padding-inline: 0 .7rem; text-align: start;
|
|
241
|
+
}
|
|
242
|
+
th:last-child, td:last-child { padding-inline-end: 0 }
|
|
243
|
+
thead th {
|
|
244
|
+
border-bottom: 1px solid color-mix(in srgb, var(--fg) 45%, transparent);
|
|
245
|
+
font: 700 var(--caps); text-transform: uppercase; letter-spacing: .09em; color: var(--mut);
|
|
246
|
+
padding-bottom: .4rem;
|
|
247
|
+
}
|
|
248
|
+
tbody tr:last-child td { border-bottom: 0 }
|
|
249
|
+
/* a totals row is not just another row */
|
|
250
|
+
tfoot td { font-weight: 700; border-top: 1px solid var(--bd); border-bottom: 0 }
|
|
251
|
+
/* our text-align was overriding HTML own alignment; honour it for numbers */
|
|
252
|
+
:is(th, td)[align=right] { text-align: right }
|
|
253
|
+
:is(th, td)[align=center] { text-align: center }
|
|
254
|
+
tbody tr:hover { background: var(--face) }
|
|
255
|
+
@media (width <= 40rem) {
|
|
256
|
+
table { display: block; overflow-x: auto; overscroll-behavior-x: contain }
|
|
257
|
+
}
|
|
258
|
+
caption { text-align: start; color: var(--mut); font-size: .85em; padding-bottom: .35rem }
|
|
259
|
+
|
|
260
|
+
/* ---- forms: fields sink, buttons rise ------------------------ */
|
|
261
|
+
button, input, select, textarea { font: inherit; color: inherit }
|
|
262
|
+
label { display: block; font-size: .85em; color: var(--mut) }
|
|
263
|
+
label:has(input) { font-size: 1rem; color: var(--fg) }
|
|
264
|
+
/* a label following a field belongs to the next one: give it air, except in a
|
|
265
|
+
.row, where the toolbar is horizontal and the margin would knock it askew */
|
|
266
|
+
:is(input, select, textarea) + label { margin-top: .9rem }
|
|
267
|
+
textarea { width: 100%; min-height: 5rem; resize: vertical }
|
|
268
|
+
@supports (field-sizing: content) {
|
|
269
|
+
textarea { field-sizing: content; max-height: 24rem }
|
|
270
|
+
}
|
|
271
|
+
input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file],[type=submit],[type=button],[type=reset]),
|
|
272
|
+
select, textarea {
|
|
273
|
+
display: block; width: 100%; padding: .4em .6em; background: var(--el);
|
|
274
|
+
border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--dn);
|
|
275
|
+
}
|
|
276
|
+
select {
|
|
277
|
+
appearance: none; padding-inline-end: 2.2rem;
|
|
278
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
279
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
280
|
+
background-position: calc(100% - 17px) 55%, calc(100% - 12.5px) 55%;
|
|
281
|
+
background-size: 5px 5px; background-repeat: no-repeat;
|
|
282
|
+
}
|
|
283
|
+
select:dir(rtl) { background-position: 12.5px 55%, 17px 55% }
|
|
284
|
+
select[multiple] { background-image: none; padding-inline-end: .6em }
|
|
285
|
+
|
|
286
|
+
/* a <select> whose dropdown can finally be styled like the rest of the sheet */
|
|
287
|
+
@supports (appearance: base-select) {
|
|
288
|
+
select, ::picker(select) { appearance: base-select }
|
|
289
|
+
select {
|
|
290
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
291
|
+
gap: .6em; background-image: none; padding-inline-end: .7em;
|
|
292
|
+
}
|
|
293
|
+
select::picker-icon {
|
|
294
|
+
content: ""; inline-size: .45em; block-size: .45em; flex: none;
|
|
295
|
+
border: solid currentColor; border-width: 0 1px 1px 0;
|
|
296
|
+
rotate: 45deg; translate: 0 -.15em; opacity: .7;
|
|
297
|
+
transition: rotate .15s ease, translate .15s ease;
|
|
298
|
+
}
|
|
299
|
+
select:open::picker-icon { rotate: 225deg; translate: 0 .1em }
|
|
300
|
+
::picker(select) {
|
|
301
|
+
min-width: anchor-size(width); /* never narrower than the field */
|
|
302
|
+
border: 1px solid var(--bd); border-radius: var(--r); padding: .3rem;
|
|
303
|
+
background: var(--el); margin-block-start: .35rem;
|
|
304
|
+
box-shadow: var(--up2), 0 20px 40px -14px oklch(from var(--sh) l c h / .4);
|
|
305
|
+
}
|
|
306
|
+
option {
|
|
307
|
+
display: flex; align-items: center; gap: .5em; padding: .4em .6em;
|
|
308
|
+
border-radius: calc(var(--r) - 2px);
|
|
309
|
+
}
|
|
310
|
+
option:hover { background: var(--face) }
|
|
311
|
+
option:checked { background: var(--ac); color: var(--on-ac) }
|
|
312
|
+
option::checkmark { order: 1; margin-inline-start: auto; content: "✓" }
|
|
313
|
+
}
|
|
314
|
+
:is(input,select,textarea):hover:not(:disabled) { border-color: color-mix(in srgb, var(--fg) 25%, transparent) }
|
|
315
|
+
:is(input,select,textarea):focus-visible { border-color: var(--ac) }
|
|
316
|
+
input[type=checkbox], input[type=radio] {
|
|
317
|
+
width: 1.15em; height: 1.15em; margin: 0 .45em 0 0; vertical-align: -.18em;
|
|
318
|
+
}
|
|
319
|
+
label:has(input[type=checkbox]), label:has(input[type=radio]) { line-height: 1.9 }
|
|
320
|
+
input[type=range] { width: 100%; height: 1.15em }
|
|
321
|
+
::placeholder { color: var(--mut) }
|
|
322
|
+
@supports selector(::spelling-error) {
|
|
323
|
+
::spelling-error { text-decoration: wavy underline light-dark(#c0392b, #ff6b6b) }
|
|
324
|
+
::grammar-error { text-decoration: wavy underline light-dark(#9a6700, #d29922) }
|
|
325
|
+
}
|
|
326
|
+
:is(input, textarea):read-only:not(:disabled) { background: var(--face); cursor: default }
|
|
327
|
+
label:has(+ :required)::after, label:has(:required)::after {
|
|
328
|
+
content: " *"; color: var(--ac); /* mark what is actually required */
|
|
329
|
+
}
|
|
330
|
+
/* the browser paints autofilled fields yellow; repaint them ourselves */
|
|
331
|
+
input:autofill, input:autofill:hover, input:autofill:focus {
|
|
332
|
+
-webkit-text-fill-color: var(--fg);
|
|
333
|
+
box-shadow: var(--dn), inset 0 0 0 100px var(--el);
|
|
334
|
+
caret-color: var(--ac);
|
|
335
|
+
}
|
|
336
|
+
:disabled { color: var(--mut); cursor: not-allowed; box-shadow: none }
|
|
337
|
+
/* No notched border: the legend becomes the panel title. The float is not
|
|
338
|
+
nostalgia — a legend keeps its magic placement under block, grid and flex
|
|
339
|
+
fieldsets alike, and floating it is still the only way out. */
|
|
340
|
+
fieldset {
|
|
341
|
+
border: 1px solid var(--bd); border-radius: var(--r); padding: .9rem 1rem 1rem;
|
|
342
|
+
background: var(--face);
|
|
343
|
+
}
|
|
344
|
+
fieldset:focus-within { border-color: color-mix(in srgb, var(--ac) 45%, var(--bd)) }
|
|
345
|
+
legend {
|
|
346
|
+
float: left; width: 100%; margin-bottom: .8rem; padding: 0 0 .6rem;
|
|
347
|
+
border-bottom: 1px solid var(--bd);
|
|
348
|
+
font-size: .95em; font-weight: 700; color: var(--fg);
|
|
349
|
+
}
|
|
350
|
+
legend + * { clear: left }
|
|
351
|
+
|
|
352
|
+
button, [type=submit], [type=button], [type=reset],
|
|
353
|
+
:where(a.primary, a[role=button]), ::file-selector-button {
|
|
354
|
+
display: inline-block; text-decoration: none; text-align: center;
|
|
355
|
+
padding: .38em 1.1em; background: var(--el); color: var(--fg); cursor: pointer;
|
|
356
|
+
border: 1px solid var(--bd); border-radius: var(--r); box-shadow: var(--up);
|
|
357
|
+
font: inherit; min-width: 5rem;
|
|
358
|
+
}
|
|
359
|
+
:is(button,[type=submit],[type=button],[type=reset],a.primary,a[role=button]):hover:not(:disabled) { background: var(--face) }
|
|
360
|
+
:is(button,[type=submit],[type=button],[type=reset],a.primary,a[role=button]):active:not(:disabled) {
|
|
361
|
+
box-shadow: var(--dn); translate: 0 .5px;
|
|
362
|
+
}
|
|
363
|
+
[type=submit], .primary { background: var(--ac); border-color: transparent; color: var(--on-ac) }
|
|
364
|
+
:is([type=submit], .primary, a.primary):hover:not(:disabled) {
|
|
365
|
+
background: oklch(from var(--ac) calc(l + .05) c h);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* button detail: sizes, variants, toggles, icon and block forms */
|
|
369
|
+
[data-size=sm] { padding: .2em .7em; font-size: .85em; min-width: 0 }
|
|
370
|
+
[data-size=lg] { padding: .55em 1.5em; font-size: 1.05em }
|
|
371
|
+
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] {
|
|
376
|
+
background: none; border: 0; box-shadow: none; min-width: 0; padding-inline: .2em;
|
|
377
|
+
color: var(--ac); text-decoration: underline; text-underline-offset: 3px;
|
|
378
|
+
}
|
|
379
|
+
[data-variant=link]:hover:not(:disabled) { background: none; text-decoration-thickness: 2px }
|
|
380
|
+
button[aria-pressed=true] { background: var(--face); color: var(--ac); font-weight: 700 }
|
|
381
|
+
button[aria-pressed=true]:hover:not(:disabled) {
|
|
382
|
+
background: color-mix(in srgb, var(--fg) 7%, var(--face));
|
|
383
|
+
}
|
|
384
|
+
/* filled buttons get a lit top edge, the way a real control does */
|
|
385
|
+
:is([type=submit], .primary) { box-shadow: var(--up), inset 0 1px 0 light-dark(#ffffff33, #ffffff26) }
|
|
386
|
+
:is([type=submit], .primary):active:not(:disabled) { box-shadow: inset 0 1px 3px #0006 }
|
|
387
|
+
|
|
388
|
+
@supports (corner-shape: squircle) {
|
|
389
|
+
* { corner-shape: squircle } /* inert wherever the radius is 0 */
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* ---- disclosure / misc -------------------------------------- */
|
|
393
|
+
details {
|
|
394
|
+
border: 1px solid var(--bd); border-radius: var(--r);
|
|
395
|
+
padding: .7rem 1.1rem; background: var(--el);
|
|
396
|
+
}
|
|
397
|
+
summary { cursor: pointer; font-weight: 700; list-style: none }
|
|
398
|
+
summary::before { content: "▸ "; color: var(--mut) }
|
|
399
|
+
details[open] > summary::before { content: "▾ " }
|
|
400
|
+
details[open] > summary { margin-bottom: .6rem }
|
|
401
|
+
dialog {
|
|
402
|
+
background: var(--el); color: var(--fg); border: 1px solid var(--bd);
|
|
403
|
+
border-radius: var(--rw); padding: 1.4rem; max-width: min(30rem, 92dvw);
|
|
404
|
+
box-shadow: 0 32px 64px -16px oklch(from var(--sh) l c h / .45);
|
|
405
|
+
}
|
|
406
|
+
dialog > :last-child:not(footer) { margin-bottom: 0 }
|
|
407
|
+
dialog, [popover] { overscroll-behavior: contain }
|
|
408
|
+
dialog::backdrop { background: oklch(from var(--sh) l c h / .35); backdrop-filter: blur(3px) }
|
|
409
|
+
|
|
410
|
+
/* dialog anatomy: an optional header and footer, pinned while the body scrolls.
|
|
411
|
+
Sticky rather than a grid, because a classless sheet has no wrapper around
|
|
412
|
+
the loose middle children to make a grid row out of. */
|
|
413
|
+
dialog:has(> :is(header, footer)) { max-block-size: min(44rem, 85dvh); overflow: auto }
|
|
414
|
+
dialog > :is(header, footer) {
|
|
415
|
+
position: sticky; z-index: 1;
|
|
416
|
+
margin-inline: -1.4rem; padding: .7rem 1.4rem;
|
|
417
|
+
background: var(--el); /* the dialog's own surface, so it reads as one sheet */
|
|
418
|
+
}
|
|
419
|
+
dialog > header {
|
|
420
|
+
inset-block-start: -1.4rem; margin-block: -1.4rem 1rem;
|
|
421
|
+
font-weight: 700; border-bottom: 1px solid var(--bd);
|
|
422
|
+
border-radius: var(--rw) var(--rw) 0 0;
|
|
423
|
+
}
|
|
424
|
+
/* the title carries the header's weight; it does not need its own margins */
|
|
425
|
+
dialog > header > :is(h1, h2, h3, h4, p) { margin: 0; font-size: 1.05rem }
|
|
426
|
+
dialog > footer {
|
|
427
|
+
inset-block-end: -1.4rem; margin-block: 1rem -1.4rem;
|
|
428
|
+
border-top: 1px solid var(--bd); border-radius: 0 0 var(--rw) var(--rw);
|
|
429
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
|
|
430
|
+
justify-content: flex-end; /* actions sit at the end edge */
|
|
431
|
+
}
|
|
432
|
+
/* a note beside the buttons pushes them over, rather than sitting under them */
|
|
433
|
+
dialog > footer > :is(p, span):first-child { margin: 0; margin-inline-end: auto; color: var(--mut) }
|
|
434
|
+
/* sheets come up, they do not appear */
|
|
435
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
436
|
+
dialog, dialog::backdrop {
|
|
437
|
+
transition: opacity .2s, translate .2s, display .2s allow-discrete, overlay .2s allow-discrete;
|
|
438
|
+
}
|
|
439
|
+
dialog { opacity: 0; translate: 0 10px }
|
|
440
|
+
dialog[open] { opacity: 1; translate: 0 }
|
|
441
|
+
dialog::backdrop { opacity: 0 }
|
|
442
|
+
dialog[open]::backdrop { opacity: 1 }
|
|
443
|
+
@starting-style { dialog[open] { opacity: 0; translate: 0 10px } }
|
|
444
|
+
@starting-style { dialog[open]::backdrop { opacity: 0 } }
|
|
445
|
+
[popover] {
|
|
446
|
+
opacity: 0; translate: 0 -4px;
|
|
447
|
+
transition: opacity .13s, translate .13s, display .13s allow-discrete, overlay .13s allow-discrete;
|
|
448
|
+
}
|
|
449
|
+
[popover]:popover-open { opacity: 1; translate: 0 }
|
|
450
|
+
@starting-style { [popover]:popover-open { opacity: 0; translate: 0 -4px } }
|
|
451
|
+
}
|
|
452
|
+
search { display: block }
|
|
453
|
+
::target-text { background: color-mix(in srgb, var(--ac) 28%, transparent) }
|
|
454
|
+
aside {
|
|
455
|
+
border: 1px solid var(--bd); border-inline-start: 3px solid var(--ac);
|
|
456
|
+
border-radius: var(--r); border-start-start-radius: 0; border-end-start-radius: 0;
|
|
457
|
+
background: var(--el); padding: .7rem 1.1rem;
|
|
458
|
+
}
|
|
459
|
+
progress { width: 100%; height: .5rem; border: 0; border-radius: calc(infinity * 1px); background: var(--face) }
|
|
460
|
+
progress::-webkit-progress-bar { background: var(--face); border-radius: calc(infinity * 1px) }
|
|
461
|
+
progress::-webkit-progress-value { background: var(--ac); border-radius: calc(infinity * 1px) }
|
|
462
|
+
progress::-moz-progress-bar { background: var(--ac); border-radius: calc(infinity * 1px) }
|
|
463
|
+
|
|
464
|
+
/* details can only animate to auto height once keywords interpolate */
|
|
465
|
+
@supports selector(::details-content) {
|
|
466
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
467
|
+
:root { interpolate-size: allow-keywords }
|
|
468
|
+
details::details-content {
|
|
469
|
+
block-size: 0; overflow: clip;
|
|
470
|
+
transition: block-size .28s ease, content-visibility .28s allow-discrete;
|
|
471
|
+
}
|
|
472
|
+
details[open]::details-content { block-size: auto }
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* #core-omit-start */
|
|
477
|
+
/* ---- components, addressed by ARIA rather than by class ----- */
|
|
478
|
+
|
|
479
|
+
/* segmented control: <div role=group> of buttons */
|
|
480
|
+
[role=group] { display: inline-flex; vertical-align: middle }
|
|
481
|
+
[role=group] > * { border-radius: 0; margin-inline-start: -1px; box-shadow: none; min-width: 0 }
|
|
482
|
+
[role=group] > :first-child {
|
|
483
|
+
margin-inline-start: 0;
|
|
484
|
+
border-start-start-radius: var(--r); border-end-start-radius: var(--r);
|
|
485
|
+
}
|
|
486
|
+
[role=group] > :last-child {
|
|
487
|
+
border-start-end-radius: var(--r); border-end-end-radius: var(--r);
|
|
488
|
+
}
|
|
489
|
+
[role=group] > [aria-pressed=true] { background: var(--face); color: var(--ac); font-weight: 700 }
|
|
490
|
+
|
|
491
|
+
/* tabs: <div role=tablist> of <button role=tab aria-selected> */
|
|
492
|
+
[role=tablist] { display: flex; gap: .1rem; border-bottom: 1px solid var(--bd); margin: 1.1rem 0 }
|
|
493
|
+
[role=tab] {
|
|
494
|
+
background: none; border: 0; box-shadow: none; min-width: 0; color: var(--mut);
|
|
495
|
+
padding: .45em .9em; border-radius: var(--r) var(--r) 0 0; margin-bottom: -1px;
|
|
496
|
+
}
|
|
497
|
+
[role=tab]:hover:not(:disabled) { background: var(--face); color: var(--fg) }
|
|
498
|
+
[role=tab][aria-selected=true] { color: var(--fg); box-shadow: inset 0 -2px 0 var(--ac) }
|
|
499
|
+
|
|
500
|
+
/* switch: <input type=checkbox role=switch> */
|
|
501
|
+
input[type=checkbox][switch],
|
|
502
|
+
input[type=checkbox][role=switch] {
|
|
503
|
+
appearance: none; width: 2.2em; height: 1.3em; border-radius: calc(infinity * 1px); border: 0;
|
|
504
|
+
background: color-mix(in srgb, var(--fg) 20%, transparent);
|
|
505
|
+
position: relative; cursor: pointer; vertical-align: -.3em; margin-inline-end: .5em;
|
|
506
|
+
}
|
|
507
|
+
input[switch]::after, input[role=switch]::after {
|
|
508
|
+
content: ""; position: absolute; inset-block-start: .14em; inset-inline-start: .14em;
|
|
509
|
+
width: 1.02em; height: 1.02em; border-radius: 50%; background: #fff; box-shadow: var(--up);
|
|
510
|
+
transition: translate .16s ease;
|
|
511
|
+
}
|
|
512
|
+
input[switch]:checked, input[role=switch]:checked { background: var(--ac) }
|
|
513
|
+
/* translate is physical, so the knob is told which way to travel */
|
|
514
|
+
input[switch]:checked::after, input[role=switch]:checked::after { translate: .9em 0 }
|
|
515
|
+
:is(input[switch], input[role=switch]):checked:dir(rtl)::after { translate: -.9em 0 }
|
|
516
|
+
|
|
517
|
+
/* menu: any [popover] */
|
|
518
|
+
[popover] {
|
|
519
|
+
background: var(--el); color: var(--fg); border: 1px solid var(--bd);
|
|
520
|
+
border-radius: var(--r); padding: .3rem; min-width: 12rem;
|
|
521
|
+
max-block-size: 60dvh; overflow: auto; font-size: .95em;
|
|
522
|
+
box-shadow: var(--up2), 0 20px 40px -14px oklch(from var(--sh) l c h / .4);
|
|
523
|
+
}
|
|
524
|
+
/* anchor to the invoking button; without support it centres, which is fine */
|
|
525
|
+
@supports (position-area: block-end span-inline-end) {
|
|
526
|
+
[popover]:popover-open {
|
|
527
|
+
position: fixed;
|
|
528
|
+
inset: auto; /* clear the UA inset:0 centring */
|
|
529
|
+
position-anchor: auto; /* the button that opened it */
|
|
530
|
+
position-area: block-end span-inline-end; /* keywords must share an axis */
|
|
531
|
+
position-try-fallbacks: flip-block, flip-inline;
|
|
532
|
+
margin: .35rem 0 0;
|
|
533
|
+
}
|
|
534
|
+
@supports (min-width: anchor-size(width)) {
|
|
535
|
+
[popover]:popover-open {
|
|
536
|
+
min-width: max(12rem, anchor-size(width));
|
|
537
|
+
position-visibility: anchors-visible; /* hide with its anchor */
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
[popover] :is(ul, menu) { list-style: none; padding: 0; margin: 0 }
|
|
542
|
+
/* specificity has to clear the generic button rule (0,3,0) — hence :not(:disabled) */
|
|
543
|
+
[popover] :is(a, button):not(:disabled) {
|
|
544
|
+
display: block; width: 100%; text-align: start; background: none; border: 0;
|
|
545
|
+
box-shadow: none; min-width: 0; padding: .4em .6em; text-decoration: none;
|
|
546
|
+
color: inherit; border-radius: calc(var(--r) - 2px); font-weight: 400;
|
|
547
|
+
}
|
|
548
|
+
[popover] :is(a, button):hover:not(:disabled) { background: var(--ac); color: var(--on-ac) }
|
|
549
|
+
[popover] hr { margin: .3rem 0 }
|
|
550
|
+
|
|
551
|
+
/* breadcrumbs: <nav aria-label="Breadcrumb"><ol> */
|
|
552
|
+
nav[aria-label] ol { display: flex; flex-wrap: wrap; gap: .5em; list-style: none; padding: 0; font-size: .9em }
|
|
553
|
+
nav[aria-label] li + li::before { content: "/"; margin-inline-end: .5em; color: var(--mut) }
|
|
554
|
+
|
|
555
|
+
/* busy: [aria-busy=true] */
|
|
556
|
+
[aria-busy=true] { cursor: progress }
|
|
557
|
+
[aria-busy=true]::before {
|
|
558
|
+
content: ""; display: inline-block; width: .85em; height: .85em; margin-inline-end: .55em;
|
|
559
|
+
border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
|
|
560
|
+
vertical-align: -.1em;
|
|
561
|
+
}
|
|
562
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
563
|
+
[aria-busy=true]::before { animation: fertig-spin .6s linear infinite }
|
|
564
|
+
}
|
|
565
|
+
@keyframes fertig-spin { to { rotate: 360deg } }
|
|
566
|
+
|
|
567
|
+
/* tooltip: [data-tooltip] */
|
|
568
|
+
[data-tooltip] { position: relative }
|
|
569
|
+
[data-tooltip]:is(:hover, :focus-visible)::after {
|
|
570
|
+
content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%;
|
|
571
|
+
translate: -50% -.45em; z-index: 10; white-space: nowrap; pointer-events: none;
|
|
572
|
+
background: light-dark(#1d1d1f, #3a3a40); color: #fff; font-size: .8em;
|
|
573
|
+
padding: .25em .55em; border-radius: var(--r); box-shadow: var(--up2);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/* tones: retint any component by redefining its accent */
|
|
577
|
+
[data-tone=ok] { --ac: light-dark(var(--sage-700), oklch(76% .085 155)) }
|
|
578
|
+
[data-tone=warn] { --ac: light-dark(var(--gold-700), oklch(80% .095 85)) }
|
|
579
|
+
[data-tone=err] { --ac: light-dark(var(--clay-700), oklch(75% .105 35)) }
|
|
580
|
+
[data-tone] .badge, .badge[data-tone] { color: var(--ac); border-color: var(--ac) }
|
|
581
|
+
|
|
582
|
+
/* ---- parity with the component libraries --------------------- */
|
|
583
|
+
|
|
584
|
+
/* avatar: works on an <img> or on initials in a <span> */
|
|
585
|
+
[data-avatar] {
|
|
586
|
+
display: inline-grid; place-items: center; flex: none; overflow: hidden;
|
|
587
|
+
inline-size: 2.5rem; aspect-ratio: 1; border-radius: 50%;
|
|
588
|
+
background: var(--face); border: 1px solid var(--bd);
|
|
589
|
+
font-size: .8em; font-weight: 700; color: var(--mut); object-fit: cover;
|
|
590
|
+
}
|
|
591
|
+
[data-avatar=sm] { inline-size: 1.75rem; font-size: .7em }
|
|
592
|
+
[data-avatar=lg] { inline-size: 3.5rem; font-size: 1em }
|
|
593
|
+
|
|
594
|
+
/* skeleton: put it on the element that is still loading */
|
|
595
|
+
[data-skeleton] {
|
|
596
|
+
color: transparent; border-radius: var(--r); pointer-events: none;
|
|
597
|
+
user-select: none; min-height: 1em;
|
|
598
|
+
background: linear-gradient(90deg, var(--face),
|
|
599
|
+
color-mix(in srgb, var(--fg) 9%, var(--face)), var(--face));
|
|
600
|
+
background-size: 200% 100%;
|
|
601
|
+
}
|
|
602
|
+
[data-skeleton] * { visibility: hidden }
|
|
603
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
604
|
+
[data-skeleton] { animation: fertig-shimmer 1.4s linear infinite }
|
|
605
|
+
@keyframes fertig-shimmer { to { background-position: -200% 0 } }
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* sheet / drawer: the same <dialog>, docked to an edge */
|
|
609
|
+
dialog[data-side] { max-width: none; margin: 0; border-radius: 0; padding: 1.4rem }
|
|
610
|
+
dialog[data-side=right] {
|
|
611
|
+
inset: 0 0 0 auto; block-size: 100dvh; inline-size: min(28rem, 92dvw);
|
|
612
|
+
border-block: 0; border-inline-end: 0;
|
|
613
|
+
}
|
|
614
|
+
dialog[data-side=left] {
|
|
615
|
+
inset: 0 auto 0 0; block-size: 100dvh; inline-size: min(28rem, 92dvw);
|
|
616
|
+
border-block: 0; border-inline-start: 0;
|
|
617
|
+
}
|
|
618
|
+
dialog[data-side=bottom] {
|
|
619
|
+
inset: auto 0 0 0; inline-size: 100%; max-block-size: 85dvh;
|
|
620
|
+
border-radius: var(--rw) var(--rw) 0 0; border-bottom: 0;
|
|
621
|
+
}
|
|
622
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
623
|
+
dialog[data-side=right] { translate: 100% 0 }
|
|
624
|
+
dialog[data-side=left] { translate: -100% 0 }
|
|
625
|
+
dialog[data-side=bottom]{ translate: 0 100% }
|
|
626
|
+
dialog[data-side][open] { translate: 0 0 }
|
|
627
|
+
@starting-style {
|
|
628
|
+
dialog[data-side=right][open] { translate: 100% 0 }
|
|
629
|
+
dialog[data-side=left][open] { translate: -100% 0 }
|
|
630
|
+
dialog[data-side=bottom][open]{ translate: 0 100% }
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* toast: a non-modal popover parked in a corner */
|
|
635
|
+
[popover][data-toast] {
|
|
636
|
+
position: fixed; inset: auto 1rem 1rem auto; margin: 0;
|
|
637
|
+
min-width: 16rem; max-width: min(24rem, 92dvw); padding: .8rem 1rem;
|
|
638
|
+
}
|
|
639
|
+
@supports (position-area: block-end span-inline-end) {
|
|
640
|
+
[popover][data-toast]:popover-open {
|
|
641
|
+
position-area: none; inset: auto 1rem 1rem auto; min-width: 16rem;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/* list group: a plain <menu> outside a popover is a bordered list */
|
|
646
|
+
menu {
|
|
647
|
+
list-style: none; padding: 0; margin: 1.1rem 0;
|
|
648
|
+
border: 1px solid var(--bd); border-radius: var(--r); background: var(--el);
|
|
649
|
+
}
|
|
650
|
+
menu > li { padding: .55rem .85rem }
|
|
651
|
+
menu > li + li { border-top: 1px solid var(--bd) }
|
|
652
|
+
[popover] menu { border: 0; padding: 0; margin: 0; background: none }
|
|
653
|
+
[popover] menu > li { padding: 0 }
|
|
654
|
+
[popover] menu > li + li { border-top: 0 }
|
|
655
|
+
|
|
656
|
+
/* app layout: a sticky sidebar beside the content */
|
|
657
|
+
[data-layout=sidebar] { display: grid; gap: 1.5rem; align-items: start }
|
|
658
|
+
@media (width >= 56rem) {
|
|
659
|
+
[data-layout=sidebar] { grid-template-columns: 15rem minmax(0, 1fr) }
|
|
660
|
+
[data-layout=sidebar] > :first-child { position: sticky; top: 4.5rem }
|
|
661
|
+
}
|
|
662
|
+
[data-layout=sidebar] > * { min-width: 0 }
|
|
663
|
+
|
|
664
|
+
/* carousel: scroll snapping, with real scroll buttons where they exist */
|
|
665
|
+
[data-carousel] {
|
|
666
|
+
display: flex; gap: 1rem; overflow-x: auto; padding-bottom: .5rem;
|
|
667
|
+
scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
|
|
668
|
+
}
|
|
669
|
+
[data-carousel] > * { scroll-snap-align: start; flex: 0 0 min(20rem, 82%) }
|
|
670
|
+
|
|
671
|
+
/* #core-omit-end */
|
|
672
|
+
|
|
673
|
+
/* ---- the only classes (all optional) ------------------------ */
|
|
674
|
+
.muted { color: var(--mut) }
|
|
675
|
+
.center { text-align: center }
|
|
676
|
+
.wide { max-width: 66rem }
|
|
677
|
+
.row { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center }
|
|
678
|
+
/* Form controls are full-width blocks by default, which is right in a form and
|
|
679
|
+
wrong in a toolbar. Inside a .row they size to the row instead. */
|
|
680
|
+
.row :is(input:not([type=checkbox],[type=radio]), textarea) {
|
|
681
|
+
width: auto; flex: 1 1 10rem; min-width: 0;
|
|
682
|
+
}
|
|
683
|
+
.row select { width: auto; flex: 0 1 auto; min-width: 0 }
|
|
684
|
+
.row :is(progress, meter) { width: auto; flex: 1 1 6rem }
|
|
685
|
+
.row > label { flex: none; margin-top: 0 }
|
|
686
|
+
.grid { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)) }
|
|
687
|
+
.card {
|
|
688
|
+
container-type: inline-size; /* query it: @container (min-width: …) */
|
|
689
|
+
border: 1px solid var(--bd); border-radius: var(--r); padding: .9rem;
|
|
690
|
+
background: var(--el); box-shadow: var(--up2);
|
|
691
|
+
display: flex; flex-direction: column; /* so footers line up across a row */
|
|
692
|
+
gap: .85rem; /* spacing is the container's job */
|
|
693
|
+
}
|
|
694
|
+
.card > * { margin-block: 0 }
|
|
695
|
+
|
|
696
|
+
/* ---- layout utilities --------------------------------------
|
|
697
|
+
Names borrowed from Tailwind on purpose: if you know them there, they mean
|
|
698
|
+
the same thing here, so muscle memory carries over and nothing new has to
|
|
699
|
+
be learned. Layout and width only — there is no colour or type scale, and
|
|
700
|
+
no responsive variants, because those want a build step and this file does
|
|
701
|
+
not have one. The margins are flow-relative (ms/me, not
|
|
702
|
+
ml/mr), so a toolbar still works in Persian or Arabic. */
|
|
703
|
+
.flex { display: flex }
|
|
704
|
+
/* .grid is already above: auto-fit columns. Adding grid-cols-N pins the count. */
|
|
705
|
+
.flex-col { flex-direction: column }
|
|
706
|
+
.flex-wrap { flex-wrap: wrap }
|
|
707
|
+
.flex-1 { flex: 1; min-width: 0 } /* min-width, or a <pre> inside blows the row open */
|
|
708
|
+
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) }
|
|
709
|
+
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) }
|
|
710
|
+
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)) }
|
|
711
|
+
.items-start { align-items: start }
|
|
712
|
+
.items-center { align-items: center }
|
|
713
|
+
.items-end { align-items: end }
|
|
714
|
+
.justify-start { justify-content: start }
|
|
715
|
+
.justify-center { justify-content: center }
|
|
716
|
+
.justify-end { justify-content: end }
|
|
717
|
+
.justify-between { justify-content: space-between }
|
|
718
|
+
.gap-0 { gap: 0 }
|
|
719
|
+
.gap-1 { gap: .25rem }
|
|
720
|
+
.gap-2 { gap: .5rem }
|
|
721
|
+
.gap-3 { gap: .75rem }
|
|
722
|
+
.gap-4 { gap: 1rem }
|
|
723
|
+
.gap-6 { gap: 1.5rem }
|
|
724
|
+
.mx-auto { margin-inline: auto }
|
|
725
|
+
.ms-auto { margin-inline-start: auto }
|
|
726
|
+
.me-auto { margin-inline-end: auto }
|
|
727
|
+
.w-full { width: 100% }
|
|
728
|
+
/* max-inline-size, not max-width, so the scale still means "along the text" in
|
|
729
|
+
a vertical writing mode. The whole Tailwind scale, because a half-scale
|
|
730
|
+
where .max-w-2xl works and .max-w-3xl silently does not is worse than none.
|
|
731
|
+
These outrank the `body > *` measure (0,1,0 beats 0,0,1), so they retarget
|
|
732
|
+
a page container as readily as anything inside one. */
|
|
733
|
+
.max-w-xs { max-inline-size: 20rem }
|
|
734
|
+
.max-w-sm { max-inline-size: 24rem }
|
|
735
|
+
.max-w-md { max-inline-size: 28rem }
|
|
736
|
+
.max-w-lg { max-inline-size: 32rem }
|
|
737
|
+
.max-w-xl { max-inline-size: 36rem }
|
|
738
|
+
.max-w-2xl { max-inline-size: 42rem }
|
|
739
|
+
.max-w-3xl { max-inline-size: 48rem }
|
|
740
|
+
.max-w-4xl { max-inline-size: 56rem }
|
|
741
|
+
.max-w-5xl { max-inline-size: 64rem }
|
|
742
|
+
.max-w-6xl { max-inline-size: 72rem }
|
|
743
|
+
.max-w-7xl { max-inline-size: 80rem }
|
|
744
|
+
.max-w-full { max-inline-size: 100% }
|
|
745
|
+
.max-w-none { max-inline-size: none }
|
|
746
|
+
/* the measure the sheet already sets prose on, as a class you can reach for */
|
|
747
|
+
.max-w-prose { max-inline-size: var(--w) }
|
|
748
|
+
.hidden { display: none }
|
|
749
|
+
|
|
750
|
+
.badge {
|
|
751
|
+
border: 1px solid var(--bd); border-radius: calc(infinity * 1px); background: var(--face);
|
|
752
|
+
padding: .1em .6em; font: 700 var(--caps); color: var(--mut);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/* card anatomy: an optional header, footer, and full-bleed media */
|
|
756
|
+
.card > :is(header, footer) {
|
|
757
|
+
margin-inline: -.9rem; padding: .55rem .9rem; background: var(--face);
|
|
758
|
+
}
|
|
759
|
+
.card > header {
|
|
760
|
+
margin-top: -.9rem; font-weight: 700;
|
|
761
|
+
border-bottom: 1px solid var(--bd); border-radius: var(--r) var(--r) 0 0;
|
|
762
|
+
}
|
|
763
|
+
.card > footer {
|
|
764
|
+
margin-top: auto; margin-bottom: -.9rem; /* pinned to the bottom edge */
|
|
765
|
+
color: var(--mut); font-size: .85em;
|
|
766
|
+
border-top: 1px solid var(--bd); border-radius: 0 0 var(--r) var(--r);
|
|
767
|
+
}
|
|
768
|
+
.card > :is(img, video):first-child {
|
|
769
|
+
align-self: stretch; width: auto; max-width: none; margin: -.9rem -.9rem 0;
|
|
770
|
+
aspect-ratio: 16 / 9; object-fit: cover;
|
|
771
|
+
border: 0; border-radius: var(--r) var(--r) 0 0; box-shadow: none;
|
|
772
|
+
}
|
|
773
|
+
/* inset, not a border, so a tone never shifts the card geometry */
|
|
774
|
+
.card[data-tone] { box-shadow: var(--up2), inset 0 2px 0 var(--ac) }
|
|
775
|
+
/* a header paints over the card inset, so it carries the rule itself */
|
|
776
|
+
.card[data-tone] > header:first-child { box-shadow: inset 0 2px 0 var(--ac) }
|
|
777
|
+
/* a card that is itself a link or button reacts like one */
|
|
778
|
+
:is(a, button).card {
|
|
779
|
+
display: flex; width: 100%; /* keep the column from .card */
|
|
780
|
+
text-align: start; color: inherit;
|
|
781
|
+
text-decoration: none; font: inherit;
|
|
782
|
+
}
|
|
783
|
+
:is(a, button).card:hover:not(:disabled) {
|
|
784
|
+
border-color: color-mix(in srgb, var(--ac) 45%, var(--bd));
|
|
785
|
+
box-shadow: var(--up2), 0 12px 26px -14px oklch(from var(--sh) l c h / .4);
|
|
786
|
+
translate: 0 -2px;
|
|
787
|
+
}
|
|
788
|
+
:is(a, button).card:active:not(:disabled) { translate: 0 0 }
|
|
789
|
+
|
|
790
|
+
/* ---- details ------------------------------------------------ */
|
|
791
|
+
/* beats .card/.row's display without !important, which a layer would let
|
|
792
|
+
win over your own sheet; hidden=until-found must stay findable */
|
|
793
|
+
[hidden]:not([hidden=until-found]) { display: none }
|
|
794
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) { html { scroll-behavior: smooth } }
|
|
795
|
+
:target { scroll-margin-top: 4rem } /* clears the toolbar */
|
|
796
|
+
:is(h1,h2,h3,h4):target { background: color-mix(in srgb, var(--ac) 12%, transparent) }
|
|
797
|
+
a[target=_blank]::after { content: " ↗"; font-size: .85em; opacity: .6 }
|
|
798
|
+
a:has(> img), a:has(> code) { text-decoration: none }
|
|
799
|
+
sup, sub { line-height: 0; font-size: .75em } /* stop them stretching lines */
|
|
800
|
+
q { quotes: "“" "”" "‘" "’" }
|
|
801
|
+
dfn { font-style: normal; font-weight: 700 }
|
|
802
|
+
ins { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: var(--ac) }
|
|
803
|
+
del { color: var(--mut) }
|
|
804
|
+
address { font-style: normal; color: var(--mut) }
|
|
805
|
+
hgroup > * { margin: 0 }
|
|
806
|
+
hgroup > p { color: var(--mut) }
|
|
807
|
+
output { font-weight: 700 }
|
|
808
|
+
meter { width: 100%; height: .5rem }
|
|
809
|
+
optgroup { font-weight: 700; color: var(--mut) }
|
|
810
|
+
::file-selector-button { margin-inline-end: .8em }
|
|
811
|
+
::file-selector-button:hover { background: var(--face) }
|
|
812
|
+
input:user-invalid, textarea:user-invalid { border-color: light-dark(#c0392b, #ff6b6b) }
|
|
813
|
+
input:user-invalid + small { color: light-dark(#c0392b, #ff6b6b) }
|
|
814
|
+
summary:hover { color: var(--ac) }
|
|
815
|
+
/* thin scrollbars, macOS weight */
|
|
816
|
+
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--fg) 25%, transparent) transparent }
|
|
817
|
+
|
|
818
|
+
@page { margin: 2cm }
|
|
819
|
+
@media print {
|
|
820
|
+
:root { --bg: #fff; --el: #fff; --fg: #000; --bd: #999 }
|
|
821
|
+
body > nav, body > footer { display: none }
|
|
822
|
+
body > * { border: 0; max-width: none }
|
|
823
|
+
p, blockquote, li { orphans: 3; widows: 3 } /* no stranded single lines */
|
|
824
|
+
a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555 }
|
|
825
|
+
pre, table, figure, details, tr, img { break-inside: avoid }
|
|
826
|
+
h1, h2, h3, h4 { break-after: avoid }
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* WCAG 2.5.8: give touch pointers a real target */
|
|
830
|
+
@media (pointer: coarse) {
|
|
831
|
+
button, [type=submit], [type=button], [type=reset],
|
|
832
|
+
:where(a.primary, a[role=button]), select, summary,
|
|
833
|
+
input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file]) {
|
|
834
|
+
min-height: 44px;
|
|
835
|
+
}
|
|
836
|
+
input[type=checkbox], input[type=radio] { width: 1.4em; height: 1.4em }
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/* the OS is already inverting; stop images being inverted twice */
|
|
840
|
+
@media (inverted-colors: inverted) {
|
|
841
|
+
img, video, iframe { filter: invert(1) hue-rotate(180deg) }
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* a wider gamut can hold a cleaner accent than sRGB can */
|
|
845
|
+
@media (color-gamut: p3) {
|
|
846
|
+
@supports (color: oklch(60% .2 250)) {
|
|
847
|
+
:root { --ac: light-dark(oklch(52% .21 258), oklch(76% .15 258)) }
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* CJK text wants punctuation and Latin spacing handled */
|
|
852
|
+
:root { text-spacing-trim: trim-start; text-autospace: normal }
|
|
853
|
+
|
|
854
|
+
/* translucency is decorative; drop it when it is unwanted */
|
|
855
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
856
|
+
body > nav { background: var(--face); backdrop-filter: none }
|
|
857
|
+
dialog::backdrop { backdrop-filter: none }
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
@media (prefers-contrast: more) {
|
|
861
|
+
:root {
|
|
862
|
+
--bd: light-dark(#00000059, #ffffff6b);
|
|
863
|
+
--mut: light-dark(#45454a, #c6c6ce);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
@media (prefers-reduced-motion: no-preference) and (update: fast) {
|
|
868
|
+
a, button, input, select, textarea, summary, tr,
|
|
869
|
+
[type=submit], [type=button], [type=reset] {
|
|
870
|
+
transition: box-shadow .15s, background-color .15s, border-color .15s, translate .08s;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/* Windows High Contrast: the OS supplies the palette, so get out of the way.
|
|
877
|
+
A later layer, so `*` beats every shadow in the sheet on layer order alone. */
|
|
878
|
+
@layer fertig-a11y {
|
|
879
|
+
@media (forced-colors: active) {
|
|
880
|
+
* { box-shadow: none }
|
|
881
|
+
button, [type=submit], [type=button], [type=reset], input, select, textarea,
|
|
882
|
+
.card, .badge, details, dialog, [popover], fieldset, table {
|
|
883
|
+
border: 1px solid CanvasText;
|
|
884
|
+
}
|
|
885
|
+
[role=tab][aria-selected=true], button[aria-pressed=true] { border-color: Highlight }
|
|
886
|
+
}
|
|
887
|
+
}
|