clairity.css 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +66 -0
- data/CHANGELOG.md +423 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +373 -0
- data/README.md +170 -0
- data/Rakefile +12 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/clairity.css.gemspec +51 -0
- data/lib/assets/css/clairity/base.css +951 -0
- data/lib/assets/css/clairity/classes.css +264 -0
- data/lib/assets/css/clairity/components.css +946 -0
- data/lib/assets/css/clairity/cosmetic.css +157 -0
- data/lib/assets/css/clairity/functions.css +46 -0
- data/lib/assets/css/clairity/icons.css +44 -0
- data/lib/assets/css/clairity/normalize.css +239 -0
- data/lib/assets/css/clairity/palette.css +329 -0
- data/lib/assets/css/clairity/shims.css +20 -0
- data/lib/assets/css/clairity/states.css +756 -0
- data/lib/assets/css/clairity/utilities.css +68 -0
- data/lib/assets/css/clairity/variables.css +301 -0
- data/lib/assets/css/clairity.basic.css +28 -0
- data/lib/assets/css/clairity.classless.css +50 -0
- data/lib/assets/css/clairity.css +109 -0
- data/lib/assets/images/input-image.png +0 -0
- data/lib/assets/images/logo.png +0 -0
- data/lib/assets/images/profile-avatar.png +0 -0
- data/lib/assets/js/clairity.css.js +6 -0
- data/lib/assets/media/t-rex_roar.mp3 +0 -0
- data/lib/clairity.css/version.rb +5 -0
- data/lib/clairity.css.rb +10 -0
- data/sig/clairity.css.rbs +4 -0
- data/test/test_clairity_css.rb +17 -0
- data/test/test_helper.rb +5 -0
- metadata +128 -0
@@ -0,0 +1,756 @@
|
|
1
|
+
/******************************************************************************\
|
2
|
+
STATES - these are convenience styles that go beyond defining the base elements.
|
3
|
+
in general, these add richness to the UI/UX while still being minimal
|
4
|
+
and "classless", but not pseudo-classless.
|
5
|
+
|
6
|
+
regarding new selectors, :not() for a single element is well-supported
|
7
|
+
but not so for a list of selectors (until very recently). :has() is
|
8
|
+
only safari 15.4+ and chrome 105+, with firefox support on the way.
|
9
|
+
\******************************************************************************/
|
10
|
+
|
11
|
+
|
12
|
+
/* -----------------------------------------------------------------------------
|
13
|
+
// #root - global states - listed first so they can be overriden subsequently
|
14
|
+
// -------------------------------------------------------------------------- */
|
15
|
+
|
16
|
+
/* #:hover - default :hover border for form inputs and buttons */
|
17
|
+
:hover { border-color: var(--highlighted); } /* [0010] specificity */
|
18
|
+
|
19
|
+
/* #disabled #hover - must be >= specificity of equivablent non-disabled field*/
|
20
|
+
:is([disabled], :disabled):hover { /* [0020] specificity */
|
21
|
+
color: var(--inactive); /* for select options */
|
22
|
+
background-color: var(--bg-inactive); /* messes up unchecked radio */
|
23
|
+
border: var(--solid) var(--border); /* to overcome :hover */
|
24
|
+
cursor: not-allowed; /* TODO: shouldn't need this? */
|
25
|
+
--icon-color: unset; /* revert icon color to previous value */
|
26
|
+
}
|
27
|
+
|
28
|
+
/* #:focus - the focused pseudo-class, used by links, form fields, etc. */
|
29
|
+
:focus { }
|
30
|
+
/* #:focus-visible - applies to keyboard-focused buttons but not mouse-focused.
|
31
|
+
note safari only started supporting this with 15.4 - [0010] specificity */
|
32
|
+
:focus-visible {
|
33
|
+
outline: var(--dashed) var(--emphasized);
|
34
|
+
outline-offset: var(--thick);
|
35
|
+
}
|
36
|
+
|
37
|
+
/* #:active - actuated element, typically also has :focus, useful for keyboard
|
38
|
+
users and accessibility in general */
|
39
|
+
:active { } /* TODO: consider low-vision/keyboard users */
|
40
|
+
|
41
|
+
/* #:empty - has no children and no content other than certain whitespace */
|
42
|
+
:empty { }
|
43
|
+
|
44
|
+
|
45
|
+
/* -----------------------------------------------------------------------------
|
46
|
+
// #sectioning - block type elements meant to hold other content elements
|
47
|
+
// -------------------------------------------------------------------------- */
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
/* -----------------------------------------------------------------------------
|
52
|
+
// #headers - heading-related elements
|
53
|
+
// -------------------------------------------------------------------------- */
|
54
|
+
|
55
|
+
/*style header fragment no differently to headers- [0001] / [0011] specificity*/
|
56
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]),
|
57
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]):hover,
|
58
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]):visited {
|
59
|
+
color: unset;
|
60
|
+
font-weight: unset;
|
61
|
+
text-decoration: unset;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* header fragment indicator - [0001] specificity */
|
65
|
+
:where(h1, h2, h3, h4, h5, h6) :where([href^="#"]:hover)::after {
|
66
|
+
/*display: inline-flex;*/
|
67
|
+
font-size: var(--smaller);
|
68
|
+
color: var(--inactive);
|
69
|
+
content: "#";
|
70
|
+
margin-left: var(--s);
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
/* -----------------------------------------------------------------------------
|
75
|
+
// #links - for linking to other documents
|
76
|
+
// -------------------------------------------------------------------------- */
|
77
|
+
|
78
|
+
/* placeholder links shouldn't be styled like links until an href is added */
|
79
|
+
:where(a, a:hover):not([href]) { /* [0011]/[0021] specificity */
|
80
|
+
color: inherit;
|
81
|
+
text-decoration: none;
|
82
|
+
}
|
83
|
+
|
84
|
+
/* #mailto #tel #sms #file #external #bookmark #download - special link icons -
|
85
|
+
this uses the svg mask technique to place & color icons on hover; as such, it
|
86
|
+
must be in a separate pseudo-element so masking doesn't bleed out to text */
|
87
|
+
/* #icons - set --icon to an svg from icons.css, set color with --icon-color */
|
88
|
+
[href^="tel:"] { --icon: var(--i-phone); }
|
89
|
+
[href^="sms:"] { --icon: var(--i-message-2); }
|
90
|
+
[href^="file:"] { --icon: var(--i-file); }
|
91
|
+
[download] { --icon: var(--i-file-download); }
|
92
|
+
[href^="mailto:"] { --icon: var(--i-mail); }
|
93
|
+
[rel~="external"] { --icon: var(--i-external-link); }
|
94
|
+
[rel~="bookmark"] { --icon: var(--i-bookmark); }
|
95
|
+
|
96
|
+
/* chrome still needs prefixed -webkit-mask as of 20221001 */
|
97
|
+
:where([href^="tel:"], [href^="sms:"], [href^="file:"], [download])::before,
|
98
|
+
:where([href^="mailto:"], [rel~="external"], [rel~="bookmark"])::after {
|
99
|
+
content: ''; /* without content, no icon appears */
|
100
|
+
-webkit-mask: var(--icon) no-repeat center / var(--field-icon);
|
101
|
+
mask: var(--icon) no-repeat center / var(--field-icon);
|
102
|
+
background-color: var(--icon-color);
|
103
|
+
padding: var(--inline-padding);
|
104
|
+
}
|
105
|
+
|
106
|
+
:where([href^="mailto:"], [rel~="external"], [rel~="bookmark"])::after {
|
107
|
+
margin-left: var(--xs);
|
108
|
+
padding-top: var(--xs);
|
109
|
+
}
|
110
|
+
|
111
|
+
a:where([href^="mailto:"], [href^="tel:"], [href^="sms:"], [href^="file:"],
|
112
|
+
[rel~="external"], [rel~="bookmark"], [download]):hover {
|
113
|
+
--icon-color: var(--active);
|
114
|
+
}
|
115
|
+
|
116
|
+
/* give followed fragment links some room at the top - [0010] specificity */
|
117
|
+
[id] { scroll-margin-top: var(--l); }
|
118
|
+
|
119
|
+
/* #:any-link matches both :link and :visited for anchors with an href; safari
|
120
|
+
support is currently lagging, so don't use for now */
|
121
|
+
:any-link { }
|
122
|
+
/* #:local-link - targets same-document links - currently unspported */
|
123
|
+
/*:local-link { }*/
|
124
|
+
|
125
|
+
|
126
|
+
/* -----------------------------------------------------------------------------
|
127
|
+
// #lists - ordered and unordered lists of items
|
128
|
+
// -------------------------------------------------------------------------- */
|
129
|
+
/* #ul #ol - removes vertical gaps for lists within lists - uses :where() to get
|
130
|
+
[0001] specificity rather than [0002] */
|
131
|
+
:where(ul, ol) :is(ul, ol) { margin: 0; }
|
132
|
+
|
133
|
+
|
134
|
+
/* -----------------------------------------------------------------------------
|
135
|
+
// #tables - for tabular data
|
136
|
+
// -------------------------------------------------------------------------- */
|
137
|
+
|
138
|
+
/* makes the first column sticky - [0012] specificity */
|
139
|
+
:not(tfoot) > tr th:first-of-type {
|
140
|
+
position: sticky; /* works best w/ <table> display ≅ block */
|
141
|
+
left: 0;
|
142
|
+
background-color: var(--background); /* is transparent otherwise */
|
143
|
+
}
|
144
|
+
/* makes the table header row sticky - [0002] specificity */
|
145
|
+
thead th {
|
146
|
+
position: sticky;
|
147
|
+
top: 0;
|
148
|
+
z-index: 1; /* puts header row on top of tbody rows, including
|
149
|
+
header column */
|
150
|
+
background-color: var(--background); /* is transparent otherwise */
|
151
|
+
}
|
152
|
+
/* sticks top left box on top of both header columns/rows - [0012] specificity*/
|
153
|
+
thead th:first-child { z-index: 2; }
|
154
|
+
|
155
|
+
/* zebra-striping of table rows - [0013] specificity */
|
156
|
+
tbody tr:nth-child(even), tbody tr:nth-child(even) th {
|
157
|
+
background-color: var(--bg-contrast);
|
158
|
+
}
|
159
|
+
/* this is more robust because it excludes hidden rows, but only safari and
|
160
|
+
chrome support this syntax - [0023] specificity */
|
161
|
+
@supports selector(:nth-child(even of :not([hidden]))) {
|
162
|
+
tbody tr:where(:nth-child( even of :not([hidden]) )),
|
163
|
+
tbody tr:where(:nth-child( even of :not([hidden]) )) th {
|
164
|
+
background-color: var(--bg-contrast);
|
165
|
+
}}
|
166
|
+
|
167
|
+
/* hover effect for table rows */
|
168
|
+
tbody tr:hover, tbody tr:hover th {
|
169
|
+
background-color: var(--contrast);
|
170
|
+
}
|
171
|
+
/* adds sticky borders below thead and above tfoot */
|
172
|
+
thead tr:last-of-type th {box-shadow: inset 0 -2px var(--secondary, --neutral);}
|
173
|
+
tfoot :where(td, th) {box-shadow: inset 0 2px var(--secondary, --neutral);}
|
174
|
+
|
175
|
+
|
176
|
+
/* -----------------------------------------------------------------------------
|
177
|
+
// #forms - for submitting data to the server
|
178
|
+
// -------------------------------------------------------------------------- */
|
179
|
+
/* #form #fieldset #focus-within - highlight whole forms & fieldsets when one of
|
180
|
+
its contained elements is focused - [0010] specificity */
|
181
|
+
:where(form, fieldset):focus-within {
|
182
|
+
/*background-color: var(--bg-contrast);*/
|
183
|
+
}
|
184
|
+
/* #legend #fieldset #hover - remove :hover effect on legend & fieldset by
|
185
|
+
setting the same border color - [0010] specificity */
|
186
|
+
:where(legend, fieldset):hover {
|
187
|
+
border-color: var(--border);
|
188
|
+
}
|
189
|
+
|
190
|
+
/* #form #ul - since lists in forms typically list out form controls, remove
|
191
|
+
the bullet styling from them - [0002] specificity */
|
192
|
+
form ul { list-style: none; }
|
193
|
+
|
194
|
+
/* #form #section #p #div - subgrid is firefox 71+ & safari 16+ only 20221001 */
|
195
|
+
@supports (grid-template-columns: subgrid) {
|
196
|
+
/* :where() can be used here, since @supports is also a recent addition */
|
197
|
+
form :where(section, p, div) { /* [0001] specificity */
|
198
|
+
grid-column: full;
|
199
|
+
display: grid;
|
200
|
+
grid-template-columns: subgrid;
|
201
|
+
gap: inherit;
|
202
|
+
}}
|
203
|
+
/*form section :where(h1,h2,h3,h4,h5,h6) + p { display: block; }*/
|
204
|
+
|
205
|
+
/* chrome/edge doesn't support subgrid yet but does support :has() & :where() */
|
206
|
+
@supports selector(:has(*)) {
|
207
|
+
fieldset:where(:has(label, input, button, textarea, select, fieldset)) {
|
208
|
+
margin: 0;
|
209
|
+
display: grid;
|
210
|
+
grid-template-columns:[labels full-start] 3fr [elements] 7fr [full-end];
|
211
|
+
gap: var(--m);
|
212
|
+
}
|
213
|
+
fieldset:where(:has(p :is([type='checkbox'], [type='radio'])):not(:has(select,
|
214
|
+
textarea, [type="text"], [type="date"], [type="email"], [type="password"],
|
215
|
+
[type="datetime-local"], [type="search"]))) {
|
216
|
+
row-gap: 0;
|
217
|
+
}
|
218
|
+
/* since grid properties fall through p tags, assign margin-top to first child
|
219
|
+
elements to get a gap between paragraphs of different input types */
|
220
|
+
fieldset p:has([type='checkbox'], [type='radio']) + p:has([type='radio'],
|
221
|
+
[type='checkbox']) :is([type='radio'], [type='checkbox'],
|
222
|
+
label):first-of-type {
|
223
|
+
margin-top: var(--m);
|
224
|
+
}
|
225
|
+
/* chrome/edge doesn't support subgrid, so pierce the grid barrier instead */
|
226
|
+
fieldset :where(p:has(label, input, button, textarea, select, fieldset)) {
|
227
|
+
display: contents;
|
228
|
+
}
|
229
|
+
/* labels go on the labels gridline */
|
230
|
+
:where(fieldset :is(p, div)) label {
|
231
|
+
grid-column: labels;
|
232
|
+
}
|
233
|
+
/* lists that contain radios/checkboxes go on the elements gridline */
|
234
|
+
form:has(fieldset :where(select, textarea, [type="text"], [type="date"],
|
235
|
+
[type="email"], [type="password"], [type="datetime-local"],
|
236
|
+
[type="search"])) fieldset ul:has(li label :where([type='radio'],
|
237
|
+
[type='checkbox'])) {
|
238
|
+
grid-column: elements;
|
239
|
+
}
|
240
|
+
|
241
|
+
/* inputs on the elements gridline; pad right side away from fieldset line */
|
242
|
+
:where(fieldset) :where(p):has(input, button, textarea, select, fieldset)
|
243
|
+
:where(input:not(:is([type='checkbox'],[type='radio']), button,
|
244
|
+
textarea, select, fieldset)) {
|
245
|
+
grid-column: elements;
|
246
|
+
margin-right: var(--m);
|
247
|
+
}
|
248
|
+
/* for radios & checks, invert the placement of the input and label */
|
249
|
+
:where(fieldset) :where(p):has(label) :where([type='checkbox'],
|
250
|
+
[type='radio']):has(+ label) {
|
251
|
+
grid-column: labels;
|
252
|
+
align-self: center;
|
253
|
+
justify-self: end;
|
254
|
+
}
|
255
|
+
:where(fieldset p:has(label) :is([type='checkbox'],
|
256
|
+
[type='radio']):has(+ label)) + label {
|
257
|
+
grid-column: elements;
|
258
|
+
justify-self: start;
|
259
|
+
}
|
260
|
+
/* for paragraph tags that aren't form element containers, span the grid */
|
261
|
+
:where(fieldset) :where(p, div, h2, h3, h4, h5, h6):not(:has(label, input,
|
262
|
+
button, textarea, select, fieldset)) {
|
263
|
+
display: block;
|
264
|
+
grid-column: full;
|
265
|
+
}
|
266
|
+
}
|
267
|
+
/*form :where(fieldset, input, select, button, input[type="button"]) {
|
268
|
+
grid-column: elements;
|
269
|
+
}*/
|
270
|
+
|
271
|
+
fieldset > :last-child {
|
272
|
+
margin-bottom: 0;
|
273
|
+
}
|
274
|
+
|
275
|
+
|
276
|
+
/* -----------------------------------------------------------------------------
|
277
|
+
// #inputs - standard form input elements
|
278
|
+
// -------------------------------------------------------------------------- */
|
279
|
+
|
280
|
+
/* if [size] is set on an input, respect that choice - [0011] specificity */
|
281
|
+
input[size] {
|
282
|
+
width: fit-content;
|
283
|
+
min-width: unset;
|
284
|
+
max-width: unset;
|
285
|
+
}
|
286
|
+
|
287
|
+
/* #input #optgroup #option #textarea #select #read-only #focus - bg field icons
|
288
|
+
are Field system color by default, and --icon-color on focus/hover */
|
289
|
+
:where(input, optgroup, option, textarea, select):focus {
|
290
|
+
border-color: var(--highlighted);
|
291
|
+
--icon-color: var(--primary);
|
292
|
+
}
|
293
|
+
|
294
|
+
/* input images */
|
295
|
+
[type="date"], [type="time"], [type="datetime"], [type="datetime-local"],
|
296
|
+
[type="month"], [type="week"], [type="url"], [type="email"],
|
297
|
+
[type="number"], [type="tel"], [type="password"], [type="search"] {
|
298
|
+
padding-left: var(--height);
|
299
|
+
background-blend-mode: var(--icon-blend-mode);
|
300
|
+
}
|
301
|
+
[type="date"], [type="month"], [type="week"] {
|
302
|
+
background-image: var(--colorize-icon), var(--i-calendar); }
|
303
|
+
[type="datetime"], [type="datetime-local"], [type="time"] {
|
304
|
+
background-image: var(--colorize-icon), var(--i-calendar-time); }
|
305
|
+
[type="url"] { background-image: var(--colorize-icon), var(--i-browser); }
|
306
|
+
[type="email"] { background-image: var(--colorize-icon), var(--i-mail); }
|
307
|
+
[type="number"] { background-image: var(--colorize-icon), var(--i-numbers); }
|
308
|
+
[type="tel"] { background-image: var(--colorize-icon), var(--i-phone); }
|
309
|
+
[type="password"]{ background-image: var(--colorize-icon), var(--i-lock); }
|
310
|
+
[type="search"] { background-image: var(--colorize-icon), var(--i-search); }
|
311
|
+
|
312
|
+
/*@media (prefers-color-scheme: dark) {
|
313
|
+
[type="date"], [type="time"], [type="datetime"], [type="datetime-local"],
|
314
|
+
[type="week"], [type="month"], [type="url"], [type="email"],
|
315
|
+
[type="number"], [type="tel"], [type="password"], [type="search"] {
|
316
|
+
mask: var(--icon) no-repeat center left / var(--field-icon);
|
317
|
+
background-color: var(--icon-color);
|
318
|
+
mask-repeat: no-repeat;
|
319
|
+
background-image: unset;
|
320
|
+
}
|
321
|
+
[type="date"], [type="month"], [type="week"] { --icon: var(--i-calendar); }
|
322
|
+
[type="datetime"], [type="datetime-local"], [type="time"] {
|
323
|
+
--icon: var(--i-calendar-time); }
|
324
|
+
[type="url"] { --icon: var(--i-browser); }
|
325
|
+
[type="email"] { --icon: var(--i-mail); }
|
326
|
+
[type="number"] { --icon: var(--i-numbers); }
|
327
|
+
[type="tel"] { --icon: var(--i-phone); }
|
328
|
+
[type="password"]{ --icon: var(--i-lock); }
|
329
|
+
[type="search"] { --icon: var(--i-search); }
|
330
|
+
}*/
|
331
|
+
|
332
|
+
/* hide nav search's placeholder text if the background icon is present; opacity
|
333
|
+
is used here because other methods are disallowed on ::placeholder - [0001]*/
|
334
|
+
:where(nav) :where([type="search"])::placeholder { opacity: 0; }
|
335
|
+
|
336
|
+
:where(input, optgroup, option, textarea, select):required:is(:valid,:in-range){
|
337
|
+
border-color: var(--success-high, forestgreen);
|
338
|
+
}
|
339
|
+
:where(input, optgroup, option, textarea, select):required:is(:valid,
|
340
|
+
:in-range):hover, :where(input, optgroup, option, textarea,
|
341
|
+
select):required:is(:valid, :in-range):focus {
|
342
|
+
border-color: var(--success, limegreen);
|
343
|
+
}
|
344
|
+
/* remove borders on :read-only text and textarea fields */
|
345
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
346
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
347
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
348
|
+
[type="search"]):read-only:enabled {
|
349
|
+
border-style: none;
|
350
|
+
}
|
351
|
+
|
352
|
+
/*:read-only text fields shouldn't look editable on hover - [0040] specificity*/
|
353
|
+
:where(textarea, input:not([type="radio"],[type="checkbox"],[type="button"],
|
354
|
+
[type="reset"],[type="submit"],[type="image"])):read-only:enabled:hover {
|
355
|
+
border-style: none;
|
356
|
+
border-color: var(--emphasized);
|
357
|
+
}
|
358
|
+
/* deemphasize :read-only text fields on :focus - [0030] specificity*/
|
359
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
360
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
361
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
362
|
+
[type="search"]):read-only:focus {
|
363
|
+
border-style: none;
|
364
|
+
border-color: var(--deemphasized);
|
365
|
+
}
|
366
|
+
|
367
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
368
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
369
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
370
|
+
[type="search"]):hover {
|
371
|
+
--icon-color: var(--primary);
|
372
|
+
}
|
373
|
+
|
374
|
+
/* #:invalid - :invalid pseudo-class for when a field fails validation */
|
375
|
+
/*:where(input, optgroup, option, textarea, select):required:empty,*/
|
376
|
+
:where(input, optgroup, option, textarea, select):is(:invalid, :out-of-range) {
|
377
|
+
border-color: var(--warning-high, gold);
|
378
|
+
}
|
379
|
+
/* #invalid:hover - for when an :invalid field is also :hover'ed */
|
380
|
+
/*:where(input, optgroup, option, textarea, select):required:empty:hover,*/
|
381
|
+
:where(input, optgroup, option, textarea, select):is(:invalid,
|
382
|
+
:out-of-range):hover, :where(input, optgroup, option, textarea,
|
383
|
+
select):is(:invalid, :out-of-range):focus {
|
384
|
+
border-color: var(--warning, khaki);
|
385
|
+
}
|
386
|
+
|
387
|
+
/* HACK: these two rules undo validation borders on initial load because
|
388
|
+
browsers other than firefox don't support :user-invalid */
|
389
|
+
/*:is(input, optgroup, option, textarea, select):optional:not(:valid,
|
390
|
+
[type="button"], [type="reset"]) {
|
391
|
+
border-color: var(--border);
|
392
|
+
}
|
393
|
+
:is(input, optgroup, option, textarea, select):optional:not(:valid, :disabled,
|
394
|
+
[type="button"], [type="reset"]):hover {
|
395
|
+
border-color: var(--highlighted);
|
396
|
+
}
|
397
|
+
*/
|
398
|
+
/* #:user-invalid - experimental - firefox only for now */
|
399
|
+
:where(input, optgroup, option, textarea, select):user-invalid {
|
400
|
+
border-color: var(--error-high, firebrick);
|
401
|
+
}
|
402
|
+
:where(input, optgroup, option, textarea, select):user-invalid:hover,
|
403
|
+
:where(input, optgroup, option, textarea, select):user-invalid:focus {
|
404
|
+
border-color: var(--error, red);
|
405
|
+
}
|
406
|
+
|
407
|
+
|
408
|
+
/* -----------------------------------------------------------------------------
|
409
|
+
// #radio #checkbox - radio buttons and checkboxes
|
410
|
+
// -------------------------------------------------------------------------- */
|
411
|
+
|
412
|
+
/* indeterminate checkboxes - activated only via js - [0010] specificity */
|
413
|
+
:where([type="checkbox"]):indeterminate {
|
414
|
+
border-color: var(--warning);
|
415
|
+
background-image: var(--colorize-icon), var(--i-dash);
|
416
|
+
background-blend-mode: var(--icon-blend-mode);
|
417
|
+
background-position: center;
|
418
|
+
background-size: 1.1em;
|
419
|
+
box-shadow: unset;
|
420
|
+
}
|
421
|
+
:where([type="checkbox"]):indeterminate:required { /* [0020] specificity */
|
422
|
+
border-color: var(--error);
|
423
|
+
}
|
424
|
+
:where([type="checkbox"]):indeterminate:hover {
|
425
|
+
--icon-color: var(--300);
|
426
|
+
}
|
427
|
+
|
428
|
+
/* #radio #checkbox #label #disabled #checked - these 3 rules style the disabled
|
429
|
+
radio/checkbox and its subsequent label - [0010] specificity using :where();
|
430
|
+
don't want first rule to be [0001] as regular label rules would override it*/
|
431
|
+
:where([type="radio"], [type="checkbox"]):disabled + :where(label) {
|
432
|
+
color: var(--inactive);
|
433
|
+
cursor: not-allowed;
|
434
|
+
}
|
435
|
+
/* needs to be >= specificity of global :disabled:hover rule to override it */
|
436
|
+
:is([type="radio"], [type="checkbox"]):disabled { /* [0020] specificity */
|
437
|
+
background-color: var(--unchecked); /* removes "check" */
|
438
|
+
border: var(--solid) var(--deemphasized); /* grey border */
|
439
|
+
box-shadow: var(--inset) var(--unchecked); /* inset bg */
|
440
|
+
}
|
441
|
+
:is([type="radio"]:checked, [type="checkbox"]:checked):disabled {
|
442
|
+
background-color: var(--deactivated); /* greyed-out "check" */
|
443
|
+
}
|
444
|
+
|
445
|
+
/* if radios/checkboxes are inline, insert a gap between them for readability */
|
446
|
+
:where([type="radio"] + label, [type="checkbox"] + label) +
|
447
|
+
:is([type="radio"], [type="checkbox"]) { /* [0010] specificity */
|
448
|
+
margin-left: var(--m);
|
449
|
+
}
|
450
|
+
|
451
|
+
/* inputs are (somewhat unspecified as) "replaced elements" & therefore can't
|
452
|
+
have ::before or ::after pseudo-elements on itself; use on label instead */
|
453
|
+
/* mark required fields' labels; labels usually follow checkboxes & radios */
|
454
|
+
/* :has() is only safari 16+ and chrome 105+ for now */
|
455
|
+
@supports selector(:has(*)) {
|
456
|
+
label:has(+ :is(input, select):not([type="checkbox"], [type="radio"]):required)::after,
|
457
|
+
input:where([type="checkbox"], [type="radio"]):required + label::after {
|
458
|
+
content: " *";
|
459
|
+
}
|
460
|
+
/* mark as successful the fieldset & legend borders for valid radio buttons */
|
461
|
+
/* :has() is safari only for now */
|
462
|
+
|
463
|
+
fieldset:has([type="radio"]:required:checked),
|
464
|
+
fieldset:has([type="radio"]:required:checked) legend {
|
465
|
+
border-color: var(--success, forestgreen);
|
466
|
+
}
|
467
|
+
}
|
468
|
+
|
469
|
+
[type="checkbox"]:required:user-invalid + label {
|
470
|
+
color: var(--error, red);
|
471
|
+
}
|
472
|
+
|
473
|
+
|
474
|
+
/* -----------------------------------------------------------------------------
|
475
|
+
// specialized inputs - #date #time #datetime-local #month #week #datetime
|
476
|
+
// #email #password #search #file #image #url #tel #color
|
477
|
+
// #range #number #hidden
|
478
|
+
// -------------------------------------------------------------------------- */
|
479
|
+
|
480
|
+
/* search decorations - [0011] specificity */
|
481
|
+
[type="search"]::-webkit-search-cancel-button,
|
482
|
+
[type="search"]::-webkit-search-decoration {
|
483
|
+
appearance: none; /* needed? */
|
484
|
+
}
|
485
|
+
|
486
|
+
|
487
|
+
/* -----------------------------------------------------------------------------
|
488
|
+
// #buttons - <button>s and <input>s of type=button, reset, and submit
|
489
|
+
// -------------------------------------------------------------------------- */
|
490
|
+
|
491
|
+
/* #button #submit #hover - [0010] specificity */
|
492
|
+
:where(button, [type="button"], [type="submit"]):hover,
|
493
|
+
::file-selector-button:hover {
|
494
|
+
background-color: var(--bg-highlight);
|
495
|
+
border-color: var(--foreground);
|
496
|
+
}
|
497
|
+
/* #reset #hover */
|
498
|
+
:where([type="reset"]):hover { /* [0010] specificity */
|
499
|
+
background-color: var(--contrast); /* TODO: make grey in dark mode */
|
500
|
+
}
|
501
|
+
|
502
|
+
/* #disabled & disabled #hover button states - needs >= specificity of
|
503
|
+
non-disabled versions */
|
504
|
+
:is(button, button:hover, [type="button"], [type="submit"]):disabled,
|
505
|
+
[type="file"]:disabled::file-selector-button { /* [0020] specificity */
|
506
|
+
color: var(--inactive);
|
507
|
+
background-color: var(--bg-inactive);
|
508
|
+
border-color: var(--deemphasized);
|
509
|
+
cursor: not-allowed;
|
510
|
+
}
|
511
|
+
|
512
|
+
[type="file"]:disabled {
|
513
|
+
background-color: unset;
|
514
|
+
border: unset;
|
515
|
+
cursor: not-allowed;
|
516
|
+
}
|
517
|
+
|
518
|
+
[type="reset"]:disabled {
|
519
|
+
color: var(--inactive);
|
520
|
+
background-color: var(--background);
|
521
|
+
border-color: var(--deemphasized);
|
522
|
+
cursor: not-allowed;
|
523
|
+
}
|
524
|
+
|
525
|
+
/* highlights the :focus style */
|
526
|
+
:where(button, [type="button"], [type="reset"], [type="submit"],
|
527
|
+
[type="image"]):not(:disabled):active {
|
528
|
+
outline: var(--dashed) var(--bg-highlight);
|
529
|
+
outline-offset: var(--thick);
|
530
|
+
}
|
531
|
+
|
532
|
+
/* includes css from .tertiary.button - [00xx] specificity */
|
533
|
+
[role="alert"] button:empty {
|
534
|
+
color: currentColor; /* invert foreground/background */
|
535
|
+
background-color: unset; /* border is transparent */
|
536
|
+
padding-inline: var(--xxs);
|
537
|
+
float: right;
|
538
|
+
}
|
539
|
+
|
540
|
+
[role="alert"] button:empty::after {
|
541
|
+
content: '⨉';
|
542
|
+
font-weight: var(--boldest);
|
543
|
+
}
|
544
|
+
|
545
|
+
[role="alert"] button:empty:hover {
|
546
|
+
text-decoration: underline; /* TODO: make grey in dark mode */
|
547
|
+
border-color: var(--transparent);
|
548
|
+
}
|
549
|
+
|
550
|
+
/*form:has(button[aria-label="close" i]:empty) {
|
551
|
+
padding-inline: var(--xxs);
|
552
|
+
float: right;
|
553
|
+
}
|
554
|
+
*/
|
555
|
+
/* -----------------------------------------------------------------------------
|
556
|
+
// #selections - dropdown/multi-item single-/multiple-selection form controls
|
557
|
+
// -------------------------------------------------------------------------- */
|
558
|
+
|
559
|
+
/* #option #hover #disabled */
|
560
|
+
option:not(:disabled, [disabled]):hover { /* [0011] specificity */
|
561
|
+
color: var(--whitish);
|
562
|
+
background-color: var(--bg-highlight);
|
563
|
+
}
|
564
|
+
option:disabled, option:disabled:hover { /* [0011] / [0021] specificity */
|
565
|
+
border-color: var(--transparent);
|
566
|
+
background-color: unset;
|
567
|
+
}
|
568
|
+
|
569
|
+
/* #select #multiple #hover #active - dropdown selection form controls- [0011]*/
|
570
|
+
select:not(:disabled, [disabled]):hover, select:active { /* dropdown chevron */
|
571
|
+
background-image: url("data:image/svg+xml;utf8,<svg \
|
572
|
+
xmlns='http://www.w3.org/2000/svg' width='40' height='40'><path \
|
573
|
+
d='M0,10 20,30 40,10' fill='none' stroke='%23666' stroke-width='2'/></svg>");
|
574
|
+
}
|
575
|
+
/* multi-selects shouldn't have chevron - [0020] specificity */
|
576
|
+
[multiple]:hover, [multiple]:active, select[size]:hover, select[size]:active {
|
577
|
+
background-image: none;
|
578
|
+
}
|
579
|
+
/* #multiple #option #checked */
|
580
|
+
[multiple] option:disabled:checked {
|
581
|
+
background-color: var(--deactivated) linear-gradient(0deg,
|
582
|
+
var(--deactivated) 0%, var(--deactivated) 100%);
|
583
|
+
}
|
584
|
+
/* #multiple #focus #option #disabled #checked */
|
585
|
+
:where(select[size], [multiple]):focus option:not(:disabled):checked {
|
586
|
+
/* --checked may be invalid due to SelectedItem, so provide a fallback */
|
587
|
+
background-color: var(--checked, LinkText) linear-gradient(0deg,
|
588
|
+
var(--checked, LinkText) 0%, var(--checked, LinkText) 100%);
|
589
|
+
}
|
590
|
+
|
591
|
+
/* #label for taller #multiple #textarea should align top - firefox 106 still
|
592
|
+
doesn't properly support :has() with combinator selectors */
|
593
|
+
@supports selector(:has(*)) {
|
594
|
+
label:where(:has(+ [multiple],+ textarea,+ select[size])){/*[0001] specificity*/
|
595
|
+
vertical-align: top;
|
596
|
+
align-self: start;
|
597
|
+
}}
|
598
|
+
|
599
|
+
|
600
|
+
/* -----------------------------------------------------------------------------
|
601
|
+
// #outputs - informational output/display
|
602
|
+
// -------------------------------------------------------------------------- */
|
603
|
+
|
604
|
+
progress:hover { border-color: revert; }
|
605
|
+
|
606
|
+
/* specify theme colors for chrome - [0011] specificity */
|
607
|
+
:-moz-meter-optimum::-moz-meter-bar { background-color: var(--success);}
|
608
|
+
:-moz-meter-sub-optimum::-moz-meter-bar { background-color: var(--warning);}
|
609
|
+
:-moz-meter-sub-sub-optimum::-moz-meter-bar { background-color: var(--error); }
|
610
|
+
|
611
|
+
|
612
|
+
/* -----------------------------------------------------------------------------
|
613
|
+
// #block text elements - elements that contain text blocks
|
614
|
+
// -------------------------------------------------------------------------- */
|
615
|
+
|
616
|
+
blockquote::before { /* [0002] specificity is the least possible here */
|
617
|
+
content: open-quote;
|
618
|
+
color: var(--pale);
|
619
|
+
font-size: 5rem;
|
620
|
+
font-family: var(--serif);
|
621
|
+
left: var(--xs);
|
622
|
+
line-height: 1;
|
623
|
+
position: absolute;
|
624
|
+
top: 0;
|
625
|
+
z-index: -1;
|
626
|
+
}
|
627
|
+
blockquote > :first-child { /* [0011] specificity */
|
628
|
+
margin-top: 0;
|
629
|
+
text-indent: var(--m);
|
630
|
+
}
|
631
|
+
blockquote > :last-child { /* [0011] specificity */
|
632
|
+
margin-bottom: 0;
|
633
|
+
}
|
634
|
+
|
635
|
+
|
636
|
+
/* -----------------------------------------------------------------------------
|
637
|
+
// #inline elements - elements that occur principally within text blocks
|
638
|
+
// -------------------------------------------------------------------------- */
|
639
|
+
|
640
|
+
/* #q - inline quotations -note that you must add the closing smart quote as
|
641
|
+
<q>'s automatic quotes are added via pseudo elements */
|
642
|
+
q[cite]::after { content: "” (cf. " attr(cite) ") "; }
|
643
|
+
|
644
|
+
/* #kbd #hover - remove default :hover styles - [0011] specificity */
|
645
|
+
kbd:hover { border: var(--solid) var(--distinguished); }
|
646
|
+
|
647
|
+
/* #:dir, #:lang - linguistic pseudo-classes */
|
648
|
+
/*:dir { }*/ /* experimental */
|
649
|
+
/*:lang { }*/
|
650
|
+
|
651
|
+
|
652
|
+
/* -----------------------------------------------------------------------------
|
653
|
+
// #media / #embedded - elements that add multimedia to a page
|
654
|
+
// -------------------------------------------------------------------------- */
|
655
|
+
|
656
|
+
/* #figure */
|
657
|
+
figure:hover { border-color: var(--border); }
|
658
|
+
/* #figure #headings - #h1 #h2 #h3 #h4 #h5 #h6 ~*/
|
659
|
+
figure > :where(h1,h2,h3,h4,h5,h6):first-child { margin-top: 0; }
|
660
|
+
|
661
|
+
/* #blockquote inside #figure - safari only for now - :where is older than :has
|
662
|
+
so it can be used here to reduce specificity - [0001] specificity */
|
663
|
+
figure:where(:has(blockquote)) {
|
664
|
+
background-color: revert;
|
665
|
+
}
|
666
|
+
/* fix doubled margin of a blockquote in a figure - [0002] specificity */
|
667
|
+
figure > blockquote {
|
668
|
+
text-align: start;
|
669
|
+
margin: var(--ms);
|
670
|
+
padding: var(--ms);
|
671
|
+
max-width: calc(var(--line-length) - (var(--ms) * 2));
|
672
|
+
}
|
673
|
+
|
674
|
+
/* #svg - [0001] specificity */
|
675
|
+
svg:not(:where(:root)) {
|
676
|
+
width: var(--m);
|
677
|
+
/*max-width: 100%;*/
|
678
|
+
overflow: hidden;
|
679
|
+
}
|
680
|
+
|
681
|
+
/* #audio - audio w/p controls shouldn't take up space - [0011] specificity */
|
682
|
+
audio:not([controls]) {
|
683
|
+
display: none;
|
684
|
+
height: 0;
|
685
|
+
}
|
686
|
+
|
687
|
+
/* resource state pseudo-classes */
|
688
|
+
/*:playing { }*/
|
689
|
+
/*:paused { }*/
|
690
|
+
/*:fullscreen { }*/
|
691
|
+
/*:picture-in-picture { }*/
|
692
|
+
/* time-dimensional pseudo-classes */
|
693
|
+
/*:current { }*/ /* experimental */
|
694
|
+
/*:past { }*/ /* experimental */
|
695
|
+
/*:future { }*/ /* experimental */
|
696
|
+
|
697
|
+
|
698
|
+
/* -----------------------------------------------------------------------------
|
699
|
+
// #interactive - built-in interactive components
|
700
|
+
// -------------------------------------------------------------------------- */
|
701
|
+
|
702
|
+
/* #dialog - built-in modal overlay - comes with 'Esc' support with no js! */
|
703
|
+
/* wrap long dialogs in a container to scroll the body content sans button */
|
704
|
+
dialog > :where(div, section, article) {
|
705
|
+
overflow-y: auto; /* scrolls as necessary w/ height set */
|
706
|
+
max-height: calc(80vh * 0.8); /* container queries needed! */
|
707
|
+
}
|
708
|
+
|
709
|
+
dialog button {
|
710
|
+
margin-top: var(--ms);
|
711
|
+
}
|
712
|
+
|
713
|
+
dialog :where(h1, h2, h3, h4, h5, h6) {
|
714
|
+
border-bottom: var(--solid) var(--border);
|
715
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
716
|
+
color: var(--distinct); /* differentiate dialog headings */
|
717
|
+
font-size: var(--large); /* dialogs typically only need 1 heading
|
718
|
+
level, so make it all the same size */
|
719
|
+
margin: 0 0 0 calc(-1 * var(--m)); /* negative left margin
|
720
|
+
lines up heading with paragraph text */
|
721
|
+
padding: 0 var(--m) var(--m); /* top padding provided by dialog
|
722
|
+
padding so remove it here */
|
723
|
+
}
|
724
|
+
|
725
|
+
/* #details - an accordion-like widget for question & answer style layouts */
|
726
|
+
/* #summary - the 'question' sub-element of details */
|
727
|
+
details:where([open]) summary {
|
728
|
+
border-bottom: var(--solid) var(--border);
|
729
|
+
}
|
730
|
+
|
731
|
+
details + details {
|
732
|
+
border-top: 0;
|
733
|
+
border-radius: 0;
|
734
|
+
}
|
735
|
+
|
736
|
+
details:first-of-type {
|
737
|
+
border-top-left-radius: var(--radius);
|
738
|
+
border-top-right-radius: var(--radius);
|
739
|
+
}
|
740
|
+
|
741
|
+
details:last-of-type {
|
742
|
+
border-bottom-left-radius: var(--radius);
|
743
|
+
border-bottom-right-radius: var(--radius);
|
744
|
+
}
|
745
|
+
|
746
|
+
|
747
|
+
/* -----------------------------------------------------------------------------
|
748
|
+
// #breaks - spacing elements
|
749
|
+
// -------------------------------------------------------------------------- */
|
750
|
+
|
751
|
+
|
752
|
+
/* -----------------------------------------------------------------------------
|
753
|
+
// #@page - fixed page-oriented class, usually for print layouts
|
754
|
+
// -------------------------------------------------------------------------- */
|
755
|
+
/* @page pseudo-classes */
|
756
|
+
/* @page :first, @page :left, @page :right, @page :blank { } */
|