actual-css 0.3.1 → 0.4.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 +67 -3
- package/README.md +35 -30
- package/dist/actual.css +73 -4946
- package/dist/actual.full.css +7090 -0
- package/dist/actual.full.js +1 -0
- package/dist/actual.full.min.css +1 -0
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/cascade-layer.md +34 -13
- package/docs/design-notes/column-layout.md +677 -0
- package/docs/design-notes/forced-colors.md +3 -0
- package/docs/design-notes/grid.md +612 -0
- package/docs/design-notes/naming.md +15 -0
- package/docs/design-notes/widget-primitives.md +1 -0
- package/docs/navigation.json +5 -1
- package/docs/pages/components/alert.md +16 -14
- package/docs/pages/components/aura.md +1 -1
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +7 -7
- package/docs/pages/components/button.md +16 -16
- package/docs/pages/components/card.md +13 -9
- package/docs/pages/components/chat.md +8 -8
- package/docs/pages/components/dialog.md +7 -7
- package/docs/pages/components/drawer.md +3 -3
- package/docs/pages/components/fab.md +7 -7
- package/docs/pages/components/navbar.md +9 -8
- package/docs/pages/components/pagination.md +4 -3
- package/docs/pages/components/skeleton.md +4 -4
- package/docs/pages/components/spinner.md +5 -5
- package/docs/pages/components/table.md +30 -5
- package/docs/pages/enhancements/flyout.md +13 -13
- package/docs/pages/enhancements/overview.md +1 -1
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/status-bar.md +5 -1
- package/docs/pages/enhancements/tabs.md +6 -6
- package/docs/pages/enhancements/tooltip.md +1 -1
- package/docs/pages/examples/overview.md +39 -22
- package/docs/pages/forms/choice-cards.md +11 -6
- package/docs/pages/forms/controls.md +1 -1
- package/docs/pages/forms/detached-actions.md +1 -1
- package/docs/pages/forms/floating-field.md +10 -5
- package/docs/pages/forms/form-actions.md +1 -1
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/joined-controls.md +4 -4
- package/docs/pages/forms/labels.md +6 -6
- package/docs/pages/forms/otp.md +1 -1
- package/docs/pages/forms/overview.md +19 -19
- package/docs/pages/forms/password.md +1 -1
- package/docs/pages/forms/range.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +41 -45
- package/docs/pages/forms/sticky-actions.md +3 -3
- package/docs/pages/forms/switch.md +110 -2
- package/docs/pages/forms/validation.md +9 -9
- package/docs/pages/foundations/philosophy.md +1 -1
- package/docs/pages/foundations/theming.md +17 -2
- package/docs/pages/foundations/tokens.md +22 -10
- package/docs/pages/foundations/typography.md +50 -20
- package/docs/pages/guides/bootstrap.md +11 -11
- package/docs/pages/guides/integrating-actual-css.md +186 -0
- package/docs/pages/guides/introduction.md +23 -26
- package/docs/pages/guides/modular-import.md +173 -0
- package/docs/pages/guides/progressive-enhancement.md +81 -78
- package/docs/pages/guides/tailwind.md +6 -8
- package/docs/pages/layout/app-shell.md +1 -1
- package/docs/pages/layout/center.md +1 -1
- package/docs/pages/layout/choosing.md +39 -0
- package/docs/pages/layout/column-layout.md +206 -0
- package/docs/pages/layout/frame.md +6 -1
- package/docs/pages/layout/grid.md +100 -102
- package/docs/pages/layout/list.md +1 -1
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +15 -2
- package/docs/pages/layout/scroll-snap.md +2 -2
- package/docs/pages/layout/scroll-target.md +45 -0
- package/docs/pages/layout/sidebar-layout.md +20 -19
- package/docs/pages/layout/stack.md +53 -1
- package/docs/pages/layout/switcher.md +10 -26
- package/docs/pages/layout/topbar.md +2 -2
- package/docs/pages/patterns/actions.md +1 -1
- package/docs/pages/patterns/code-block.md +1 -1
- package/docs/pages/patterns/exclusive-accordion.md +1 -1
- package/docs/pages/patterns/hierarchical-nav.md +1 -1
- package/docs/pages/patterns/icon-input.md +1 -1
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/patterns/navigation-list.md +1 -1
- package/docs/pages/patterns/overview.md +1 -1
- package/docs/pages/patterns/structured-lists.md +1 -1
- package/docs/pages/utilities/accessibility.md +3 -3
- package/docs/pages/utilities/flex.md +15 -15
- package/docs/pages/utilities/links.md +4 -4
- package/docs/pages/utilities/list.md +8 -8
- package/docs/pages/utilities/overflow.md +8 -8
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/shape.md +3 -3
- package/docs/pages/utilities/sizing-wrapping.md +23 -16
- package/docs/pages/utilities/spacing.md +20 -20
- package/docs/pages/utilities/text.md +10 -10
- package/llms.txt +46 -4
- package/package.json +42 -13
- package/scripts/reserved-classes.json +261 -0
- package/src/cli/actual-css.js +133 -0
- package/src/css/actual.css +1 -31
- package/src/css/actual.full.css +11 -2
- package/src/css/components/alert.css +17 -4
- package/src/css/components/badge.css +18 -6
- package/src/css/components/breadcrumb.css +1 -1
- package/src/css/components/button.css +19 -1
- package/src/css/components/card.css +27 -2
- package/src/css/components/drawer.css +27 -2
- package/src/css/components/flyout.css +5 -0
- package/src/css/components/index.css +32 -27
- package/src/css/components/join.css +8 -1
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +11 -1
- package/src/css/components/meter.css +4 -1
- package/src/css/components/modal.css +5 -0
- package/src/css/components/pagination.css +11 -0
- package/src/css/components/spinner.css +5 -0
- package/src/css/components/status-bar.css +5 -0
- package/src/css/components/surface.css +5 -0
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +5 -0
- package/src/css/core/index.css +9 -0
- package/src/css/core/print.css +18 -0
- package/src/css/{theme.css → core/theme.css} +20 -10
- package/src/css/{tokens.css → core/tokens.css} +15 -2
- package/src/css/{variants.css → core/variants.css} +3 -2
- package/src/css/effects/index.css +2 -0
- package/src/css/forms/base.css +8 -0
- package/src/css/forms/choice-card.css +5 -4
- package/src/css/forms/choice.css +49 -37
- package/src/css/forms/control.css +4 -1
- package/src/css/forms/custom-select.css +2 -0
- package/src/css/{optional → forms}/floating-field.css +25 -7
- package/src/css/forms/index.css +10 -13
- package/src/css/forms/native.css +15 -2
- package/src/css/{optional → forms}/otp.css +20 -16
- package/src/css/forms/switch.css +31 -29
- package/src/css/layout/app-shell.css +6 -0
- package/src/css/layout/center.css +13 -0
- package/src/css/layout/cluster.css +21 -0
- package/src/css/layout/column-layout.css +144 -0
- package/src/css/layout/container-query.css +13 -0
- package/src/css/layout/frame.css +19 -0
- package/src/css/layout/grid.css +141 -0
- package/src/css/layout/index.css +42 -0
- package/src/css/layout/measure.css +9 -0
- package/src/css/layout/media.css +10 -0
- package/src/css/layout/scroll-target.css +15 -0
- package/src/css/layout/scroller.css +41 -0
- package/src/css/layout/sidebar-layout.css +31 -0
- package/src/css/layout/stack.css +21 -0
- package/src/css/layout/switcher.css +23 -0
- package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
- package/src/css/typography/index.css +5 -0
- package/src/css/typography/lead.css +7 -0
- package/src/css/{components → typography}/overline.css +2 -2
- package/src/css/{prose.css → typography/prose.css} +53 -38
- package/src/css/{utilities.css → utilities/base.css} +2 -0
- package/src/css/utilities/index.css +3 -0
- package/src/js/escape.js +36 -0
- package/src/js/flyout.js +22 -12
- package/src/js/full.js +26 -0
- package/src/js/index.js +5 -21
- package/src/js/surface.js +1 -31
- package/src/js/tooltip.js +46 -18
- package/src/js/validation.js +1 -1
- package/src/tooling/css-bundle.js +308 -0
- package/dist/actual-themes.min.css +0 -1
- package/dist/optional.css +0 -1048
- package/dist/optional.min.css +0 -1
- package/docs/pages/guides/optional-components.md +0 -33
- package/src/css/grid.css +0 -109
- package/src/css/layout.css +0 -123
- package/src/css/optional/index.css +0 -10
- package/src/css/optional/scroller.css +0 -63
- package/src/css/print.css +0 -47
- package/src/css/themes/corporate.css +0 -60
- package/src/css/themes/cyberpunk.css +0 -60
- package/src/css/themes/dim.css +0 -33
- package/src/css/themes/edge.css +0 -117
- package/src/css/themes/forest.css +0 -65
- package/src/css/themes/gradient.css +0 -97
- package/src/css/themes/index.css +0 -47
- package/src/css/themes/indigo.css +0 -33
- package/src/css/themes/lavender.css +0 -66
- package/src/css/themes/mono.css +0 -60
- package/src/css/themes/neon.css +0 -66
- package/src/css/themes/ocean.css +0 -60
- package/src/css/themes/petrol.css +0 -64
- package/src/css/themes/spruce.css +0 -64
- package/src/css/themes/square.css +0 -65
- package/src/css/themes/sunset.css +0 -66
- /package/src/css/{optional → components}/chat.css +0 -0
- /package/src/css/{optional → components}/fab.css +0 -0
- /package/src/css/{base.css → core/base.css} +0 -0
- /package/src/css/{focus.css → core/focus.css} +0 -0
- /package/src/css/{intents.css → core/intents.css} +0 -0
- /package/src/css/{reset.css → core/reset.css} +0 -0
- /package/src/css/{optional → effects}/aura.css +0 -0
- /package/src/css/{optional → layout}/scroll-snap.css +0 -0
- /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
- /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
package/dist/actual.css
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* Core — the minimal Actual language. Every module depends on this order. */
|
|
1
2
|
/* Mild reset: useful defaults without classless magic. */
|
|
2
3
|
*,
|
|
3
4
|
*::before,
|
|
@@ -130,13 +131,21 @@ select {
|
|
|
130
131
|
|
|
131
132
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
132
133
|
.sm/.lg (variants.css) switch --control-size; --control-font-size stays
|
|
133
|
-
at its baseline because density does not change typography.
|
|
134
|
+
at its baseline because density does not change typography.
|
|
135
|
+
|
|
136
|
+
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
137
|
+
they sit on a line of text, so they follow --control-font-size through
|
|
138
|
+
--choice-control-size instead of --control-size. Being an em value in
|
|
139
|
+
:root is deliberate — an unregistered custom property resolves its units
|
|
140
|
+
at the element that consumes it, so one declaration tracks whatever font
|
|
141
|
+
size the choice actually renders at. */
|
|
134
142
|
--control-size-sm: 2rem;
|
|
135
143
|
--control-size-md: 2.375rem;
|
|
136
144
|
--control-size-lg: 2.75rem;
|
|
137
145
|
--control-size: var(--control-size-md);
|
|
138
146
|
--control-pad-x: 1em;
|
|
139
147
|
--control-font-size: var(--font-size-md);
|
|
148
|
+
--choice-control-size: 1.125em;
|
|
140
149
|
--disabled-opacity: 0.65;
|
|
141
150
|
}
|
|
142
151
|
|
|
@@ -165,6 +174,10 @@ select {
|
|
|
165
174
|
--soft-bg-mix: 88%;
|
|
166
175
|
--soft-border-mix: 65%;
|
|
167
176
|
--soft-hover-alpha: 12%;
|
|
177
|
+
/* Soft foreground: the share of raw intent in soft text. 100% keeps the
|
|
178
|
+
pure intent color; lower values rebate it toward --text, which vivid or
|
|
179
|
+
light intent palettes need to stay legible on their own soft surface. */
|
|
180
|
+
--soft-fg-mix: 100%;
|
|
168
181
|
|
|
169
182
|
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
170
183
|
--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
@@ -189,7 +202,8 @@ select {
|
|
|
189
202
|
}
|
|
190
203
|
|
|
191
204
|
@media (forced-colors: active) {
|
|
192
|
-
:root
|
|
205
|
+
:root,
|
|
206
|
+
[data-theme] {
|
|
193
207
|
--state-selected: Highlight;
|
|
194
208
|
--state-selected-fg: HighlightText;
|
|
195
209
|
--state-disabled: GrayText;
|
|
@@ -331,6 +345,16 @@ select {
|
|
|
331
345
|
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
332
346
|
}
|
|
333
347
|
|
|
348
|
+
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
349
|
+
restyled by a component class still follow the theme accent. Declared on
|
|
350
|
+
every island: accent-color inherits as a computed value, so islands must
|
|
351
|
+
recompute it from their own --primary (same resolution rule as above).
|
|
352
|
+
Unsupported browsers ignore it; forced-colors keeps browser control. */
|
|
353
|
+
:root,
|
|
354
|
+
[data-theme] {
|
|
355
|
+
accent-color: var(--primary);
|
|
356
|
+
}
|
|
357
|
+
|
|
334
358
|
/* Tone subsystem — fixed-backdrop foreground for components placed over media
|
|
335
359
|
or other surfaces outside the theme token stack. Use data-theme islands for
|
|
336
360
|
full dark/light subtree inversion. */
|
|
@@ -345,13 +369,13 @@ select {
|
|
|
345
369
|
/* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
|
|
346
370
|
is also supported, so it covers the color-mix-without-light-dark browser
|
|
347
371
|
tier on its own; the light-dark block below refines it further with the
|
|
348
|
-
light/dark alpha split once both features are available.
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
light/dark alpha split once both features are available. Declared on every
|
|
373
|
+
[data-theme] island (not just the built-in light/dark pair) so custom
|
|
374
|
+
islands recolor the ring for free; a preset that re-declares --focus-ring
|
|
375
|
+
later in source order intentionally pins its own ring instead. */
|
|
351
376
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
352
377
|
:root,
|
|
353
|
-
[data-theme
|
|
354
|
-
[data-theme="dark"] {
|
|
378
|
+
[data-theme] {
|
|
355
379
|
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
356
380
|
}
|
|
357
381
|
}
|
|
@@ -400,7 +424,7 @@ select {
|
|
|
400
424
|
|
|
401
425
|
@supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
|
|
402
426
|
:root,
|
|
403
|
-
[data-theme
|
|
427
|
+
[data-theme] {
|
|
404
428
|
--focus-ring: light-dark(
|
|
405
429
|
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
406
430
|
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
@@ -410,8 +434,7 @@ select {
|
|
|
410
434
|
|
|
411
435
|
@media (forced-colors: active) {
|
|
412
436
|
:root,
|
|
413
|
-
[data-theme
|
|
414
|
-
[data-theme="dark"] {
|
|
437
|
+
[data-theme] {
|
|
415
438
|
--focus-outline-color: Highlight;
|
|
416
439
|
--focus-ring-shadow: none;
|
|
417
440
|
|
|
@@ -421,8 +444,9 @@ select {
|
|
|
421
444
|
|
|
422
445
|
@media (prefers-contrast: more) {
|
|
423
446
|
:root,
|
|
424
|
-
|
|
425
|
-
|
|
447
|
+
/* The repeated attribute intentionally outranks single-attribute preset
|
|
448
|
+
palettes, including a preset stylesheet loaded after the core. */
|
|
449
|
+
[data-theme][data-theme] {
|
|
426
450
|
--border-width: 2px;
|
|
427
451
|
--focus-outline-offset: 3px;
|
|
428
452
|
--focus-ring-width: 4px;
|
|
@@ -530,441 +554,6 @@ small {
|
|
|
530
554
|
--intent-fg: var(--neutral-fg);
|
|
531
555
|
}
|
|
532
556
|
|
|
533
|
-
/* Prose before layout: layout primitives (.stack, .cluster) reset child
|
|
534
|
-
margins and must win over .prose's flow margins at equal specificity —
|
|
535
|
-
source order is the tie-breaker, so prose loads first. */
|
|
536
|
-
/*
|
|
537
|
-
* Prose is the opt-in rich text scope for authored content.
|
|
538
|
-
*
|
|
539
|
-
* Global element styles stay mild so app UI is not shaped accidentally. Page
|
|
540
|
-
* layout, forms, cards, navigation, and app tables remain outside this scope.
|
|
541
|
-
*
|
|
542
|
-
* Public hooks:
|
|
543
|
-
* --prose-size
|
|
544
|
-
* --prose-line-height
|
|
545
|
-
* --prose-measure
|
|
546
|
-
* --prose-flow
|
|
547
|
-
* --prose-heading-line-height
|
|
548
|
-
* --prose-h1
|
|
549
|
-
* --prose-h2
|
|
550
|
-
* --prose-h3
|
|
551
|
-
* --prose-h4
|
|
552
|
-
*/
|
|
553
|
-
|
|
554
|
-
.prose {
|
|
555
|
-
/* Prose scale */
|
|
556
|
-
--prose-size: 1rem;
|
|
557
|
-
--prose-line-height: 1.7;
|
|
558
|
-
--prose-measure: 70ch;
|
|
559
|
-
--prose-flow: 1em;
|
|
560
|
-
--prose-heading-line-height: 1.2;
|
|
561
|
-
--prose-h1: 2rem;
|
|
562
|
-
--prose-h2: 1.5rem;
|
|
563
|
-
--prose-h3: 1.25rem;
|
|
564
|
-
--prose-h4: 1.125rem;
|
|
565
|
-
|
|
566
|
-
max-inline-size: var(--prose-measure);
|
|
567
|
-
color: var(--text);
|
|
568
|
-
font-size: var(--prose-size);
|
|
569
|
-
line-height: var(--prose-line-height);
|
|
570
|
-
overflow-wrap: break-word;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
.prose :where(p, ul, ol, dl, blockquote, pre, table, figure) {
|
|
574
|
-
margin-block: var(--prose-flow) 0;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
.prose :where(h1, h2, h3, h4, h5, h6) {
|
|
578
|
-
margin-block: 1.5em 0;
|
|
579
|
-
color: var(--text);
|
|
580
|
-
font-weight: var(--font-weight-strong);
|
|
581
|
-
line-height: var(--prose-heading-line-height);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
.prose :where(p, li, blockquote) {
|
|
585
|
-
text-wrap: pretty;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
.prose > :first-child {
|
|
589
|
-
margin-block-start: 0;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
.prose h1 {
|
|
593
|
-
font-size: var(--prose-h1);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
.prose h2 {
|
|
597
|
-
font-size: var(--prose-h2);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
.prose h3 {
|
|
601
|
-
font-size: var(--prose-h3);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
.prose h4 {
|
|
605
|
-
font-size: var(--prose-h4);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
.prose a {
|
|
609
|
-
color: var(--primary);
|
|
610
|
-
text-decoration-line: underline;
|
|
611
|
-
text-decoration-thickness: 0.08em;
|
|
612
|
-
text-underline-offset: 0.18em;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.prose a:hover {
|
|
616
|
-
text-decoration-thickness: 0.12em;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
620
|
-
.prose a:hover {
|
|
621
|
-
color: color-mix(in oklch, var(--primary), var(--text) 15%);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
.prose :where(ul, ol) {
|
|
626
|
-
padding-inline-start: 1.5em;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
.prose li + li {
|
|
630
|
-
margin-block-start: 0.35em;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
.prose code {
|
|
634
|
-
padding: 0.15em 0.3em;
|
|
635
|
-
border-radius: var(--radius-sm);
|
|
636
|
-
background: var(--surface-subtle);
|
|
637
|
-
color: var(--text);
|
|
638
|
-
font-size: 0.875em;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
|
|
642
|
-
.prose kbd {
|
|
643
|
-
display: inline-flex;
|
|
644
|
-
align-items: center;
|
|
645
|
-
justify-content: center;
|
|
646
|
-
min-inline-size: 1.75em;
|
|
647
|
-
min-block-size: 1.6em;
|
|
648
|
-
padding-inline: 0.45em;
|
|
649
|
-
border: var(--border-width) solid var(--border);
|
|
650
|
-
border-block-end-width: calc(var(--border-width) * 2);
|
|
651
|
-
border-radius: var(--radius-sm);
|
|
652
|
-
background: var(--surface-raised);
|
|
653
|
-
color: var(--text);
|
|
654
|
-
font-family: var(--font-mono);
|
|
655
|
-
font-size: 0.875em;
|
|
656
|
-
font-weight: var(--font-weight-medium);
|
|
657
|
-
line-height: 1;
|
|
658
|
-
white-space: nowrap;
|
|
659
|
-
vertical-align: 0.08em;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
.prose pre {
|
|
663
|
-
overflow-x: auto;
|
|
664
|
-
padding: 1em;
|
|
665
|
-
border: var(--border-width) solid var(--border);
|
|
666
|
-
border-radius: var(--radius);
|
|
667
|
-
background: var(--surface-subtle);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
.prose pre code {
|
|
671
|
-
padding: 0;
|
|
672
|
-
background: transparent;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
.prose blockquote {
|
|
676
|
-
padding-inline-start: 1em;
|
|
677
|
-
border-inline-start: var(--border-width) solid var(--border);
|
|
678
|
-
color: var(--text-muted);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
.prose figure {
|
|
682
|
-
margin-inline: 0;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
.prose figcaption {
|
|
686
|
-
margin-block-start: 0.5em;
|
|
687
|
-
color: var(--text-muted);
|
|
688
|
-
font-size: 0.875em;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
.prose table {
|
|
692
|
-
inline-size: 100%;
|
|
693
|
-
border-collapse: collapse;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
.prose th,
|
|
697
|
-
.prose td {
|
|
698
|
-
padding: 0.5em;
|
|
699
|
-
border-block-end: var(--border-width) solid var(--border);
|
|
700
|
-
text-align: start;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
|
|
704
|
-
Non-fluid and always available — use .text-lead from the optional fluid
|
|
705
|
-
module for marketing/display surfaces. */
|
|
706
|
-
.lead {
|
|
707
|
-
font-size: 1.125em;
|
|
708
|
-
line-height: var(--line-height-relaxed);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/* Readable content measure. Composes with .center for centering or with
|
|
712
|
-
margin-inline: auto for a one-off centered block. */
|
|
713
|
-
.measure {
|
|
714
|
-
max-inline-size: var(--prose-measure, 70ch);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.prose.sm {
|
|
718
|
-
--prose-size: 0.9375rem;
|
|
719
|
-
--prose-h1: 1.75rem;
|
|
720
|
-
--prose-h2: 1.375rem;
|
|
721
|
-
--prose-h3: 1.125rem;
|
|
722
|
-
--prose-h4: 1rem;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
.prose.lg {
|
|
726
|
-
--prose-size: 1.125rem;
|
|
727
|
-
--prose-measure: 72ch;
|
|
728
|
-
--prose-h1: 2.25rem;
|
|
729
|
-
--prose-h2: 1.625rem;
|
|
730
|
-
--prose-h3: 1.375rem;
|
|
731
|
-
--prose-h4: 1.25rem;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/*
|
|
735
|
-
* Core layout primitives: stack, cluster, center, frame, app-shell, media,
|
|
736
|
-
* sidebar-layout. Each primitive owns a single, readable behavior; they are
|
|
737
|
-
* meant to compose in markup.
|
|
738
|
-
*
|
|
739
|
-
* Public hooks:
|
|
740
|
-
* --cluster-align
|
|
741
|
-
* --cluster-justify
|
|
742
|
-
* --cluster-wrap
|
|
743
|
-
* --center-size
|
|
744
|
-
* --center-pad
|
|
745
|
-
* --frame-ratio
|
|
746
|
-
* --sidebar-layout-size
|
|
747
|
-
*
|
|
748
|
-
* Child contracts:
|
|
749
|
-
* .stack owns vertical rhythm: direct-child block-axis margins are reset,
|
|
750
|
-
* spacing comes from --gap, inline margins stay free.
|
|
751
|
-
* .cluster owns inline rhythm: direct-child margins are reset, spacing comes
|
|
752
|
-
* from --gap.
|
|
753
|
-
* .frame sizes direct media children (img/video/iframe/canvas/svg) to fill the
|
|
754
|
-
* frame.
|
|
755
|
-
*/
|
|
756
|
-
|
|
757
|
-
/* Responsive recipes:
|
|
758
|
-
- avatar/icon + flexible content -> .media
|
|
759
|
-
- inline items that wrap when space runs out -> .cluster (keep wrapping)
|
|
760
|
-
- content + aside -> .sidebar-layout inside .container-query
|
|
761
|
-
- row that becomes a column when constrained -> .switcher (optional module)
|
|
762
|
-
|
|
763
|
-
Anti-patterns:
|
|
764
|
-
- viewport breakpoints for ordinary composition
|
|
765
|
-
- fixed grid templates (--grid-columns) where .grid/.sidebar-layout express
|
|
766
|
-
the layout
|
|
767
|
-
- nowrap on page-level compositions; prefer wrapping and container-aware
|
|
768
|
-
primitives */
|
|
769
|
-
.container-query {
|
|
770
|
-
container-type: inline-size;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
.stack {
|
|
774
|
-
display: flex;
|
|
775
|
-
flex-direction: column;
|
|
776
|
-
gap: var(--gap);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/* .stack owns the block axis only: block margins reset, inline margins stay
|
|
780
|
-
free (self-centering children via margin-inline: auto keep working). */
|
|
781
|
-
.stack > * {
|
|
782
|
-
margin-block: 0;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
.cluster {
|
|
786
|
-
display: flex;
|
|
787
|
-
flex-wrap: var(--cluster-wrap, wrap);
|
|
788
|
-
align-items: var(--cluster-align, center);
|
|
789
|
-
justify-content: var(--cluster-justify, flex-start);
|
|
790
|
-
gap: var(--gap);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.cluster > * {
|
|
794
|
-
margin: 0;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
.center {
|
|
798
|
-
box-sizing: content-box;
|
|
799
|
-
max-inline-size: var(--center-size, 72rem);
|
|
800
|
-
margin-inline: auto;
|
|
801
|
-
padding-inline: var(--center-pad, 1rem);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
.frame {
|
|
805
|
-
overflow: hidden;
|
|
806
|
-
aspect-ratio: var(--frame-ratio, 16 / 9);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
.frame > :where(img, video, iframe, canvas, svg) {
|
|
810
|
-
inline-size: 100%;
|
|
811
|
-
block-size: 100%;
|
|
812
|
-
object-fit: cover;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
.app-shell {
|
|
816
|
-
min-block-size: var(--viewport-block);
|
|
817
|
-
display: flex;
|
|
818
|
-
flex-direction: column;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
/* Media object: fixed-width leading element (avatar, icon, image) + flexible
|
|
822
|
-
trailing content. Replaces the repeated "auto minmax(0, 1fr)" pattern in
|
|
823
|
-
author cards, comments, meta rows, and similar compositions. */
|
|
824
|
-
.media {
|
|
825
|
-
display: grid;
|
|
826
|
-
grid-template-columns: auto minmax(0, 1fr);
|
|
827
|
-
gap: var(--gap);
|
|
828
|
-
align-items: start;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/* Sidebar layout: content + aside using grid. Use this when the aside is
|
|
832
|
-
sticky or the content is multi-section. The aside width is tunable with
|
|
833
|
-
--sidebar-layout-size. */
|
|
834
|
-
.sidebar-layout {
|
|
835
|
-
display: grid;
|
|
836
|
-
gap: var(--gap);
|
|
837
|
-
grid-template-columns: minmax(0, 1fr);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/* Container queries are the preferred path; without them the @supports not
|
|
841
|
-
block below restores the same columns at the same viewport breakpoint. */
|
|
842
|
-
@container (width >= 64rem) {
|
|
843
|
-
.container-query .sidebar-layout {
|
|
844
|
-
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
845
|
-
align-items: start;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
@supports not (container-type: inline-size) {
|
|
850
|
-
@media (min-width: 64rem) {
|
|
851
|
-
.container-query .sidebar-layout {
|
|
852
|
-
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
853
|
-
align-items: start;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/*
|
|
859
|
-
* Responsive equal-width grids, with fixed-column presets below.
|
|
860
|
-
*
|
|
861
|
-
* Public hooks:
|
|
862
|
-
* --grid-min
|
|
863
|
-
* --grid-columns
|
|
864
|
-
*/
|
|
865
|
-
|
|
866
|
-
/* --grid-columns replaces the intrinsic auto-fit template: once set, the
|
|
867
|
-
grid no longer collapses on its own — the author owns narrow-container
|
|
868
|
-
collapse. Prefer .sidebar-layout inside .container-query for content +
|
|
869
|
-
aside layouts; reserve --grid-columns for genuinely fixed or asymmetric
|
|
870
|
-
templates. */
|
|
871
|
-
|
|
872
|
-
/* Recipe:
|
|
873
|
-
- N equal cards that collapse when narrow -> .grid (auto-fit, tune
|
|
874
|
-
--grid-min) or .grid-2/3/4/6 inside .container-query
|
|
875
|
-
- content + aside -> .sidebar-layout inside .container-query
|
|
876
|
-
- fixed or asymmetric columns -> --grid-columns with an explicit collapse
|
|
877
|
-
rule of your own */
|
|
878
|
-
.grid {
|
|
879
|
-
display: grid;
|
|
880
|
-
gap: var(--gap);
|
|
881
|
-
grid-template-columns: var(
|
|
882
|
-
--grid-columns,
|
|
883
|
-
repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr))
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/* Fixed-column grids at their full count by default: .grid-2/3/4/6 stay at
|
|
888
|
-
2/3/4/6 columns unless wrapped in .container-query, which collapses the
|
|
889
|
-
column count on narrow wrappers (a 2-col intermediate step for
|
|
890
|
-
.grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant for
|
|
891
|
-
compact items that do not need a full single-column stack). */
|
|
892
|
-
.grid-2,
|
|
893
|
-
.grid-3,
|
|
894
|
-
.grid-4,
|
|
895
|
-
.grid-6 {
|
|
896
|
-
display: grid;
|
|
897
|
-
gap: var(--gap);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
.grid-2 {
|
|
901
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
902
|
-
}
|
|
903
|
-
.grid-3 {
|
|
904
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
905
|
-
}
|
|
906
|
-
.grid-4 {
|
|
907
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
908
|
-
}
|
|
909
|
-
.grid-6 {
|
|
910
|
-
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/* Container queries are the preferred responsive path; browsers without them
|
|
914
|
-
ignore these rules and get the equivalent @media rules from the
|
|
915
|
-
@supports not (container-type) block below — same columns, same breakpoints. */
|
|
916
|
-
@container (width < 28rem) {
|
|
917
|
-
.container-query .grid-2,
|
|
918
|
-
.container-query .grid-3,
|
|
919
|
-
.container-query .grid-4 {
|
|
920
|
-
grid-template-columns: 1fr;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
.container-query .grid-6 {
|
|
924
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
@container (28rem <= width < 48rem) {
|
|
929
|
-
.container-query .grid-3,
|
|
930
|
-
.container-query .grid-4 {
|
|
931
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
@container (28rem <= width < 64rem) {
|
|
936
|
-
.container-query .grid-6 {
|
|
937
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
@supports not (container-type: inline-size) {
|
|
942
|
-
@media (max-width: 63.999rem) {
|
|
943
|
-
.container-query .grid-6 {
|
|
944
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
@media (max-width: 47.999rem) {
|
|
949
|
-
.container-query .grid-3,
|
|
950
|
-
.container-query .grid-4 {
|
|
951
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
@media (max-width: 27.999rem) {
|
|
956
|
-
.container-query .grid-2,
|
|
957
|
-
.container-query .grid-3,
|
|
958
|
-
.container-query .grid-4 {
|
|
959
|
-
grid-template-columns: 1fr;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
.container-query .grid-6 {
|
|
963
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
557
|
/*
|
|
969
558
|
* Cross-cutting variant token mapping.
|
|
970
559
|
*
|
|
@@ -1052,12 +641,13 @@ small {
|
|
|
1052
641
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1053
642
|
.soft {
|
|
1054
643
|
/* @sync soft-recipe */
|
|
1055
|
-
--ui-bg: color-mix(in
|
|
644
|
+
--ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
|
|
1056
645
|
--ui-border: color-mix(
|
|
1057
|
-
in
|
|
646
|
+
in oklab,
|
|
1058
647
|
var(--surface) var(--soft-border-mix),
|
|
1059
648
|
var(--intent, var(--neutral))
|
|
1060
649
|
);
|
|
650
|
+
--ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix));
|
|
1061
651
|
/* @sync soft-recipe */
|
|
1062
652
|
}
|
|
1063
653
|
|
|
@@ -1069,4511 +659,48 @@ small {
|
|
|
1069
659
|
/* Interactive hover recipes live in the components that consume them:
|
|
1070
660
|
components/button.css and components/badge.css. */
|
|
1071
661
|
|
|
1072
|
-
/*
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
*
|
|
1076
|
-
* .btn is primarily for actionable elements: <button>, <a href>,
|
|
1077
|
-
* input[type="button"|"submit"|"reset"].
|
|
1078
|
-
*
|
|
1079
|
-
* Keeps shared intent, variant, and size behavior with a solid default.
|
|
1080
|
-
*
|
|
1081
|
-
* Disabled state uses the native disabled attribute for buttons and inputs.
|
|
1082
|
-
* Anchors have no native disabled state, so a.btn uses aria-disabled="true"
|
|
1083
|
-
* for disabled styling and activation guards.
|
|
1084
|
-
*
|
|
1085
|
-
* Public hooks:
|
|
1086
|
-
* --btn-font-size
|
|
1087
|
-
* --btn-min-size
|
|
1088
|
-
* --btn-pad-x
|
|
1089
|
-
* --btn-radius
|
|
1090
|
-
* --btn-focus-color
|
|
1091
|
-
* --btn-gap
|
|
1092
|
-
* --btn-hover-overlay
|
|
1093
|
-
* --btn-default-bg
|
|
1094
|
-
* --btn-default-fg
|
|
1095
|
-
* --btn-default-border
|
|
1096
|
-
* --btn-border-color
|
|
1097
|
-
*
|
|
1098
|
-
* Internal:
|
|
1099
|
-
* --btn-bg
|
|
1100
|
-
* --btn-fg
|
|
1101
|
-
* --btn-border
|
|
1102
|
-
* --btn-default-hover-bg
|
|
1103
|
-
* --btn-focus-ring-color
|
|
1104
|
-
* --btn-border-block-end-color
|
|
1105
|
-
* --btn-border-block-end-width
|
|
1106
|
-
* --spinner-width
|
|
1107
|
-
*/
|
|
1108
|
-
|
|
1109
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
1110
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
1111
|
-
:where(.btn) {
|
|
1112
|
-
--intent: initial;
|
|
1113
|
-
--intent-fg: initial;
|
|
1114
|
-
--ui-bg: initial;
|
|
1115
|
-
--ui-fg: initial;
|
|
1116
|
-
--ui-border: initial;
|
|
1117
|
-
--ui-hover-bg: initial;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
.btn {
|
|
1121
|
-
--btn-font-size: var(--control-font-size);
|
|
1122
|
-
--btn-min-size: var(--control-size);
|
|
1123
|
-
--btn-pad-x: var(--control-pad-x, 1em);
|
|
1124
|
-
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
1125
|
-
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
1126
|
-
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
1127
|
-
--btn-default-hover-bg: var(--btn-bg);
|
|
1128
|
-
--btn-radius: var(--radius);
|
|
1129
|
-
--btn-focus-color: var(--intent, var(--focus));
|
|
1130
|
-
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
1131
|
-
|
|
1132
|
-
/* Positioning context for indicators, overlays, and grouped states. */
|
|
1133
|
-
position: relative;
|
|
1134
|
-
display: inline-flex;
|
|
1135
|
-
align-items: center;
|
|
1136
|
-
justify-content: center;
|
|
1137
|
-
gap: var(--btn-gap, 0.375em);
|
|
1138
|
-
min-block-size: var(--btn-min-size);
|
|
1139
|
-
padding-block: var(--space-20);
|
|
1140
|
-
padding-block: max(var(--space-10), calc((var(--btn-min-size) - 1lh) / 2));
|
|
1141
|
-
padding-inline: var(--btn-pad-x);
|
|
1142
|
-
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
1143
|
-
border-block-end-color: var(
|
|
1144
|
-
--btn-border-block-end-color,
|
|
1145
|
-
var(--btn-border-color, var(--btn-border))
|
|
1146
|
-
);
|
|
1147
|
-
border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
|
|
1148
|
-
border-radius: var(--btn-radius);
|
|
1149
|
-
background: var(--btn-bg);
|
|
1150
|
-
color: var(--btn-fg);
|
|
1151
|
-
font: inherit;
|
|
1152
|
-
font-size: var(--btn-font-size);
|
|
1153
|
-
font-weight: var(--font-weight-medium);
|
|
1154
|
-
line-height: 1;
|
|
1155
|
-
text-decoration: none;
|
|
1156
|
-
cursor: pointer;
|
|
1157
|
-
transition:
|
|
1158
|
-
background-color var(--duration),
|
|
1159
|
-
border-color var(--duration),
|
|
1160
|
-
color var(--duration),
|
|
1161
|
-
box-shadow var(--duration),
|
|
1162
|
-
transform var(--duration);
|
|
1163
|
-
}
|
|
662
|
+
/* Focus baseline — a visible outline is the guaranteed focus affordance. The
|
|
663
|
+
:focus outline covers legacy browsers; modern browsers hide it for
|
|
664
|
+
pointer/mouse focus and interactive components layer their ring on top.
|
|
1164
665
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
666
|
+
Invariant: an interactive component must never rely on box-shadow alone
|
|
667
|
+
for its keyboard focus indicator. Components that remove the native
|
|
668
|
+
outline on :focus-visible replace it with a transparent outline — invisible
|
|
669
|
+
normally, forced to the system focus color under forced-colors. */
|
|
670
|
+
:focus {
|
|
671
|
+
outline: var(--focus-outline);
|
|
672
|
+
outline-offset: var(--focus-outline-offset);
|
|
1168
673
|
}
|
|
1169
674
|
|
|
1170
|
-
@supports (
|
|
1171
|
-
|
|
1172
|
-
|
|
675
|
+
@supports selector(:focus-visible) {
|
|
676
|
+
:focus:not(:focus-visible) {
|
|
677
|
+
outline: none;
|
|
1173
678
|
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
|
|
1177
|
-
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
1178
|
-
.btn.ghost {
|
|
1179
|
-
--ui-bg: transparent;
|
|
1180
|
-
--ui-fg: var(--intent, currentColor);
|
|
1181
|
-
--ui-border: transparent;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
/* Solid/soft get the overlay darkening on hover; outline/ghost/link stay flat.
|
|
1185
|
-
Routed through a variable instead of a :not(.outline, .ghost, .link)
|
|
1186
|
-
exclusion so the hover rule below only has to state the interactive
|
|
1187
|
-
element list once. */
|
|
1188
|
-
.btn.outline,
|
|
1189
|
-
.btn.ghost,
|
|
1190
|
-
.btn.link {
|
|
1191
|
-
--btn-hover-overlay: transparent;
|
|
1192
|
-
}
|
|
1193
679
|
|
|
1194
|
-
/*
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
1202
|
-
.btn {
|
|
1203
|
-
--btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
|
|
680
|
+
/* Pointer focus must still signal in high-contrast mode, where the shared
|
|
681
|
+
box-shadow rings are disabled (--focus-ring-shadow: none). */
|
|
682
|
+
@media (forced-colors: active) {
|
|
683
|
+
:focus:not(:focus-visible) {
|
|
684
|
+
outline: var(--focus-outline);
|
|
685
|
+
}
|
|
1204
686
|
}
|
|
687
|
+
}
|
|
1205
688
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
689
|
+
@media print {
|
|
690
|
+
:root,
|
|
691
|
+
body {
|
|
692
|
+
/* Save some ink in dark mode */
|
|
693
|
+
color-scheme: light;
|
|
694
|
+
background: #fff;
|
|
695
|
+
color: #000;
|
|
1212
696
|
}
|
|
1213
697
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
698
|
+
h1,
|
|
699
|
+
h2,
|
|
700
|
+
h3,
|
|
701
|
+
h4,
|
|
702
|
+
h5,
|
|
703
|
+
h6 {
|
|
704
|
+
break-after: avoid;
|
|
1217
705
|
}
|
|
1218
706
|
}
|
|
1219
|
-
|
|
1220
|
-
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
1221
|
-
the overlay darkening for solid/soft is .btn-specific. */
|
|
1222
|
-
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1223
|
-
background: var(--ui-hover-bg, var(--btn-default-hover-bg));
|
|
1224
|
-
box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
.btn:not(:where(:disabled, [aria-disabled="true"])):active {
|
|
1228
|
-
transform: translateY(1px);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
.btn.link {
|
|
1232
|
-
--ui-bg: transparent;
|
|
1233
|
-
--ui-fg: var(--intent, currentColor);
|
|
1234
|
-
--ui-border: transparent;
|
|
1235
|
-
--btn-radius: var(--radius-sm);
|
|
1236
|
-
--btn-border-block-end-color: transparent;
|
|
1237
|
-
--btn-border-block-end-width: var(--border-width);
|
|
1238
|
-
|
|
1239
|
-
min-block-size: auto;
|
|
1240
|
-
padding-inline: 0;
|
|
1241
|
-
inline-size: fit-content;
|
|
1242
|
-
max-inline-size: 100%;
|
|
1243
|
-
border-color: transparent;
|
|
1244
|
-
text-decoration: underline;
|
|
1245
|
-
text-underline-offset: 0.18em;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
/* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
|
|
1249
|
-
after components and overrides border-radius at equal specificity. */
|
|
1250
|
-
|
|
1251
|
-
.btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1252
|
-
background: transparent;
|
|
1253
|
-
text-decoration-thickness: 0.12em;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
.btn:focus-visible {
|
|
1257
|
-
outline: 2px solid transparent;
|
|
1258
|
-
box-shadow: 0 0 0 var(--focus-ring-width) var(--btn-focus-ring-color, var(--focus-ring));
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
.btn.link:focus-visible {
|
|
1262
|
-
outline: var(--focus-outline);
|
|
1263
|
-
outline-offset: 0.2em;
|
|
1264
|
-
box-shadow: none;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
.btn:disabled {
|
|
1268
|
-
cursor: not-allowed;
|
|
1269
|
-
opacity: var(--disabled-opacity);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
/* Anchors have no native disabled state.
|
|
1273
|
-
a.btn[aria-disabled="true"] only provides disabled styling and blocks pointer
|
|
1274
|
-
activation. For a truly disabled link, remove href or prevent activation in
|
|
1275
|
-
app code. */
|
|
1276
|
-
a.btn[aria-disabled="true"] {
|
|
1277
|
-
cursor: not-allowed;
|
|
1278
|
-
opacity: var(--disabled-opacity);
|
|
1279
|
-
pointer-events: none;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
/* Busy */
|
|
1283
|
-
.btn[aria-busy="true"]:not(:disabled) {
|
|
1284
|
-
cursor: progress;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
.btn[aria-busy="true"] > .spinner {
|
|
1288
|
-
--spinner-width: 0.15em;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
/* Toggle and current state.
|
|
1292
|
-
State must outrank the shared .outline/.ghost hover recipes: a pressed or
|
|
1293
|
-
current button keeps its intent background and foreground while hovered.
|
|
1294
|
-
aria-current="page" is the pagination current-page marker; aria-pressed
|
|
1295
|
-
covers toggle buttons. */
|
|
1296
|
-
.btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
|
|
1297
|
-
--btn-bg: var(--intent, var(--state-selected));
|
|
1298
|
-
--btn-fg: var(--intent-fg, var(--state-selected-fg));
|
|
1299
|
-
--btn-border: var(--intent, var(--state-selected));
|
|
1300
|
-
--ui-hover-bg: var(--btn-bg);
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
@media (forced-colors: active) {
|
|
1304
|
-
.btn {
|
|
1305
|
-
border-color: ButtonText;
|
|
1306
|
-
background: ButtonFace;
|
|
1307
|
-
color: ButtonText;
|
|
1308
|
-
box-shadow: none;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
.btn.link {
|
|
1312
|
-
border-color: transparent;
|
|
1313
|
-
background: Canvas;
|
|
1314
|
-
color: LinkText;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
.btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
|
|
1318
|
-
border-color: Highlight;
|
|
1319
|
-
background: Highlight;
|
|
1320
|
-
color: HighlightText;
|
|
1321
|
-
forced-color-adjust: none;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
.btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
|
|
1325
|
-
outline: var(--border-width) solid ButtonText;
|
|
1326
|
-
outline-offset: 2px;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
.btn:disabled,
|
|
1330
|
-
a.btn[aria-disabled="true"] {
|
|
1331
|
-
border-color: GrayText;
|
|
1332
|
-
background: Canvas;
|
|
1333
|
-
color: GrayText;
|
|
1334
|
-
forced-color-adjust: auto;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
/*
|
|
1339
|
-
* Badges for compact labels, counts, and state hints.
|
|
1340
|
-
*
|
|
1341
|
-
* Soft by default. .solid for counters and notification badges. The dot
|
|
1342
|
-
* (.badge:empty) stays solid regardless — a soft dot is pointless.
|
|
1343
|
-
* Shared .outline variant stays available.
|
|
1344
|
-
*
|
|
1345
|
-
* Public hooks:
|
|
1346
|
-
* --badge-size
|
|
1347
|
-
* --badge-pad-x
|
|
1348
|
-
* --badge-font-size
|
|
1349
|
-
* --badge-dot-size
|
|
1350
|
-
* --badge-radius
|
|
1351
|
-
*
|
|
1352
|
-
* Internal:
|
|
1353
|
-
* --badge-default-bg
|
|
1354
|
-
* --badge-default-fg
|
|
1355
|
-
* --badge-default-border
|
|
1356
|
-
* --badge-default-hover-bg
|
|
1357
|
-
* --badge-bg
|
|
1358
|
-
* --badge-fg
|
|
1359
|
-
* --badge-border
|
|
1360
|
-
*/
|
|
1361
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
1362
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
1363
|
-
:where(.badge) {
|
|
1364
|
-
--intent: initial;
|
|
1365
|
-
--intent-fg: initial;
|
|
1366
|
-
--ui-bg: initial;
|
|
1367
|
-
--ui-fg: initial;
|
|
1368
|
-
--ui-border: initial;
|
|
1369
|
-
--ui-hover-bg: initial;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
.badge {
|
|
1373
|
-
--badge-size: var(--density-compact-size);
|
|
1374
|
-
--badge-pad-x: 0.55em;
|
|
1375
|
-
--badge-font-size: 0.8125rem;
|
|
1376
|
-
--badge-dot-size: 0.5rem;
|
|
1377
|
-
--badge-default-bg: var(--surface-subtle);
|
|
1378
|
-
--badge-default-fg: var(--intent, var(--text));
|
|
1379
|
-
--badge-default-border: var(--border);
|
|
1380
|
-
--badge-default-hover-bg: var(--badge-bg);
|
|
1381
|
-
--badge-bg: var(--ui-bg, var(--badge-default-bg));
|
|
1382
|
-
--badge-fg: var(--ui-fg, var(--badge-default-fg));
|
|
1383
|
-
--badge-border: var(--ui-border, var(--badge-default-border));
|
|
1384
|
-
--badge-radius: var(--radius-full);
|
|
1385
|
-
|
|
1386
|
-
display: inline-flex;
|
|
1387
|
-
align-items: center;
|
|
1388
|
-
justify-content: center;
|
|
1389
|
-
gap: 0.35em;
|
|
1390
|
-
inline-size: fit-content;
|
|
1391
|
-
max-inline-size: 100%;
|
|
1392
|
-
min-block-size: var(--badge-size);
|
|
1393
|
-
padding-inline: var(--badge-pad-x);
|
|
1394
|
-
border: var(--border-width) solid var(--badge-border);
|
|
1395
|
-
border-radius: var(--badge-radius);
|
|
1396
|
-
background: var(--badge-bg);
|
|
1397
|
-
color: var(--badge-fg);
|
|
1398
|
-
font-size: var(--badge-font-size);
|
|
1399
|
-
font-weight: var(--font-weight-strong);
|
|
1400
|
-
line-height: 1;
|
|
1401
|
-
text-decoration: none;
|
|
1402
|
-
white-space: nowrap;
|
|
1403
|
-
transition:
|
|
1404
|
-
background-color var(--duration),
|
|
1405
|
-
border-color var(--duration),
|
|
1406
|
-
color var(--duration),
|
|
1407
|
-
box-shadow var(--duration);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
/* Soft by default. Local defaults remain behind --ui-* in the resolution
|
|
1411
|
-
chain, so explicit shared treatments compose without selector exclusions. */
|
|
1412
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
1413
|
-
.badge {
|
|
1414
|
-
/* @sync soft-recipe */
|
|
1415
|
-
--badge-default-bg: color-mix(
|
|
1416
|
-
in oklch,
|
|
1417
|
-
var(--surface) var(--soft-bg-mix),
|
|
1418
|
-
var(--intent, var(--neutral))
|
|
1419
|
-
);
|
|
1420
|
-
--badge-default-border: color-mix(
|
|
1421
|
-
in oklch,
|
|
1422
|
-
var(--surface) var(--soft-border-mix),
|
|
1423
|
-
var(--intent, var(--neutral))
|
|
1424
|
-
);
|
|
1425
|
-
/* @sync soft-recipe */
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
.badge:empty {
|
|
1430
|
-
--badge-size: var(--badge-dot-size);
|
|
1431
|
-
--badge-bg: var(--intent, var(--neutral));
|
|
1432
|
-
--badge-fg: var(--intent-fg, var(--neutral-fg));
|
|
1433
|
-
--badge-border: transparent;
|
|
1434
|
-
padding: 0;
|
|
1435
|
-
border: 0;
|
|
1436
|
-
inline-size: var(--badge-size);
|
|
1437
|
-
block-size: var(--badge-size);
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
.badge > button {
|
|
1441
|
-
display: inline-grid;
|
|
1442
|
-
place-items: center;
|
|
1443
|
-
flex: 0 0 auto;
|
|
1444
|
-
min-inline-size: 1.5rem;
|
|
1445
|
-
min-block-size: 1.5rem;
|
|
1446
|
-
margin-block: -0.25rem;
|
|
1447
|
-
margin-inline-start: -0.15em;
|
|
1448
|
-
margin-inline-end: -0.45em;
|
|
1449
|
-
padding: 0.25rem;
|
|
1450
|
-
border: 0;
|
|
1451
|
-
border-radius: var(--radius-full);
|
|
1452
|
-
background: transparent;
|
|
1453
|
-
color: inherit;
|
|
1454
|
-
font: inherit;
|
|
1455
|
-
line-height: 1;
|
|
1456
|
-
cursor: pointer;
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
.badge > button:not(:disabled):hover {
|
|
1460
|
-
background: var(--hover-overlay);
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
1464
|
-
Declared outside :hover like button.css for the same reason. */
|
|
1465
|
-
:is(a.badge, button.badge).outline {
|
|
1466
|
-
--ui-hover-bg: transparent;
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
1470
|
-
:is(a.badge, button.badge):is(.solid, :empty) {
|
|
1471
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
1475
|
-
--badge-default-hover-bg: color-mix(
|
|
1476
|
-
in oklch,
|
|
1477
|
-
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
1478
|
-
var(--intent, var(--neutral))
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
:is(a.badge, button.badge).outline {
|
|
1483
|
-
--ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1488
|
-
background: var(--ui-hover-bg, var(--badge-default-hover-bg));
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
|
|
1492
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
button.badge {
|
|
1496
|
-
cursor: pointer;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
button.badge:disabled,
|
|
1500
|
-
.badge[aria-disabled="true"] {
|
|
1501
|
-
opacity: var(--disabled-opacity);
|
|
1502
|
-
cursor: not-allowed;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
a.badge[aria-disabled="true"] {
|
|
1506
|
-
pointer-events: none;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
@media (forced-colors: active) {
|
|
1510
|
-
.badge:empty {
|
|
1511
|
-
background: ButtonText;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
:where(a.badge, button.badge):hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
1515
|
-
outline: var(--border-width) solid ButtonText;
|
|
1516
|
-
outline-offset: 2px;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
.badge > button:not(:disabled):hover {
|
|
1520
|
-
outline: var(--border-width) solid currentColor;
|
|
1521
|
-
outline-offset: 1px;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
button.badge:disabled,
|
|
1525
|
-
.badge[aria-disabled="true"] {
|
|
1526
|
-
border-color: GrayText;
|
|
1527
|
-
background: Canvas;
|
|
1528
|
-
color: GrayText;
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
/*
|
|
1533
|
-
* Alerts for inline feedback and status messages.
|
|
1534
|
-
*
|
|
1535
|
-
* Keeps alert defaults local while consuming the shared --ui-* variant
|
|
1536
|
-
* plumbing for emphasis.
|
|
1537
|
-
*
|
|
1538
|
-
* --alert-border-inline-start-color/-width are unset extension points, mirroring
|
|
1539
|
-
* --btn-border-block-end-* (button.css), for themes that give alerts a
|
|
1540
|
-
* callout-style colored flag on the leading edge instead of a uniform border.
|
|
1541
|
-
*
|
|
1542
|
-
* Public hooks:
|
|
1543
|
-
* --alert-pad-block
|
|
1544
|
-
* --alert-pad-inline
|
|
1545
|
-
* --alert-font-size
|
|
1546
|
-
* --alert-radius
|
|
1547
|
-
* --alert-icon-size
|
|
1548
|
-
* --alert-dismiss-size
|
|
1549
|
-
* --alert-border-inline-start-color
|
|
1550
|
-
* --alert-border-inline-start-width
|
|
1551
|
-
*
|
|
1552
|
-
* Internal:
|
|
1553
|
-
* --alert-default-bg
|
|
1554
|
-
* --alert-default-border
|
|
1555
|
-
* --alert-bg
|
|
1556
|
-
* --alert-fg
|
|
1557
|
-
* --alert-border
|
|
1558
|
-
* --alert-dismiss-icon-size
|
|
1559
|
-
*/
|
|
1560
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
1561
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
1562
|
-
:where(.alert) {
|
|
1563
|
-
--intent: initial;
|
|
1564
|
-
--intent-fg: initial;
|
|
1565
|
-
--ui-bg: initial;
|
|
1566
|
-
--ui-fg: initial;
|
|
1567
|
-
--ui-border: initial;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
.alert {
|
|
1571
|
-
--alert-pad-block: var(--space-30);
|
|
1572
|
-
--alert-pad-inline: var(--space-40);
|
|
1573
|
-
--alert-font-size: var(--font-size-md);
|
|
1574
|
-
--alert-icon-size: 1.25rem;
|
|
1575
|
-
--alert-default-bg: var(--surface-subtle);
|
|
1576
|
-
--alert-default-border: var(--border);
|
|
1577
|
-
--alert-bg: var(--ui-bg, var(--alert-default-bg));
|
|
1578
|
-
--alert-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
1579
|
-
--alert-border: var(--ui-border, var(--alert-default-border));
|
|
1580
|
-
|
|
1581
|
-
padding-block: var(--alert-pad-block);
|
|
1582
|
-
padding-inline: var(--alert-pad-inline);
|
|
1583
|
-
background: var(--alert-bg);
|
|
1584
|
-
color: var(--alert-fg);
|
|
1585
|
-
border: var(--border-width) solid var(--alert-border);
|
|
1586
|
-
border-inline-start-color: var(--alert-border-inline-start-color, var(--alert-border));
|
|
1587
|
-
border-inline-start-width: var(--alert-border-inline-start-width, var(--border-width));
|
|
1588
|
-
border-radius: var(--alert-radius, var(--radius-lg));
|
|
1589
|
-
font-size: var(--alert-font-size);
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
.alert > * {
|
|
1593
|
-
min-inline-size: 0;
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
.alert > :first-child {
|
|
1597
|
-
margin-block-start: 0;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
.alert > :last-child {
|
|
1601
|
-
margin-block-end: 0;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
/* Alerts are soft by default. The recipe writes local defaults rather than
|
|
1605
|
-
--ui-* so an explicit shared treatment always wins without exclusions. */
|
|
1606
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
1607
|
-
.alert {
|
|
1608
|
-
/* @sync soft-recipe */
|
|
1609
|
-
--alert-default-bg: color-mix(
|
|
1610
|
-
in oklch,
|
|
1611
|
-
var(--surface) var(--soft-bg-mix),
|
|
1612
|
-
var(--intent, var(--neutral))
|
|
1613
|
-
);
|
|
1614
|
-
--alert-default-border: color-mix(
|
|
1615
|
-
in oklch,
|
|
1616
|
-
var(--surface) var(--soft-border-mix),
|
|
1617
|
-
var(--intent, var(--neutral))
|
|
1618
|
-
);
|
|
1619
|
-
/* @sync soft-recipe */
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
/* Decorative leading icon — use .alert-icon on the icon element. */
|
|
1624
|
-
.alert > .alert-icon {
|
|
1625
|
-
display: inline-block;
|
|
1626
|
-
flex: none;
|
|
1627
|
-
inline-size: var(--alert-icon-size);
|
|
1628
|
-
block-size: var(--alert-icon-size);
|
|
1629
|
-
font-size: var(--alert-icon-size);
|
|
1630
|
-
line-height: 1;
|
|
1631
|
-
vertical-align: -0.125em;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
/* Dismiss — compact close button for inline alerts. Transparent at rest with
|
|
1635
|
-
a reduced opacity glyph so it stays secondary inside an already-colored
|
|
1636
|
-
alert; hover reinforces it via the shared overlay. Uses the shared
|
|
1637
|
-
--icon-close mask, so no icon font or text glyph is required in the markup.
|
|
1638
|
-
Standard alerts place it as a direct trailing child; admonitions place it
|
|
1639
|
-
inside .alert-title (see below). */
|
|
1640
|
-
.alert-dismiss {
|
|
1641
|
-
--alert-dismiss-size: 1.75rem;
|
|
1642
|
-
--alert-dismiss-icon-size: 0.875rem;
|
|
1643
|
-
|
|
1644
|
-
display: inline-grid;
|
|
1645
|
-
place-items: center;
|
|
1646
|
-
inline-size: var(--alert-dismiss-size);
|
|
1647
|
-
block-size: var(--alert-dismiss-size);
|
|
1648
|
-
flex: none;
|
|
1649
|
-
padding: 0;
|
|
1650
|
-
border: 0;
|
|
1651
|
-
border-radius: var(--radius);
|
|
1652
|
-
background: transparent;
|
|
1653
|
-
color: currentColor;
|
|
1654
|
-
opacity: 0.65;
|
|
1655
|
-
cursor: pointer;
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
.alert-dismiss::before {
|
|
1659
|
-
content: "";
|
|
1660
|
-
inline-size: var(--alert-dismiss-icon-size);
|
|
1661
|
-
block-size: var(--alert-dismiss-icon-size);
|
|
1662
|
-
background: currentColor;
|
|
1663
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
1664
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
@media (hover: hover) {
|
|
1668
|
-
.alert-dismiss:hover {
|
|
1669
|
-
background: var(--hover-overlay);
|
|
1670
|
-
opacity: 1;
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
/* Grid anatomy — activated by a leading icon, a trailing dismiss button, or
|
|
1675
|
-
both. Icon and dismiss are pinned to the outer columns; anything else (the
|
|
1676
|
-
message content) flows into the remaining text column. */
|
|
1677
|
-
@supports selector(.alert:has(> *)) {
|
|
1678
|
-
.alert:has(> .alert-icon, > .alert-dismiss) {
|
|
1679
|
-
display: grid;
|
|
1680
|
-
grid-template-columns: minmax(0, 1fr);
|
|
1681
|
-
align-items: var(--items-align, start);
|
|
1682
|
-
column-gap: var(--space-30);
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
.alert:has(> .alert-icon) {
|
|
1686
|
-
grid-template-columns: auto minmax(0, 1fr);
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
.alert:has(> .alert-icon) > .alert-icon {
|
|
1690
|
-
grid-column: 1;
|
|
1691
|
-
grid-row: 1 / -1;
|
|
1692
|
-
display: inline-grid;
|
|
1693
|
-
place-items: center;
|
|
1694
|
-
margin-block-start: 0.125em;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
.alert:has(> .alert-icon) > :not(.alert-icon, .alert-dismiss) {
|
|
1698
|
-
grid-column: 2;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
.alert:has(> .alert-dismiss) {
|
|
1702
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
.alert:has(> .alert-icon):has(> .alert-dismiss) {
|
|
1706
|
-
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
.alert:has(> .alert-dismiss) > .alert-dismiss {
|
|
1710
|
-
grid-column: -2 / -1;
|
|
1711
|
-
grid-row: 1;
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
/* Links */
|
|
1716
|
-
.alert :where(a:not(.btn)) {
|
|
1717
|
-
color: inherit;
|
|
1718
|
-
font-weight: var(--font-weight-strong);
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/* Forced colors: no override needed. The always-on border gets a forced
|
|
1722
|
-
readable color, and text on a Highlight surface would trigger Chromium's
|
|
1723
|
-
Canvas backplate (unreadable) — so .solid intentionally falls back to the
|
|
1724
|
-
default look. */
|
|
1725
|
-
|
|
1726
|
-
/* Callout — neutral surface with a thick accent border on the leading edge.
|
|
1727
|
-
The thickness follows the shared --alert-border-inline-start-width hook. */
|
|
1728
|
-
.alert.callout {
|
|
1729
|
-
--alert-bg: var(--surface-subtle);
|
|
1730
|
-
--alert-fg: var(--text);
|
|
1731
|
-
border: 0;
|
|
1732
|
-
border-inline-start: var(--alert-border-inline-start-width, 4px) solid
|
|
1733
|
-
var(--alert-border-inline-start-color, var(--intent, var(--neutral)));
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
/* Admonition — structured static box with a tinted title bar and a body
|
|
1737
|
-
that rests on the page surface. Not collapsible — use .accordion inside
|
|
1738
|
-
the body if the content needs to be expandable. */
|
|
1739
|
-
.alert.admonition {
|
|
1740
|
-
padding: 0;
|
|
1741
|
-
display: block;
|
|
1742
|
-
overflow: clip;
|
|
1743
|
-
border-color: var(--intent, var(--border));
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
.alert.admonition > .alert-title {
|
|
1747
|
-
display: flex;
|
|
1748
|
-
align-items: center;
|
|
1749
|
-
gap: var(--space-20);
|
|
1750
|
-
padding-block: var(--alert-pad-block);
|
|
1751
|
-
padding-inline: var(--alert-pad-inline);
|
|
1752
|
-
border-start-start-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
1753
|
-
border-start-end-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
/* Dismiss inside an admonition title bar rides the inline end. */
|
|
1757
|
-
.alert.admonition > .alert-title > .alert-dismiss {
|
|
1758
|
-
margin-inline-start: auto;
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
1762
|
-
.alert.admonition > .alert-title {
|
|
1763
|
-
background: color-mix(
|
|
1764
|
-
in oklch,
|
|
1765
|
-
var(--surface) var(--soft-bg-mix),
|
|
1766
|
-
var(--intent, var(--neutral))
|
|
1767
|
-
);
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
@supports not (color: color-mix(in oklch, red, white)) {
|
|
1772
|
-
.alert.admonition > .alert-title {
|
|
1773
|
-
background: var(--intent, var(--neutral));
|
|
1774
|
-
color: var(--intent-fg, var(--neutral-fg));
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
.alert.admonition > .alert-body {
|
|
1779
|
-
padding-block: var(--alert-pad-block);
|
|
1780
|
-
padding-inline: var(--alert-pad-inline);
|
|
1781
|
-
background: var(--surface);
|
|
1782
|
-
color: var(--text);
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
.alert.admonition > .alert-body > :first-child {
|
|
1786
|
-
margin-block-start: 0;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
.alert.admonition > .alert-body > :last-child {
|
|
1790
|
-
margin-block-end: 0;
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
/*
|
|
1794
|
-
* Actions — a horizontal action-button cluster.
|
|
1795
|
-
*
|
|
1796
|
-
* Composes with .btn (or any inline element) and feeds layout via the shared
|
|
1797
|
-
* --gap token.
|
|
1798
|
-
*
|
|
1799
|
-
* Internal:
|
|
1800
|
-
* --gap (local value for the shared gap token)
|
|
1801
|
-
*/
|
|
1802
|
-
.actions {
|
|
1803
|
-
--gap: var(--space-20);
|
|
1804
|
-
|
|
1805
|
-
display: flex;
|
|
1806
|
-
flex-wrap: wrap;
|
|
1807
|
-
align-items: center;
|
|
1808
|
-
gap: var(--gap);
|
|
1809
|
-
margin: 0;
|
|
1810
|
-
padding: 0;
|
|
1811
|
-
list-style: none;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
.actions > li {
|
|
1815
|
-
margin: 0;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
/* Surfaces */
|
|
1820
|
-
/*
|
|
1821
|
-
* Cards provide a neutral raised surface for grouped content.
|
|
1822
|
-
*
|
|
1823
|
-
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
1824
|
-
* Direct header/footer children are structural slots. Page layout remains the
|
|
1825
|
-
* job of markup and layout primitives outside the card.
|
|
1826
|
-
*
|
|
1827
|
-
* Public hooks:
|
|
1828
|
-
* --card-pad
|
|
1829
|
-
* --card-radius
|
|
1830
|
-
* --card-max-inline-size
|
|
1831
|
-
*
|
|
1832
|
-
* Child contract:
|
|
1833
|
-
* Direct .bleed children escape --card-pad (negative inset, wider media);
|
|
1834
|
-
* edge children inherit the card corners. .bleed must be a direct child.
|
|
1835
|
-
*
|
|
1836
|
-
* Internal:
|
|
1837
|
-
* --card-bg
|
|
1838
|
-
* --card-fg
|
|
1839
|
-
* --heading
|
|
1840
|
-
* --busy-overlay-bg
|
|
1841
|
-
*/
|
|
1842
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
1843
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
1844
|
-
:where(.card) {
|
|
1845
|
-
--ui-bg: initial;
|
|
1846
|
-
--ui-fg: initial;
|
|
1847
|
-
--ui-border: initial;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
.card {
|
|
1851
|
-
--card-pad: var(--space-40);
|
|
1852
|
-
--card-radius: var(--radius-lg);
|
|
1853
|
-
--card-max-inline-size: none;
|
|
1854
|
-
--card-bg: var(--ui-bg, var(--surface-raised));
|
|
1855
|
-
--card-fg: var(--ui-fg, var(--text));
|
|
1856
|
-
|
|
1857
|
-
/* Keep contextual descendants tied to the surface this card actually owns. */
|
|
1858
|
-
--heading: var(--card-fg);
|
|
1859
|
-
--busy-overlay-bg: var(--card-bg);
|
|
1860
|
-
|
|
1861
|
-
max-inline-size: var(--card-max-inline-size);
|
|
1862
|
-
padding: var(--card-pad);
|
|
1863
|
-
background: var(--card-bg);
|
|
1864
|
-
color: var(--card-fg);
|
|
1865
|
-
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
1866
|
-
border-radius: var(--card-radius);
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
.card > * {
|
|
1870
|
-
min-inline-size: 0;
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
.card > :where(header, footer) {
|
|
1874
|
-
display: flex;
|
|
1875
|
-
flex-wrap: wrap;
|
|
1876
|
-
align-items: var(--items-align, center);
|
|
1877
|
-
justify-content: space-between;
|
|
1878
|
-
gap: var(--space-30);
|
|
1879
|
-
min-inline-size: 0;
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
.card > :where(header, footer) > * {
|
|
1883
|
-
min-inline-size: 0;
|
|
1884
|
-
margin-block: 0;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
.card > header {
|
|
1888
|
-
align-items: var(--items-align, flex-start);
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
.card > :first-child {
|
|
1892
|
-
margin-block-start: 0;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
.card > :last-child {
|
|
1896
|
-
margin-block-end: 0;
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
.card.raised {
|
|
1900
|
-
box-shadow: var(--shadow);
|
|
1901
|
-
}
|
|
1902
|
-
.card.subtle {
|
|
1903
|
-
background: var(--surface-subtle);
|
|
1904
|
-
}
|
|
1905
|
-
.card.compact {
|
|
1906
|
-
--card-pad: var(--space-20);
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
/* Full-bleed children */
|
|
1910
|
-
.card > .bleed {
|
|
1911
|
-
margin-inline: calc(var(--card-pad) * -1);
|
|
1912
|
-
max-inline-size: none;
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
.card > .bleed:first-child {
|
|
1916
|
-
margin-block-start: calc(var(--card-pad) * -1);
|
|
1917
|
-
border-start-start-radius: inherit;
|
|
1918
|
-
border-start-end-radius: inherit;
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
.card > .bleed:last-child {
|
|
1922
|
-
margin-block-end: calc(var(--card-pad) * -1);
|
|
1923
|
-
border-end-start-radius: inherit;
|
|
1924
|
-
border-end-end-radius: inherit;
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
/* Media bleed */
|
|
1928
|
-
.card > :where(img, picture, video, canvas, svg).bleed {
|
|
1929
|
-
display: block;
|
|
1930
|
-
inline-size: calc(100% + var(--card-pad) * 2);
|
|
1931
|
-
max-inline-size: none;
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
.card > picture.bleed {
|
|
1935
|
-
overflow: hidden;
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
.card > picture.bleed > img {
|
|
1939
|
-
display: block;
|
|
1940
|
-
inline-size: 100%;
|
|
1941
|
-
block-size: auto;
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
/* Padded structural bleed */
|
|
1945
|
-
.card > :where(header, footer, figure).bleed {
|
|
1946
|
-
padding: var(--card-pad);
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
/*
|
|
1950
|
-
* Surface — shared modal scrim and backdrop for flyout and drawer.
|
|
1951
|
-
*
|
|
1952
|
-
* Drawn by .drawer::backdrop (native dialog top-layer) and .surface-backdrop
|
|
1953
|
-
* (custom fixed div for flyout sheets on coarse pointers). The native
|
|
1954
|
-
* .modal::backdrop uses the same fill and animates opacity from 0 to 1
|
|
1955
|
-
* (modal.css), so the scrim strength stays consistent across the dialog
|
|
1956
|
-
* family.
|
|
1957
|
-
*
|
|
1958
|
-
* .surface-backdrop, .is-sheet, and .is-open are runtime markers set by
|
|
1959
|
-
* surface.js. Authors opt in via [data-flyout-mobile="sheet|auto|none|anchored"]
|
|
1960
|
-
* — the JS resolves "auto" into .is-sheet at run time.
|
|
1961
|
-
*/
|
|
1962
|
-
/* Custom backdrop for overlays that do not use the native dialog top-layer.
|
|
1963
|
-
* Its z-index sits just below the flyout it accompanies. */
|
|
1964
|
-
.surface-backdrop {
|
|
1965
|
-
position: fixed;
|
|
1966
|
-
inset: 0;
|
|
1967
|
-
z-index: calc(var(--z-menu) - 1);
|
|
1968
|
-
background: var(--backdrop-fill);
|
|
1969
|
-
opacity: 1;
|
|
1970
|
-
transition: opacity var(--duration) var(--ease);
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
.surface-backdrop[hidden] {
|
|
1974
|
-
display: none;
|
|
1975
|
-
opacity: 0;
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
@starting-style {
|
|
1979
|
-
.surface-backdrop:not([hidden]) {
|
|
1980
|
-
opacity: 0;
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
@supports (transition-behavior: allow-discrete) {
|
|
1985
|
-
.surface-backdrop {
|
|
1986
|
-
transition:
|
|
1987
|
-
opacity var(--duration) var(--ease),
|
|
1988
|
-
display var(--duration) allow-discrete;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
/*
|
|
1993
|
-
* Modal shell styling for native dialog elements.
|
|
1994
|
-
*
|
|
1995
|
-
* Spacing, radius, transitions, and focus treatment come from shared tokens.
|
|
1996
|
-
* Opening behavior and intent-specific decisions stay outside this component.
|
|
1997
|
-
*
|
|
1998
|
-
* Public hooks:
|
|
1999
|
-
* --modal-size
|
|
2000
|
-
* --dialog-viewport-gap
|
|
2001
|
-
* --dialog-icon-size
|
|
2002
|
-
* --dialog-icon-glyph-size
|
|
2003
|
-
*
|
|
2004
|
-
* Internal:
|
|
2005
|
-
* --dialog-close-icon-size
|
|
2006
|
-
* --dialog-viewport-gap-2x
|
|
2007
|
-
*/
|
|
2008
|
-
dialog.modal {
|
|
2009
|
-
--modal-size: 32rem;
|
|
2010
|
-
--dialog-viewport-gap: var(--space-40);
|
|
2011
|
-
/* calc() multiplication is not in the supported browser range, so the
|
|
2012
|
-
doubled gap is spelled out with an additive calc. */
|
|
2013
|
-
--dialog-viewport-gap-2x: calc(var(--dialog-viewport-gap) + var(--dialog-viewport-gap));
|
|
2014
|
-
|
|
2015
|
-
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
2016
|
-
provides the containing block for the absolutely positioned close button. */
|
|
2017
|
-
position: fixed;
|
|
2018
|
-
|
|
2019
|
-
max-inline-size: min(calc(100% - var(--dialog-viewport-gap-2x)), var(--modal-size));
|
|
2020
|
-
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
2021
|
-
border: var(--border-width) solid var(--border);
|
|
2022
|
-
border-radius: var(--radius-lg);
|
|
2023
|
-
background: var(--surface-raised);
|
|
2024
|
-
color: var(--text);
|
|
2025
|
-
box-shadow: var(--shadow);
|
|
2026
|
-
padding: 0;
|
|
2027
|
-
overflow: visible;
|
|
2028
|
-
opacity: 0;
|
|
2029
|
-
/*
|
|
2030
|
-
* Keep the open dialog root untransformed. Fixed overlays mounted inside a
|
|
2031
|
-
* modal dialog use viewport coordinates; transform/translate/scale on this
|
|
2032
|
-
* element would make them position against the dialog instead. The closed
|
|
2033
|
-
* state can still carry a small transform for enter/exit motion.
|
|
2034
|
-
*/
|
|
2035
|
-
transform: translateY(0.35rem) scale(0.98);
|
|
2036
|
-
transition:
|
|
2037
|
-
opacity var(--duration),
|
|
2038
|
-
transform var(--duration);
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
dialog.modal > form,
|
|
2042
|
-
dialog.modal > .stack {
|
|
2043
|
-
display: grid;
|
|
2044
|
-
gap: var(--space-40);
|
|
2045
|
-
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
2046
|
-
overflow: auto;
|
|
2047
|
-
overscroll-behavior: contain;
|
|
2048
|
-
padding: var(--space-40);
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
/* Keep the dialog off the viewport edges on small screens. */
|
|
2052
|
-
@media (max-width: 30rem) {
|
|
2053
|
-
dialog.modal {
|
|
2054
|
-
--dialog-viewport-gap: var(--space-30);
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
dialog.modal.scrollable > form,
|
|
2059
|
-
dialog.modal.scrollable > .stack {
|
|
2060
|
-
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
2061
|
-
overflow: hidden;
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
dialog.modal.scrollable > form > :not(header, footer),
|
|
2065
|
-
dialog.modal.scrollable > .stack > :not(header, footer) {
|
|
2066
|
-
min-block-size: 0;
|
|
2067
|
-
overflow: auto;
|
|
2068
|
-
overscroll-behavior: contain;
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
dialog.modal header > :first-child,
|
|
2072
|
-
dialog.modal footer > :first-child {
|
|
2073
|
-
margin-block-start: 0;
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
dialog.modal header > :last-child,
|
|
2077
|
-
dialog.modal footer > :last-child {
|
|
2078
|
-
margin-block-end: 0;
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
2082
|
-
content flow; headers reserve inline room so titles never run under it. */
|
|
2083
|
-
dialog.modal > form > header,
|
|
2084
|
-
dialog.modal > .stack > header {
|
|
2085
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
dialog.modal footer {
|
|
2089
|
-
display: flex;
|
|
2090
|
-
flex-wrap: wrap;
|
|
2091
|
-
justify-content: flex-end;
|
|
2092
|
-
gap: var(--gap);
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
/* Confirmation layout — a focused message followed by a visually distinct
|
|
2096
|
-
action band. The media primitive owns icon/content alignment; this variant
|
|
2097
|
-
only removes the standard form inset so both regions can own their padding. */
|
|
2098
|
-
dialog.modal.dialog-confirmation > form {
|
|
2099
|
-
gap: 0;
|
|
2100
|
-
padding: 0;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
dialog.modal.dialog-confirmation > form > .media {
|
|
2104
|
-
gap: var(--space-40);
|
|
2105
|
-
padding: var(--space-50);
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
dialog.modal.dialog-confirmation > form > footer {
|
|
2109
|
-
padding: var(--space-40) var(--space-50);
|
|
2110
|
-
border-end-start-radius: calc(var(--radius-lg) - var(--border-width));
|
|
2111
|
-
border-end-end-radius: calc(var(--radius-lg) - var(--border-width));
|
|
2112
|
-
background: var(--surface-subtle);
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
2116
|
-
intent plus .soft opts into the shared soft-variant recipe. */
|
|
2117
|
-
:where(.dialog-icon) {
|
|
2118
|
-
--intent: initial;
|
|
2119
|
-
--intent-fg: initial;
|
|
2120
|
-
--ui-bg: initial;
|
|
2121
|
-
--ui-fg: initial;
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
.dialog-icon {
|
|
2125
|
-
--dialog-icon-size: 3rem;
|
|
2126
|
-
--dialog-icon-glyph-size: 1.5rem;
|
|
2127
|
-
|
|
2128
|
-
display: inline-grid;
|
|
2129
|
-
place-items: center;
|
|
2130
|
-
inline-size: var(--dialog-icon-size);
|
|
2131
|
-
block-size: var(--dialog-icon-size);
|
|
2132
|
-
flex: none;
|
|
2133
|
-
border-radius: var(--radius-full);
|
|
2134
|
-
background: var(--ui-bg, var(--surface-subtle));
|
|
2135
|
-
color: var(--ui-fg, var(--intent, var(--text-muted)));
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
.dialog-icon > * {
|
|
2139
|
-
display: inline-grid;
|
|
2140
|
-
place-items: center;
|
|
2141
|
-
inline-size: var(--dialog-icon-glyph-size);
|
|
2142
|
-
block-size: var(--dialog-icon-glyph-size);
|
|
2143
|
-
font-size: var(--dialog-icon-glyph-size);
|
|
2144
|
-
line-height: 1;
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
@media (max-width: 30rem) {
|
|
2148
|
-
dialog.modal.dialog-confirmation > form > .media,
|
|
2149
|
-
dialog.modal.dialog-confirmation > form > footer {
|
|
2150
|
-
padding: var(--space-40);
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
dialog.modal[open] {
|
|
2155
|
-
opacity: 1;
|
|
2156
|
-
transform: none;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
@starting-style {
|
|
2160
|
-
dialog.modal[open] {
|
|
2161
|
-
opacity: 0;
|
|
2162
|
-
transform: translateY(0.35rem) scale(0.98);
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
dialog.modal::backdrop {
|
|
2167
|
-
background: var(--backdrop-fill);
|
|
2168
|
-
opacity: 0;
|
|
2169
|
-
transition: opacity var(--duration);
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
@supports (transition-behavior: allow-discrete) {
|
|
2173
|
-
dialog.modal {
|
|
2174
|
-
transition:
|
|
2175
|
-
opacity var(--duration),
|
|
2176
|
-
transform var(--duration),
|
|
2177
|
-
overlay var(--duration) allow-discrete,
|
|
2178
|
-
display var(--duration) allow-discrete;
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
dialog.modal::backdrop {
|
|
2182
|
-
transition:
|
|
2183
|
-
opacity var(--duration),
|
|
2184
|
-
overlay var(--duration) allow-discrete,
|
|
2185
|
-
display var(--duration) allow-discrete;
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
dialog.modal[open]::backdrop {
|
|
2190
|
-
opacity: 1;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
html.has-modal-open {
|
|
2194
|
-
overflow: hidden;
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
html.has-modal-open.had-scrollbar {
|
|
2198
|
-
scrollbar-gutter: stable;
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
@starting-style {
|
|
2202
|
-
dialog.modal[open]::backdrop {
|
|
2203
|
-
opacity: 0;
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
2208
|
-
dialog.modal.is-static {
|
|
2209
|
-
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
::view-transition-group(actual-dialog) {
|
|
2213
|
-
animation-duration: var(--duration);
|
|
2214
|
-
animation-timing-function: var(--ease);
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
@keyframes actual-dialog-static {
|
|
2219
|
-
0%,
|
|
2220
|
-
100% {
|
|
2221
|
-
transform: none;
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
35% {
|
|
2225
|
-
transform: translateX(-0.35rem);
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
70% {
|
|
2229
|
-
transform: translateX(0.35rem);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
@media (forced-colors: active) {
|
|
2234
|
-
dialog.modal {
|
|
2235
|
-
border-color: ButtonText;
|
|
2236
|
-
background: Canvas;
|
|
2237
|
-
color: CanvasText;
|
|
2238
|
-
box-shadow: none;
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
dialog.modal::backdrop {
|
|
2242
|
-
background: Canvas;
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
.dialog-icon {
|
|
2246
|
-
border: var(--border-width) solid CanvasText;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
/* Close controls. A close button is an action attached to a surface, so it
|
|
2251
|
-
never participates in the content flow. The modal and the drawer share the
|
|
2252
|
-
same simple placement — absolute at the panel's top inline-end, inside the
|
|
2253
|
-
surface — so both close buttons look and behave identically. The glyph comes
|
|
2254
|
-
from --icon-close, masked to currentColor, so no icon font or text glyph is
|
|
2255
|
-
required in the markup. */
|
|
2256
|
-
.dialog-close,
|
|
2257
|
-
.drawer-close {
|
|
2258
|
-
--dialog-close-icon-size: 1rem;
|
|
2259
|
-
|
|
2260
|
-
display: inline-grid;
|
|
2261
|
-
place-items: center;
|
|
2262
|
-
padding: 0;
|
|
2263
|
-
border: 0;
|
|
2264
|
-
border-radius: var(--radius);
|
|
2265
|
-
color: var(--text-muted);
|
|
2266
|
-
cursor: pointer;
|
|
2267
|
-
|
|
2268
|
-
/* Out of flow, anchored to the positioned dialog surface, not the header or
|
|
2269
|
-
any scroll container inside it. */
|
|
2270
|
-
position: absolute;
|
|
2271
|
-
z-index: 1;
|
|
2272
|
-
inset-block-start: var(--space-20);
|
|
2273
|
-
inset-inline-end: var(--space-20);
|
|
2274
|
-
|
|
2275
|
-
inline-size: var(--control-size);
|
|
2276
|
-
block-size: var(--control-size);
|
|
2277
|
-
|
|
2278
|
-
background: var(--surface-subtle);
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
.dialog-close::before,
|
|
2282
|
-
.drawer-close::before {
|
|
2283
|
-
content: "";
|
|
2284
|
-
inline-size: var(--dialog-close-icon-size);
|
|
2285
|
-
block-size: var(--dialog-close-icon-size);
|
|
2286
|
-
background: currentColor;
|
|
2287
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
2288
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
@media (hover: hover) {
|
|
2292
|
-
.dialog-close:hover,
|
|
2293
|
-
.drawer-close:hover {
|
|
2294
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
2295
|
-
color: var(--text);
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
.dialog-close:active,
|
|
2300
|
-
.drawer-close:active {
|
|
2301
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
.dialog-close:focus-visible,
|
|
2305
|
-
.drawer-close:focus-visible {
|
|
2306
|
-
outline: 2px solid transparent;
|
|
2307
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
/*
|
|
2311
|
-
* Drawer — modal side-sheet built on the native dialog element.
|
|
2312
|
-
*
|
|
2313
|
-
* Top-layer rendering, ::backdrop, Escape, and modal focus come from the
|
|
2314
|
-
* native dialog. CSS owns progressive enter/exit motion where browsers support
|
|
2315
|
-
* discrete display/overlay transitions. The drawer is a layout shell only —
|
|
2316
|
-
* navigation content inside uses .nav-link and .accordion, which carry their
|
|
2317
|
-
* own link/summary styling.
|
|
2318
|
-
*
|
|
2319
|
-
* Public hooks:
|
|
2320
|
-
* --drawer-size
|
|
2321
|
-
* --drawer-pad
|
|
2322
|
-
*
|
|
2323
|
-
* Internal:
|
|
2324
|
-
* --drawer-closed-translate (side/RTL positioning state)
|
|
2325
|
-
*/
|
|
2326
|
-
dialog.drawer {
|
|
2327
|
-
--drawer-size: 20rem;
|
|
2328
|
-
--drawer-pad: var(--space-40);
|
|
2329
|
-
--drawer-closed-translate: -100%;
|
|
2330
|
-
|
|
2331
|
-
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
2332
|
-
provides the containing block for the absolutely positioned close button.
|
|
2333
|
-
A percentage height resolves against the fixed containing viewport, while
|
|
2334
|
-
dynamic viewport units can be stale in embedded previews. */
|
|
2335
|
-
position: fixed;
|
|
2336
|
-
inset-block-start: 0;
|
|
2337
|
-
|
|
2338
|
-
inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
|
|
2339
|
-
block-size: 100%;
|
|
2340
|
-
max-inline-size: none;
|
|
2341
|
-
max-block-size: 100%;
|
|
2342
|
-
margin: 0;
|
|
2343
|
-
padding: var(--drawer-pad);
|
|
2344
|
-
border: 0;
|
|
2345
|
-
border-inline-end: var(--border-width) solid var(--border);
|
|
2346
|
-
border-radius: 0;
|
|
2347
|
-
background: var(--surface-raised);
|
|
2348
|
-
color: var(--text);
|
|
2349
|
-
box-shadow: var(--shadow);
|
|
2350
|
-
opacity: 0;
|
|
2351
|
-
transform: translateX(var(--drawer-closed-translate));
|
|
2352
|
-
transition:
|
|
2353
|
-
opacity var(--duration-slow),
|
|
2354
|
-
transform var(--duration-slow);
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
dialog.drawer[open] {
|
|
2358
|
-
display: grid;
|
|
2359
|
-
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
2360
|
-
gap: var(--space-40);
|
|
2361
|
-
opacity: 1;
|
|
2362
|
-
transform: translateX(0);
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
/* Backdrop color sourced from components/surface.css so it matches .modal
|
|
2366
|
-
and .surface-backdrop across the dialog family. */
|
|
2367
|
-
dialog.drawer::backdrop {
|
|
2368
|
-
background: var(--backdrop-fill);
|
|
2369
|
-
opacity: 0;
|
|
2370
|
-
transition: opacity var(--duration-slow);
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
dialog.drawer[data-side="end"] {
|
|
2374
|
-
--drawer-closed-translate: 100%;
|
|
2375
|
-
|
|
2376
|
-
margin-inline-start: auto;
|
|
2377
|
-
margin-inline-end: 0;
|
|
2378
|
-
border-inline-end: 0;
|
|
2379
|
-
border-inline-start: var(--border-width) solid var(--border);
|
|
2380
|
-
}
|
|
2381
|
-
|
|
2382
|
-
dialog.drawer[open]::backdrop {
|
|
2383
|
-
opacity: 1;
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
@starting-style {
|
|
2387
|
-
dialog.drawer[open] {
|
|
2388
|
-
opacity: 0;
|
|
2389
|
-
transform: translateX(var(--drawer-closed-translate));
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
dialog.drawer[open]::backdrop {
|
|
2393
|
-
opacity: 0;
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
/* Scrollable navigation region inside the drawer. */
|
|
2398
|
-
dialog.drawer nav {
|
|
2399
|
-
min-block-size: 0;
|
|
2400
|
-
overflow-y: auto;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
@supports (transition-behavior: allow-discrete) {
|
|
2404
|
-
dialog.drawer {
|
|
2405
|
-
transition:
|
|
2406
|
-
opacity var(--duration-slow),
|
|
2407
|
-
transform var(--duration-slow),
|
|
2408
|
-
overlay var(--duration-slow) allow-discrete,
|
|
2409
|
-
display var(--duration-slow) allow-discrete;
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
dialog.drawer::backdrop {
|
|
2413
|
-
transition:
|
|
2414
|
-
opacity var(--duration-slow),
|
|
2415
|
-
overlay var(--duration-slow) allow-discrete,
|
|
2416
|
-
display var(--duration-slow) allow-discrete;
|
|
2417
|
-
}
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
/* RTL: swap closed transform directions so the drawer slides from the correct
|
|
2421
|
-
inline side. */
|
|
2422
|
-
[dir="rtl"] dialog.drawer {
|
|
2423
|
-
--drawer-closed-translate: 100%;
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
[dir="rtl"] dialog.drawer[data-side="end"] {
|
|
2427
|
-
--drawer-closed-translate: -100%;
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
@media (forced-colors: active) {
|
|
2431
|
-
dialog.drawer::backdrop {
|
|
2432
|
-
background: Canvas;
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
2437
|
-
dialog.drawer.is-static {
|
|
2438
|
-
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
/* The close button shares the modal's interior placement (modal.css): absolute
|
|
2443
|
-
at the panel's top inline-end, out of the content flow. The header reserves
|
|
2444
|
-
inline room so the title never runs under it. */
|
|
2445
|
-
dialog.drawer > header,
|
|
2446
|
-
dialog.drawer > form > header {
|
|
2447
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
/* Overlays */
|
|
2452
|
-
/*
|
|
2453
|
-
* Flyout — floating panel for actions and links.
|
|
2454
|
-
*
|
|
2455
|
-
* JavaScript owns positioning (position: fixed + left/top), state
|
|
2456
|
-
* (hidden + .is-open), focus, and keyboard behavior. CSS owns the surfaced
|
|
2457
|
-
* panel appearance, item styles, and animation.
|
|
2458
|
-
*
|
|
2459
|
-
* Runtime markers (managed by surface.js): .is-open marks an open flyout,
|
|
2460
|
-
* .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
|
|
2461
|
-
* pointers. Authors opt into mobile behavior with
|
|
2462
|
-
* [data-flyout-mobile="sheet|auto|none|anchored"].
|
|
2463
|
-
* --surface-anchor-width is set by surface.js while an open flyout is positioned.
|
|
2464
|
-
*
|
|
2465
|
-
* Public hooks:
|
|
2466
|
-
* --flyout-inline-size
|
|
2467
|
-
* --flyout-max-inline-size
|
|
2468
|
-
*
|
|
2469
|
-
* Internal:
|
|
2470
|
-
* --menu-item-size (derived from --control-size for menu rows)
|
|
2471
|
-
* --available-height (measured by floating.js)
|
|
2472
|
-
* --surface-anchor-width (measured by surface.js)
|
|
2473
|
-
*/
|
|
2474
|
-
|
|
2475
|
-
.flyout-trigger {
|
|
2476
|
-
position: relative;
|
|
2477
|
-
display: inline-block;
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
/* Stretch: for a flyout trigger that must span its container — e.g. the
|
|
2481
|
-
last row of a full-width sidebar nav list. Stretches both the
|
|
2482
|
-
positioning wrapper and its trigger control. Named for the effect
|
|
2483
|
-
(stretch to fill), not the display value, to avoid reading like a
|
|
2484
|
-
generic display-block utility. */
|
|
2485
|
-
.flyout-trigger.stretch {
|
|
2486
|
-
display: block;
|
|
2487
|
-
inline-size: 100%;
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
.flyout-trigger.stretch > [aria-haspopup="menu"] {
|
|
2491
|
-
inline-size: 100%;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
.flyout {
|
|
2495
|
-
--menu-item-size: var(--control-size);
|
|
2496
|
-
z-index: var(--z-menu);
|
|
2497
|
-
/* No-JS fallback: below the trigger inside the relative .flyout-trigger.
|
|
2498
|
-
surface.js switches an open flyout to position: fixed with measured
|
|
2499
|
-
coordinates; this only covers a flyout revealed without the runtime. */
|
|
2500
|
-
position: absolute;
|
|
2501
|
-
inset-block-start: calc(100% + var(--space-10));
|
|
2502
|
-
inset-inline-start: 0;
|
|
2503
|
-
display: grid;
|
|
2504
|
-
gap: var(--space-10);
|
|
2505
|
-
inline-size: var(--flyout-inline-size, max-content);
|
|
2506
|
-
min-inline-size: min(
|
|
2507
|
-
var(--surface-anchor-width, 0px),
|
|
2508
|
-
min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-60)))
|
|
2509
|
-
);
|
|
2510
|
-
max-inline-size: min(
|
|
2511
|
-
var(--flyout-max-inline-size, 20rem),
|
|
2512
|
-
calc(var(--viewport-inline) - var(--space-60))
|
|
2513
|
-
);
|
|
2514
|
-
max-block-size: min(
|
|
2515
|
-
24rem,
|
|
2516
|
-
var(--available-height, calc(var(--viewport-block) - var(--space-60)))
|
|
2517
|
-
);
|
|
2518
|
-
overflow: auto;
|
|
2519
|
-
margin: 0;
|
|
2520
|
-
padding: var(--space-10);
|
|
2521
|
-
border: var(--border-width) solid var(--border);
|
|
2522
|
-
border-radius: var(--radius);
|
|
2523
|
-
background: var(--surface-raised);
|
|
2524
|
-
box-shadow: var(--shadow-popout);
|
|
2525
|
-
list-style: none;
|
|
2526
|
-
opacity: 1;
|
|
2527
|
-
scale: 1;
|
|
2528
|
-
transition:
|
|
2529
|
-
opacity var(--duration-fast) var(--ease),
|
|
2530
|
-
scale var(--duration-fast) var(--ease),
|
|
2531
|
-
translate var(--duration-fast) var(--ease);
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
.flyout.is-open:not(.is-sheet) {
|
|
2535
|
-
inset: auto;
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
@starting-style {
|
|
2539
|
-
.flyout.is-open {
|
|
2540
|
-
opacity: 0;
|
|
2541
|
-
scale: 0.98;
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
/* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
|
|
2546
|
-
opened on coarse pointer / narrow viewports. */
|
|
2547
|
-
.flyout.is-sheet {
|
|
2548
|
-
--menu-item-size: var(--control-size-lg);
|
|
2549
|
-
position: fixed;
|
|
2550
|
-
inset-inline: 0;
|
|
2551
|
-
inset-block-start: auto;
|
|
2552
|
-
inset-block-end: 0;
|
|
2553
|
-
inline-size: auto;
|
|
2554
|
-
min-inline-size: 0;
|
|
2555
|
-
max-inline-size: none;
|
|
2556
|
-
max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
|
|
2557
|
-
overflow: auto;
|
|
2558
|
-
grid-template-columns: 1fr;
|
|
2559
|
-
padding: max(var(--space-30), env(safe-area-inset-top)) var(--space-30)
|
|
2560
|
-
max(var(--space-30), env(safe-area-inset-bottom));
|
|
2561
|
-
border-end-start-radius: 0;
|
|
2562
|
-
border-end-end-radius: 0;
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2565
|
-
.flyout[hidden] {
|
|
2566
|
-
display: none;
|
|
2567
|
-
opacity: 0;
|
|
2568
|
-
scale: 0.98;
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
.flyout.is-sheet[hidden] {
|
|
2572
|
-
scale: 1;
|
|
2573
|
-
translate: 0 var(--space-30);
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
@starting-style {
|
|
2577
|
-
.flyout.is-sheet.is-open {
|
|
2578
|
-
scale: 1;
|
|
2579
|
-
translate: 0 var(--space-30);
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
|
-
@supports (transition-behavior: allow-discrete) {
|
|
2584
|
-
.flyout {
|
|
2585
|
-
transition:
|
|
2586
|
-
opacity var(--duration-fast) var(--ease),
|
|
2587
|
-
scale var(--duration-fast) var(--ease),
|
|
2588
|
-
translate var(--duration-fast) var(--ease),
|
|
2589
|
-
display var(--duration-fast) allow-discrete;
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
.flyout > li {
|
|
2594
|
-
margin: 0;
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
.flyout > :where(section, footer) {
|
|
2598
|
-
min-inline-size: 0;
|
|
2599
|
-
padding: var(--space-20);
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
.flyout > section > :where(h1, h2, h3, h4, h5, h6) {
|
|
2603
|
-
margin: 0;
|
|
2604
|
-
color: var(--text-muted);
|
|
2605
|
-
font-size: var(--font-size-xs);
|
|
2606
|
-
font-weight: var(--font-weight-strong);
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
.flyout > section > ul {
|
|
2610
|
-
display: grid;
|
|
2611
|
-
gap: var(--space-10);
|
|
2612
|
-
margin: var(--space-10) 0 0;
|
|
2613
|
-
padding: 0;
|
|
2614
|
-
list-style: none;
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
.flyout > section > ul > li {
|
|
2618
|
-
margin: 0;
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
/* Vertical dividers — .menu-separator (menu.css) is the canonical way to
|
|
2622
|
-
separate menu items. Flyout panels that are not menus use auto grid gap
|
|
2623
|
-
declared on .flyout itself. */
|
|
2624
|
-
|
|
2625
|
-
/*
|
|
2626
|
-
* Menu — action list primitive consumed by flyout and context-menu.
|
|
2627
|
-
*
|
|
2628
|
-
* Strict anatomy: .menu > li > .menu-item. Separators and section labels
|
|
2629
|
-
* sit directly under .menu as siblings of <li>.
|
|
2630
|
-
*
|
|
2631
|
-
* Public hooks:
|
|
2632
|
-
* --menu-item-icon-size
|
|
2633
|
-
*/
|
|
2634
|
-
|
|
2635
|
-
.menu {
|
|
2636
|
-
display: grid;
|
|
2637
|
-
gap: var(--space-10);
|
|
2638
|
-
margin: 0;
|
|
2639
|
-
padding: var(--space-10);
|
|
2640
|
-
list-style: none;
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
.menu > li {
|
|
2644
|
-
margin: 0;
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
.menu-item {
|
|
2648
|
-
--menu-item-icon-size: 1.25em;
|
|
2649
|
-
|
|
2650
|
-
display: flex;
|
|
2651
|
-
align-items: center;
|
|
2652
|
-
gap: var(--space-20);
|
|
2653
|
-
inline-size: 100%;
|
|
2654
|
-
min-block-size: var(--menu-item-size, var(--control-size));
|
|
2655
|
-
padding-inline: var(--space-20);
|
|
2656
|
-
border: 0;
|
|
2657
|
-
border-radius: var(--radius-sm);
|
|
2658
|
-
background: transparent;
|
|
2659
|
-
color: var(--text);
|
|
2660
|
-
font: inherit;
|
|
2661
|
-
text-align: start;
|
|
2662
|
-
text-decoration: none;
|
|
2663
|
-
white-space: nowrap;
|
|
2664
|
-
cursor: pointer;
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
.menu-item-icon {
|
|
2668
|
-
display: grid;
|
|
2669
|
-
flex: 0 0 var(--menu-item-icon-size);
|
|
2670
|
-
inline-size: var(--menu-item-icon-size);
|
|
2671
|
-
place-items: center;
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
.menu-item-text {
|
|
2675
|
-
flex: 1;
|
|
2676
|
-
min-inline-size: 0;
|
|
2677
|
-
overflow: hidden;
|
|
2678
|
-
text-overflow: ellipsis;
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
.menu-item-end {
|
|
2682
|
-
display: inline-flex;
|
|
2683
|
-
align-items: center;
|
|
2684
|
-
gap: var(--space-10);
|
|
2685
|
-
margin-inline-start: auto;
|
|
2686
|
-
color: var(--text-muted);
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
/* Checkable menu items are one command with one accessible state. Their
|
|
2690
|
-
indicator is generated so authors do not add a second interactive input. */
|
|
2691
|
-
.menu-item:is([role="menuitemcheckbox"], [role="menuitemradio"])::before {
|
|
2692
|
-
display: grid;
|
|
2693
|
-
flex: 0 0 var(--menu-item-icon-size);
|
|
2694
|
-
inline-size: var(--menu-item-icon-size);
|
|
2695
|
-
place-items: center;
|
|
2696
|
-
}
|
|
2697
|
-
|
|
2698
|
-
.menu-item[role="menuitemcheckbox"]::before {
|
|
2699
|
-
content: "";
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before {
|
|
2703
|
-
content: "✓";
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before {
|
|
2707
|
-
content: "−";
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
.menu-item[role="menuitemradio"]::before {
|
|
2711
|
-
content: "○";
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
.menu-item[role="menuitemradio"][aria-checked="true"]::before {
|
|
2715
|
-
content: "●";
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
.menu-item:focus-visible {
|
|
2719
|
-
background: var(--surface-subtle);
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
.menu-item:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
2723
|
-
background: var(--surface-subtle);
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
.menu-item:is(:disabled, [aria-disabled="true"]) {
|
|
2727
|
-
cursor: not-allowed;
|
|
2728
|
-
opacity: var(--disabled-opacity);
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
.menu-label {
|
|
2732
|
-
padding: var(--space-20);
|
|
2733
|
-
color: var(--text-muted);
|
|
2734
|
-
font-size: var(--font-size-xs);
|
|
2735
|
-
font-weight: var(--font-weight-strong);
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
|
-
.menu-separator {
|
|
2739
|
-
margin: 0;
|
|
2740
|
-
border: 0;
|
|
2741
|
-
border-block-start: var(--border-width) solid var(--border);
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
@media (forced-colors: active) {
|
|
2745
|
-
.menu-item:focus-visible {
|
|
2746
|
-
outline: var(--border-width) solid ButtonText;
|
|
2747
|
-
outline-offset: -1px;
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
/*
|
|
2752
|
-
* Tooltip — supplemental label overlaid near a trigger.
|
|
2753
|
-
*
|
|
2754
|
-
* Position, placement, and arrow position are managed by JavaScript
|
|
2755
|
-
* via position: fixed, left, top, data-placement, and --arrow-x.
|
|
2756
|
-
* Visibility is managed with hidden; CSS owns entry and exit animation.
|
|
2757
|
-
*
|
|
2758
|
-
* Public hooks:
|
|
2759
|
-
* --tooltip-bg
|
|
2760
|
-
* --tooltip-fg
|
|
2761
|
-
* --tooltip-arrow-size
|
|
2762
|
-
*
|
|
2763
|
-
* Internal:
|
|
2764
|
-
* --arrow-x (arrow position, written by floating.js)
|
|
2765
|
-
* --arrow-y (arrow position, written by floating.js)
|
|
2766
|
-
*/
|
|
2767
|
-
.tooltip {
|
|
2768
|
-
--tooltip-bg: var(--surface-solid);
|
|
2769
|
-
--tooltip-fg: var(--surface);
|
|
2770
|
-
--tooltip-arrow-size: 5px;
|
|
2771
|
-
|
|
2772
|
-
position: fixed;
|
|
2773
|
-
z-index: var(--z-tooltip);
|
|
2774
|
-
max-inline-size: min(24rem, calc(var(--viewport-inline) - 2rem));
|
|
2775
|
-
padding: 0.25em 0.6em;
|
|
2776
|
-
border-radius: var(--radius-sm);
|
|
2777
|
-
background: var(--tooltip-bg);
|
|
2778
|
-
color: var(--tooltip-fg);
|
|
2779
|
-
font-size: var(--font-size-xs);
|
|
2780
|
-
line-height: var(--line-height-tight);
|
|
2781
|
-
overflow-wrap: anywhere;
|
|
2782
|
-
pointer-events: auto;
|
|
2783
|
-
user-select: none;
|
|
2784
|
-
opacity: 1;
|
|
2785
|
-
scale: 1;
|
|
2786
|
-
transition:
|
|
2787
|
-
opacity var(--duration-fast) var(--ease),
|
|
2788
|
-
scale var(--duration-fast) var(--ease);
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
.tooltip[hidden] {
|
|
2792
|
-
display: none;
|
|
2793
|
-
opacity: 0;
|
|
2794
|
-
scale: 0.96;
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
@starting-style {
|
|
2798
|
-
.tooltip:not([hidden]) {
|
|
2799
|
-
opacity: 0;
|
|
2800
|
-
scale: 0.96;
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
@supports (transition-behavior: allow-discrete) {
|
|
2805
|
-
.tooltip {
|
|
2806
|
-
transition:
|
|
2807
|
-
opacity var(--duration-fast) var(--ease),
|
|
2808
|
-
scale var(--duration-fast) var(--ease),
|
|
2809
|
-
display var(--duration-fast) allow-discrete;
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
/* ── Arrow — clipped triangle ──────────────────────── */
|
|
2814
|
-
|
|
2815
|
-
.tooltip::before {
|
|
2816
|
-
content: "";
|
|
2817
|
-
position: absolute;
|
|
2818
|
-
inline-size: calc(var(--tooltip-arrow-size) * 2);
|
|
2819
|
-
block-size: var(--tooltip-arrow-size);
|
|
2820
|
-
background: var(--tooltip-bg);
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
|
-
.tooltip[data-placement^="top"]::before {
|
|
2824
|
-
bottom: calc(-0.98 * var(--tooltip-arrow-size));
|
|
2825
|
-
left: var(--arrow-x, 50%);
|
|
2826
|
-
translate: -50% 0;
|
|
2827
|
-
clip-path: polygon(50% 100%, 0 0, 100% 0);
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
.tooltip[data-placement^="bottom"]::before {
|
|
2831
|
-
top: calc(-0.98 * var(--tooltip-arrow-size));
|
|
2832
|
-
left: var(--arrow-x, 50%);
|
|
2833
|
-
translate: -50% 0;
|
|
2834
|
-
clip-path: polygon(50% 0, 0 100%, 100% 100%);
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
.tooltip[data-placement^="left"]::before {
|
|
2838
|
-
right: calc(-0.98 * var(--tooltip-arrow-size));
|
|
2839
|
-
top: var(--arrow-y, 50%);
|
|
2840
|
-
inline-size: var(--tooltip-arrow-size);
|
|
2841
|
-
block-size: calc(var(--tooltip-arrow-size) * 2);
|
|
2842
|
-
translate: 0 -50%;
|
|
2843
|
-
clip-path: polygon(100% 50%, 0 0, 0 100%);
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
.tooltip[data-placement^="right"]::before {
|
|
2847
|
-
left: calc(-0.98 * var(--tooltip-arrow-size));
|
|
2848
|
-
top: var(--arrow-y, 50%);
|
|
2849
|
-
inline-size: var(--tooltip-arrow-size);
|
|
2850
|
-
block-size: calc(var(--tooltip-arrow-size) * 2);
|
|
2851
|
-
translate: 0 -50%;
|
|
2852
|
-
clip-path: polygon(0 50%, 100% 0, 100% 100%);
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
@media (forced-colors: active) {
|
|
2856
|
-
.tooltip {
|
|
2857
|
-
--tooltip-bg: Canvas;
|
|
2858
|
-
--tooltip-fg: CanvasText;
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
.tooltip::before {
|
|
2862
|
-
display: none;
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
/* Disclosure */
|
|
2868
|
-
/*
|
|
2869
|
-
* Accordion disclosures built on native details and summary.
|
|
2870
|
-
*
|
|
2871
|
-
* Keeps the clickable summary affordance aligned and usable without JavaScript
|
|
2872
|
-
* or demo styles. Decorative palettes and custom markers belong outside the
|
|
2873
|
-
* base component.
|
|
2874
|
-
*
|
|
2875
|
-
* Public hooks:
|
|
2876
|
-
* --accordion-radius
|
|
2877
|
-
* --accordion-marker-color
|
|
2878
|
-
*/
|
|
2879
|
-
.accordion {
|
|
2880
|
-
--accordion-radius: var(--radius);
|
|
2881
|
-
--accordion-marker-color: var(--text-muted);
|
|
2882
|
-
|
|
2883
|
-
border: var(--border-width) solid var(--border);
|
|
2884
|
-
border-radius: var(--accordion-radius);
|
|
2885
|
-
background: var(--surface-raised);
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
.accordion > details + details {
|
|
2889
|
-
border-block-start: var(--border-width) solid var(--border);
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
.accordion summary {
|
|
2893
|
-
display: flex;
|
|
2894
|
-
align-items: center;
|
|
2895
|
-
gap: var(--space-30);
|
|
2896
|
-
cursor: pointer;
|
|
2897
|
-
list-style: none;
|
|
2898
|
-
padding: var(--space-40);
|
|
2899
|
-
color: var(--text);
|
|
2900
|
-
font-weight: var(--font-weight-bold);
|
|
2901
|
-
}
|
|
2902
|
-
|
|
2903
|
-
.accordion summary:hover {
|
|
2904
|
-
color: var(--primary);
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
.accordion summary::-webkit-details-marker {
|
|
2908
|
-
display: none;
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
.accordion summary::after {
|
|
2912
|
-
content: "";
|
|
2913
|
-
flex: 0 0 auto;
|
|
2914
|
-
margin-inline-start: auto;
|
|
2915
|
-
inline-size: 1rem;
|
|
2916
|
-
block-size: 1rem;
|
|
2917
|
-
background-color: var(--accordion-marker-color);
|
|
2918
|
-
-webkit-mask: var(--icon-chevron) center / contain no-repeat;
|
|
2919
|
-
mask: var(--icon-chevron) center / contain no-repeat;
|
|
2920
|
-
transition: transform var(--duration);
|
|
2921
|
-
}
|
|
2922
|
-
|
|
2923
|
-
.accordion details[open] summary::after {
|
|
2924
|
-
transform: rotate(180deg);
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
.accordion details > summary + * {
|
|
2928
|
-
padding-block: 0 var(--space-40);
|
|
2929
|
-
padding-inline: var(--space-40);
|
|
2930
|
-
color: var(--text-muted);
|
|
2931
|
-
margin-block: 0;
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
.accordion details[open] > summary + * {
|
|
2935
|
-
padding-block-start: var(--space-10);
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
.accordion details > summary + * > :first-child {
|
|
2939
|
-
margin-block-start: 0;
|
|
2940
|
-
}
|
|
2941
|
-
|
|
2942
|
-
.accordion details > summary + ul,
|
|
2943
|
-
.accordion details > summary + ol {
|
|
2944
|
-
margin: 0;
|
|
2945
|
-
padding-inline-start: 2rem;
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
@media (forced-colors: active) {
|
|
2949
|
-
.accordion {
|
|
2950
|
-
--accordion-marker-color: CanvasText;
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
/* Feedback */
|
|
2956
|
-
/*
|
|
2957
|
-
* Spinner — compact loading feedback.
|
|
2958
|
-
*
|
|
2959
|
-
* Uses currentColor so it adapts to buttons, alerts, badges, and text. The
|
|
2960
|
-
* [aria-busy] overlay composition lives in components/busy.css.
|
|
2961
|
-
*
|
|
2962
|
-
* Public hooks:
|
|
2963
|
-
* --spinner-size
|
|
2964
|
-
* --spinner-width
|
|
2965
|
-
* --spinner-track
|
|
2966
|
-
* --spinner-value
|
|
2967
|
-
*/
|
|
2968
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
2969
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
2970
|
-
:where(.spinner) {
|
|
2971
|
-
--intent: initial;
|
|
2972
|
-
}
|
|
2973
|
-
|
|
2974
|
-
.spinner {
|
|
2975
|
-
--spinner-size: 1em;
|
|
2976
|
-
--spinner-width: 0.125em;
|
|
2977
|
-
--spinner-track: currentColor;
|
|
2978
|
-
--spinner-value: transparent;
|
|
2979
|
-
|
|
2980
|
-
color: var(--intent, currentColor);
|
|
2981
|
-
display: inline-block;
|
|
2982
|
-
flex-shrink: 0;
|
|
2983
|
-
inline-size: var(--spinner-size);
|
|
2984
|
-
block-size: var(--spinner-size);
|
|
2985
|
-
border: var(--spinner-width) solid var(--spinner-track);
|
|
2986
|
-
border-inline-end-color: var(--spinner-value);
|
|
2987
|
-
border-radius: var(--radius-full);
|
|
2988
|
-
opacity: 0.85;
|
|
2989
|
-
animation: actual-spin var(--duration-spin) linear infinite;
|
|
2990
|
-
vertical-align: text-bottom;
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
2994
|
-
.spinner {
|
|
2995
|
-
--spinner-track: color-mix(in oklch, currentColor, transparent 70%);
|
|
2996
|
-
--spinner-value: currentColor;
|
|
2997
|
-
opacity: 1;
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
|
|
3001
|
-
.spinner.sm {
|
|
3002
|
-
--spinner-size: 0.75em;
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
.spinner.lg {
|
|
3006
|
-
--spinner-size: 2rem;
|
|
3007
|
-
--spinner-width: 3px;
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
@keyframes actual-spin {
|
|
3011
|
-
to {
|
|
3012
|
-
transform: rotate(360deg);
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
|
|
3016
|
-
/*
|
|
3017
|
-
* Skeleton placeholders for loading states.
|
|
3018
|
-
*
|
|
3019
|
-
* Shows approximate loading structure without implying real content hierarchy.
|
|
3020
|
-
* Geometry, animation, and color stay theme-aware.
|
|
3021
|
-
*
|
|
3022
|
-
* Public hooks:
|
|
3023
|
-
* --skeleton-size
|
|
3024
|
-
* --skeleton-width
|
|
3025
|
-
* --skeleton-radius
|
|
3026
|
-
* --skeleton-track
|
|
3027
|
-
* --skeleton-highlight
|
|
3028
|
-
*/
|
|
3029
|
-
.skeleton {
|
|
3030
|
-
--skeleton-size: 1rem;
|
|
3031
|
-
--skeleton-width: 100%;
|
|
3032
|
-
--skeleton-radius: var(--radius);
|
|
3033
|
-
--skeleton-track: var(--surface-subtle);
|
|
3034
|
-
--skeleton-highlight: var(--surface-raised);
|
|
3035
|
-
|
|
3036
|
-
display: block;
|
|
3037
|
-
inline-size: var(--skeleton-width);
|
|
3038
|
-
max-inline-size: 100%;
|
|
3039
|
-
block-size: var(--skeleton-size);
|
|
3040
|
-
border-radius: var(--skeleton-radius);
|
|
3041
|
-
background:
|
|
3042
|
-
linear-gradient(90deg, var(--skeleton-track), var(--skeleton-highlight), var(--skeleton-track))
|
|
3043
|
-
0 0 / 200% 100%;
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
3047
|
-
.skeleton {
|
|
3048
|
-
animation: actual-skeleton var(--duration-shimmer) ease-in-out infinite;
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
.skeleton[data-shape="text"] {
|
|
3053
|
-
--skeleton-size: 0.85rem;
|
|
3054
|
-
--skeleton-width: 32rem;
|
|
3055
|
-
}
|
|
3056
|
-
|
|
3057
|
-
.skeleton[data-shape="title"] {
|
|
3058
|
-
--skeleton-size: 1.4rem;
|
|
3059
|
-
--skeleton-width: 18rem;
|
|
3060
|
-
}
|
|
3061
|
-
|
|
3062
|
-
.skeleton[data-shape="avatar"] {
|
|
3063
|
-
--skeleton-size: var(--avatar-size, 2.25rem);
|
|
3064
|
-
--skeleton-width: var(--skeleton-size);
|
|
3065
|
-
--skeleton-radius: var(--radius-full);
|
|
3066
|
-
|
|
3067
|
-
flex: 0 0 auto;
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
.skeleton[data-shape="box"] {
|
|
3071
|
-
--skeleton-size: 8rem;
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
@media (forced-colors: active) {
|
|
3075
|
-
.skeleton {
|
|
3076
|
-
border: var(--border-width) solid ButtonText;
|
|
3077
|
-
background: GrayText;
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
@keyframes actual-skeleton {
|
|
3082
|
-
from {
|
|
3083
|
-
background-position: 200% 0;
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
|
-
to {
|
|
3087
|
-
background-position: -200% 0;
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
|
|
3091
|
-
/*
|
|
3092
|
-
* Busy overlay — non-button loading state.
|
|
3093
|
-
*
|
|
3094
|
-
* A non-button element with aria-busy="true" and a direct last-child .spinner
|
|
3095
|
-
* shows that spinner as a centered overlay over a faded surface. Buttons keep
|
|
3096
|
-
* their spinners inline (e.g. .btn[aria-busy] > .spinner stays inline-flex).
|
|
3097
|
-
*
|
|
3098
|
-
* Public hooks:
|
|
3099
|
-
* --busy-overlay-bg
|
|
3100
|
-
*
|
|
3101
|
-
* Internal:
|
|
3102
|
-
* --spinner-size (overlay copy of the shared spinner hook)
|
|
3103
|
-
*/
|
|
3104
|
-
[aria-busy] > .spinner:last-child {
|
|
3105
|
-
display: none;
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
[aria-busy="true"] > .spinner:last-child {
|
|
3109
|
-
display: inline-block;
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3112
|
-
@supports selector([aria-busy="true"]:has(> .spinner:last-child)) {
|
|
3113
|
-
[aria-busy="true"]:not(.btn):has(> .spinner:last-child) {
|
|
3114
|
-
position: relative;
|
|
3115
|
-
}
|
|
3116
|
-
|
|
3117
|
-
[aria-busy="true"]:not(.btn):has(> .spinner:last-child)::before {
|
|
3118
|
-
content: "";
|
|
3119
|
-
position: absolute;
|
|
3120
|
-
inset: 0;
|
|
3121
|
-
z-index: 1;
|
|
3122
|
-
border-radius: inherit;
|
|
3123
|
-
background: var(--busy-overlay-bg, var(--surface));
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
[aria-busy="true"]:not(.btn) > .spinner:last-child {
|
|
3127
|
-
--spinner-size: 2rem;
|
|
3128
|
-
position: absolute;
|
|
3129
|
-
inset: 0;
|
|
3130
|
-
z-index: 2;
|
|
3131
|
-
margin: auto;
|
|
3132
|
-
display: block;
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
|
-
@supports (selector([aria-busy="true"]:has(> .spinner:last-child))) and
|
|
3137
|
-
(color: color-mix(in oklch, red, white)) {
|
|
3138
|
-
[aria-busy="true"]:not(.btn):has(> .spinner:last-child)::before {
|
|
3139
|
-
background: color-mix(in oklch, var(--busy-overlay-bg, var(--surface)), transparent 20%);
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
|
-
/*
|
|
3144
|
-
* Progress — native progress bar with theme-aware track and value.
|
|
3145
|
-
*
|
|
3146
|
-
* Consumes the shared --intent/--primary for the value bar and --bar-height
|
|
3147
|
-
* for thickness. Indeterminate state animates a shimmer using --duration-shimmer.
|
|
3148
|
-
*
|
|
3149
|
-
* Public hooks:
|
|
3150
|
-
* --progress-track
|
|
3151
|
-
* --progress-value
|
|
3152
|
-
*/
|
|
3153
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3154
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3155
|
-
:where(.progress) {
|
|
3156
|
-
--intent: initial;
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
.progress {
|
|
3160
|
-
--progress-track: var(--surface-subtle);
|
|
3161
|
-
--progress-value: var(--intent, var(--primary));
|
|
3162
|
-
|
|
3163
|
-
appearance: none;
|
|
3164
|
-
-webkit-appearance: none;
|
|
3165
|
-
display: block;
|
|
3166
|
-
inline-size: 100%;
|
|
3167
|
-
block-size: var(--bar-height);
|
|
3168
|
-
overflow: hidden;
|
|
3169
|
-
border: 0;
|
|
3170
|
-
border-radius: var(--radius-full);
|
|
3171
|
-
background: var(--progress-track);
|
|
3172
|
-
}
|
|
3173
|
-
|
|
3174
|
-
.progress::-webkit-progress-bar {
|
|
3175
|
-
block-size: var(--bar-height);
|
|
3176
|
-
border-radius: var(--radius-full);
|
|
3177
|
-
background: var(--progress-track);
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
|
-
.progress::-webkit-progress-value {
|
|
3181
|
-
block-size: var(--bar-height);
|
|
3182
|
-
border-radius: var(--radius-full);
|
|
3183
|
-
background: var(--progress-value);
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
.progress::-moz-progress-bar {
|
|
3187
|
-
block-size: var(--bar-height);
|
|
3188
|
-
border-radius: var(--radius-full);
|
|
3189
|
-
background: var(--progress-value);
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
/* Indeterminate progress */
|
|
3193
|
-
.progress:indeterminate {
|
|
3194
|
-
background:
|
|
3195
|
-
linear-gradient(to right, var(--progress-value) 30%, var(--progress-track) 30%) top left / 150%
|
|
3196
|
-
150% no-repeat,
|
|
3197
|
-
var(--progress-track);
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
.progress:indeterminate::-webkit-progress-bar {
|
|
3201
|
-
background: transparent;
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
.progress:indeterminate::-webkit-progress-value {
|
|
3205
|
-
background: transparent;
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
.progress:indeterminate::-moz-progress-bar {
|
|
3209
|
-
background: transparent;
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
3213
|
-
.progress:indeterminate {
|
|
3214
|
-
animation: actual-progress-indeterminate var(--duration-shimmer) ease-in-out infinite;
|
|
3215
|
-
}
|
|
3216
|
-
|
|
3217
|
-
:where([dir="rtl"] .progress:indeterminate) {
|
|
3218
|
-
animation-direction: reverse;
|
|
3219
|
-
}
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
@keyframes actual-progress-indeterminate {
|
|
3223
|
-
0% {
|
|
3224
|
-
background-position: 200% 0;
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
20% {
|
|
3228
|
-
background-position: 100% 0;
|
|
3229
|
-
}
|
|
3230
|
-
|
|
3231
|
-
80% {
|
|
3232
|
-
background-position: -100% 0;
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
100% {
|
|
3236
|
-
background-position: -200% 0;
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
@media (forced-colors: active) {
|
|
3241
|
-
.progress::-webkit-progress-value,
|
|
3242
|
-
.progress:indeterminate::-webkit-progress-value {
|
|
3243
|
-
background: Highlight;
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
.progress::-moz-progress-bar,
|
|
3247
|
-
.progress:indeterminate::-moz-progress-bar {
|
|
3248
|
-
background: Highlight;
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
|
|
3252
|
-
/*
|
|
3253
|
-
* Status bar — singleton floating status area.
|
|
3254
|
-
*
|
|
3255
|
-
* One element in the HTML, empty by default, hidden when :empty but kept in the
|
|
3256
|
-
* accessibility tree so it stays a reliable live region. JavaScript only sets
|
|
3257
|
-
* text content; the visual shows and hides with the content.
|
|
3258
|
-
*
|
|
3259
|
-
* Consumes intent like badge or alert. No need for status-bar-specific modifier
|
|
3260
|
-
* classes — .danger / .success / .warning / .neutral from intents.css drive
|
|
3261
|
-
* the coloring via --intent / --intent-fg. status.js applies these as plain
|
|
3262
|
-
* classes on the target; it does not know their names or that they exist.
|
|
3263
|
-
*
|
|
3264
|
-
* CSS API: .status-bar
|
|
3265
|
-
* JS target contract: [data-status][role="status"]
|
|
3266
|
-
* Normally combined, but they describe separate concerns.
|
|
3267
|
-
*
|
|
3268
|
-
* Internal:
|
|
3269
|
-
* --status-bg
|
|
3270
|
-
* --status-fg
|
|
3271
|
-
*/
|
|
3272
|
-
|
|
3273
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3274
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3275
|
-
:where(.status-bar) {
|
|
3276
|
-
--intent: initial;
|
|
3277
|
-
--intent-fg: initial;
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
.status-bar {
|
|
3281
|
-
--status-bg: var(--intent, var(--text));
|
|
3282
|
-
--status-fg: var(--intent-fg, var(--surface));
|
|
3283
|
-
|
|
3284
|
-
position: fixed;
|
|
3285
|
-
inset-block-end: var(--space-40);
|
|
3286
|
-
inset-inline: var(--space-40);
|
|
3287
|
-
z-index: var(--z-status);
|
|
3288
|
-
|
|
3289
|
-
display: inline-flex;
|
|
3290
|
-
align-items: center;
|
|
3291
|
-
justify-content: center;
|
|
3292
|
-
gap: var(--space-20);
|
|
3293
|
-
|
|
3294
|
-
inline-size: fit-content;
|
|
3295
|
-
max-inline-size: min(calc(var(--viewport-inline) - var(--space-60)), 32rem);
|
|
3296
|
-
margin-inline: auto;
|
|
3297
|
-
overflow-wrap: anywhere;
|
|
3298
|
-
|
|
3299
|
-
padding-block: var(--space-20);
|
|
3300
|
-
padding-inline: var(--space-30);
|
|
3301
|
-
|
|
3302
|
-
border-radius: var(--radius-full);
|
|
3303
|
-
background: var(--status-bg);
|
|
3304
|
-
color: var(--status-fg);
|
|
3305
|
-
box-shadow: var(--shadow-popout);
|
|
3306
|
-
|
|
3307
|
-
opacity: 1;
|
|
3308
|
-
scale: 1;
|
|
3309
|
-
translate: 0 0;
|
|
3310
|
-
pointer-events: none;
|
|
3311
|
-
|
|
3312
|
-
transition:
|
|
3313
|
-
opacity var(--duration-slow) var(--ease),
|
|
3314
|
-
scale var(--duration-slow) var(--ease),
|
|
3315
|
-
translate var(--duration-slow) var(--ease);
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
.status-bar:empty {
|
|
3319
|
-
opacity: 0;
|
|
3320
|
-
scale: 0.96;
|
|
3321
|
-
translate: 0 var(--space-20);
|
|
3322
|
-
pointer-events: none;
|
|
3323
|
-
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
/* Display */
|
|
3327
|
-
/*
|
|
3328
|
-
* Avatars for initials, images, links, and buttons.
|
|
3329
|
-
*
|
|
3330
|
-
* The visual box stays the same across semantic elements. Grouping and status
|
|
3331
|
-
* dots (an empty .badge child) compose around the avatar without changing its
|
|
3332
|
-
* meaning.
|
|
3333
|
-
*
|
|
3334
|
-
* Public hooks:
|
|
3335
|
-
* --avatar-size
|
|
3336
|
-
* --avatar-radius
|
|
3337
|
-
* --avatar-font-size
|
|
3338
|
-
* --avatar-stack-size
|
|
3339
|
-
* --avatar-stack-font-size
|
|
3340
|
-
* --avatar-stack-overlap
|
|
3341
|
-
* --avatar-stack-ring
|
|
3342
|
-
*
|
|
3343
|
-
* Internal:
|
|
3344
|
-
* --avatar-bg
|
|
3345
|
-
* --avatar-fg
|
|
3346
|
-
* --avatar-status-translate-x
|
|
3347
|
-
*/
|
|
3348
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3349
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3350
|
-
:where(.avatar) {
|
|
3351
|
-
--intent: initial;
|
|
3352
|
-
--intent-fg: initial;
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
|
-
.avatar {
|
|
3356
|
-
--avatar-size: var(--avatar-stack-size, 2.25rem);
|
|
3357
|
-
--avatar-radius: var(--radius-full);
|
|
3358
|
-
--avatar-font-size: var(--avatar-stack-font-size, var(--font-size-sm));
|
|
3359
|
-
--avatar-bg: var(--intent, var(--surface-subtle));
|
|
3360
|
-
--avatar-fg: var(--intent-fg, var(--tone-fg, var(--text-muted)));
|
|
3361
|
-
|
|
3362
|
-
position: relative;
|
|
3363
|
-
display: inline-grid;
|
|
3364
|
-
place-items: center;
|
|
3365
|
-
inline-size: var(--avatar-size);
|
|
3366
|
-
block-size: var(--avatar-size);
|
|
3367
|
-
overflow: visible;
|
|
3368
|
-
padding: 0;
|
|
3369
|
-
border: 0;
|
|
3370
|
-
border-radius: var(--avatar-radius);
|
|
3371
|
-
background: var(--avatar-bg);
|
|
3372
|
-
color: var(--avatar-fg);
|
|
3373
|
-
font: inherit;
|
|
3374
|
-
font-size: var(--avatar-font-size);
|
|
3375
|
-
font-weight: var(--font-weight-strong);
|
|
3376
|
-
line-height: 1;
|
|
3377
|
-
text-decoration: none;
|
|
3378
|
-
vertical-align: middle;
|
|
3379
|
-
user-select: none;
|
|
3380
|
-
transition:
|
|
3381
|
-
box-shadow var(--duration),
|
|
3382
|
-
opacity var(--duration),
|
|
3383
|
-
transform var(--duration);
|
|
3384
|
-
}
|
|
3385
|
-
|
|
3386
|
-
/* Forced colors wipe the background and the stack ring (box-shadow), so the
|
|
3387
|
-
shape needs a real border to stay visible. */
|
|
3388
|
-
@media (forced-colors: active) {
|
|
3389
|
-
.avatar {
|
|
3390
|
-
border: var(--border-width) solid CanvasText;
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
|
-
:where(a.avatar, button.avatar)::after {
|
|
3394
|
-
display: none;
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
.avatar > :first-child:not(:where(img, picture, .badge)) {
|
|
3399
|
-
display: inline-grid;
|
|
3400
|
-
place-items: center;
|
|
3401
|
-
inline-size: 100%;
|
|
3402
|
-
block-size: 100%;
|
|
3403
|
-
overflow: hidden;
|
|
3404
|
-
border-radius: inherit;
|
|
3405
|
-
}
|
|
3406
|
-
|
|
3407
|
-
.avatar > img,
|
|
3408
|
-
.avatar > picture {
|
|
3409
|
-
display: block;
|
|
3410
|
-
inline-size: 100%;
|
|
3411
|
-
block-size: 100%;
|
|
3412
|
-
overflow: hidden;
|
|
3413
|
-
border-radius: inherit;
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
.avatar > img,
|
|
3417
|
-
.avatar > picture > img {
|
|
3418
|
-
inline-size: 100%;
|
|
3419
|
-
block-size: 100%;
|
|
3420
|
-
object-fit: cover;
|
|
3421
|
-
border-radius: inherit;
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
.avatar > picture > img {
|
|
3425
|
-
display: block;
|
|
3426
|
-
}
|
|
3427
|
-
|
|
3428
|
-
button.avatar {
|
|
3429
|
-
appearance: none;
|
|
3430
|
-
cursor: pointer;
|
|
3431
|
-
}
|
|
3432
|
-
|
|
3433
|
-
:where(a.avatar, button.avatar)::after {
|
|
3434
|
-
content: "";
|
|
3435
|
-
position: absolute;
|
|
3436
|
-
inset: 0;
|
|
3437
|
-
border-radius: inherit;
|
|
3438
|
-
background: var(--hover-overlay);
|
|
3439
|
-
opacity: 0;
|
|
3440
|
-
pointer-events: none;
|
|
3441
|
-
transition: opacity var(--duration);
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):hover::after {
|
|
3445
|
-
opacity: 1;
|
|
3446
|
-
}
|
|
3447
|
-
|
|
3448
|
-
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):active {
|
|
3449
|
-
transform: translateY(1px);
|
|
3450
|
-
}
|
|
3451
|
-
|
|
3452
|
-
button.avatar:disabled,
|
|
3453
|
-
a.avatar[aria-disabled="true"] {
|
|
3454
|
-
cursor: not-allowed;
|
|
3455
|
-
opacity: var(--disabled-opacity);
|
|
3456
|
-
}
|
|
3457
|
-
|
|
3458
|
-
a.avatar[aria-disabled="true"] {
|
|
3459
|
-
pointer-events: none;
|
|
3460
|
-
}
|
|
3461
|
-
|
|
3462
|
-
.avatar.sm {
|
|
3463
|
-
--avatar-stack-size: 1.75rem;
|
|
3464
|
-
--avatar-stack-font-size: var(--font-size-xs);
|
|
3465
|
-
}
|
|
3466
|
-
|
|
3467
|
-
.avatar.lg {
|
|
3468
|
-
--avatar-stack-size: 3rem;
|
|
3469
|
-
--avatar-stack-font-size: var(--font-size-lg);
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
|
-
/* Status dot: an empty .badge positioned at the avatar's bottom-end corner.
|
|
3473
|
-
The dot conveys no text — always pair it with an aria-label on the badge. */
|
|
3474
|
-
.avatar > .badge:empty {
|
|
3475
|
-
--avatar-status-translate-x: 50%;
|
|
3476
|
-
|
|
3477
|
-
position: absolute;
|
|
3478
|
-
inset-block-end: var(--indicator-offset);
|
|
3479
|
-
inset-inline-end: var(--indicator-offset);
|
|
3480
|
-
z-index: 1;
|
|
3481
|
-
box-shadow: 0 0 0 2px var(--indicator-ring);
|
|
3482
|
-
transform: translate(var(--avatar-status-translate-x), 50%);
|
|
3483
|
-
}
|
|
3484
|
-
|
|
3485
|
-
.avatar:dir(rtl) > .badge:empty {
|
|
3486
|
-
--avatar-status-translate-x: -50%;
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
|
-
/* Avatar stack — explicit class for overlapping avatar groups.
|
|
3490
|
-
Does not rely on role="group" auto-detection. */
|
|
3491
|
-
.avatar-stack {
|
|
3492
|
-
--avatar-stack-overlap: calc(var(--avatar-stack-size, 2.25rem) / -4);
|
|
3493
|
-
--avatar-stack-ring: 2px;
|
|
3494
|
-
|
|
3495
|
-
display: inline-flex;
|
|
3496
|
-
align-items: center;
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
.avatar-stack.sm {
|
|
3500
|
-
--avatar-stack-size: 1.75rem;
|
|
3501
|
-
--avatar-stack-font-size: var(--font-size-xs);
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3504
|
-
.avatar-stack.lg {
|
|
3505
|
-
--avatar-stack-size: 3rem;
|
|
3506
|
-
--avatar-stack-font-size: var(--font-size-lg);
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
@supports selector(:has(> .avatar)) {
|
|
3510
|
-
.avatar-stack:has(> .avatar.sm) {
|
|
3511
|
-
--avatar-stack-size: 1.75rem;
|
|
3512
|
-
--avatar-stack-font-size: var(--font-size-xs);
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
.avatar-stack:has(> .avatar.lg) {
|
|
3516
|
-
--avatar-stack-size: 3rem;
|
|
3517
|
-
--avatar-stack-font-size: var(--font-size-lg);
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
.avatar-stack > .avatar {
|
|
3522
|
-
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface);
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
.avatar-stack > .avatar + .avatar {
|
|
3526
|
-
margin-inline-start: var(--avatar-stack-overlap);
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
.avatar-stack > .avatar:is(:hover, :focus-visible) {
|
|
3530
|
-
z-index: 1;
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
/*
|
|
3534
|
-
* Key — keyboard keys and shortcut tokens for UI chrome.
|
|
3535
|
-
*
|
|
3536
|
-
* Use .key outside prose. Plain kbd inside .prose gets the same visual
|
|
3537
|
-
* treatment from prose.css so authored content stays convenient.
|
|
3538
|
-
*
|
|
3539
|
-
* Visual twin of .prose kbd (prose.css) — keep the two recipes in sync.
|
|
3540
|
-
*/
|
|
3541
|
-
.key {
|
|
3542
|
-
display: inline-flex;
|
|
3543
|
-
align-items: center;
|
|
3544
|
-
justify-content: center;
|
|
3545
|
-
min-inline-size: 1.75em;
|
|
3546
|
-
min-block-size: 1.6em;
|
|
3547
|
-
padding-inline: 0.45em;
|
|
3548
|
-
border: var(--border-width) solid var(--border);
|
|
3549
|
-
border-block-end-width: calc(var(--border-width) * 2);
|
|
3550
|
-
border-radius: var(--radius-sm);
|
|
3551
|
-
background: var(--surface-raised);
|
|
3552
|
-
color: var(--text);
|
|
3553
|
-
font-family: var(--font-mono);
|
|
3554
|
-
font-size: 0.875em;
|
|
3555
|
-
font-weight: var(--font-weight-medium);
|
|
3556
|
-
line-height: 1;
|
|
3557
|
-
white-space: nowrap;
|
|
3558
|
-
vertical-align: 0.08em;
|
|
3559
|
-
}
|
|
3560
|
-
|
|
3561
|
-
/*
|
|
3562
|
-
* Overline — editorial small-cap label for section openers.
|
|
3563
|
-
*
|
|
3564
|
-
* Base form is unstyled chrome that takes its surrounding color. Add .pill for
|
|
3565
|
-
* the bordered rounded shape, and combine with an intent class (.primary,
|
|
3566
|
-
* .success, etc.) to tint the text, border, and background.
|
|
3567
|
-
*
|
|
3568
|
-
* Public hooks:
|
|
3569
|
-
* --overline-radius
|
|
3570
|
-
*/
|
|
3571
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3572
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3573
|
-
:where(.overline) {
|
|
3574
|
-
--intent: initial;
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
.overline {
|
|
3578
|
-
color: var(--text-muted);
|
|
3579
|
-
font-size: var(--font-size-sm);
|
|
3580
|
-
font-weight: var(--font-weight-medium);
|
|
3581
|
-
letter-spacing: 0.04em;
|
|
3582
|
-
text-transform: uppercase;
|
|
3583
|
-
}
|
|
3584
|
-
|
|
3585
|
-
.overline.pill {
|
|
3586
|
-
--overline-radius: var(--radius-full);
|
|
3587
|
-
|
|
3588
|
-
display: inline-flex;
|
|
3589
|
-
align-items: center;
|
|
3590
|
-
gap: 0.5em;
|
|
3591
|
-
padding-inline: 0.6em;
|
|
3592
|
-
padding-block: 0.25em;
|
|
3593
|
-
inline-size: fit-content;
|
|
3594
|
-
max-inline-size: 100%;
|
|
3595
|
-
color: var(--intent, var(--text-muted));
|
|
3596
|
-
border: var(--border-width) solid var(--border);
|
|
3597
|
-
border-radius: var(--overline-radius);
|
|
3598
|
-
background: var(--surface);
|
|
3599
|
-
}
|
|
3600
|
-
|
|
3601
|
-
/* Tinted pill surface where color-mix is available; the flat fallback above
|
|
3602
|
-
keeps a readable bordered pill everywhere else. */
|
|
3603
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
3604
|
-
.overline.pill {
|
|
3605
|
-
border-color: color-mix(in oklch, var(--intent, var(--border)) 35%, var(--border));
|
|
3606
|
-
background: color-mix(in oklch, var(--intent, transparent) 6%, var(--surface));
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
/* Navigation */
|
|
3612
|
-
/*
|
|
3613
|
-
* Navbar — top-level navigation bar.
|
|
3614
|
-
*
|
|
3615
|
-
* .navbar is a horizontal bar; .navbar-brand is the brand link; .navbar-nav is
|
|
3616
|
-
* the inline link list using .nav-link items. .nav-list is the vertical nav
|
|
3617
|
-
* list shared with .drawer, fed by the shared --gap token.
|
|
3618
|
-
*
|
|
3619
|
-
* Internal:
|
|
3620
|
-
* --gap (local value for the shared gap token)
|
|
3621
|
-
*/
|
|
3622
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3623
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3624
|
-
:where(.navbar) {
|
|
3625
|
-
--ui-bg: initial;
|
|
3626
|
-
--ui-fg: initial;
|
|
3627
|
-
--ui-border: initial;
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
.navbar {
|
|
3631
|
-
display: flex;
|
|
3632
|
-
flex-wrap: wrap;
|
|
3633
|
-
align-items: center;
|
|
3634
|
-
justify-content: space-between;
|
|
3635
|
-
gap: var(--gap);
|
|
3636
|
-
padding-block: var(--space-30);
|
|
3637
|
-
padding-inline: var(--space-40);
|
|
3638
|
-
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
3639
|
-
border-radius: var(--radius-lg);
|
|
3640
|
-
background: var(--ui-bg, var(--surface-raised));
|
|
3641
|
-
color: var(--ui-fg, var(--text));
|
|
3642
|
-
}
|
|
3643
|
-
|
|
3644
|
-
.navbar-brand {
|
|
3645
|
-
display: inline-flex;
|
|
3646
|
-
align-items: center;
|
|
3647
|
-
gap: var(--space-20);
|
|
3648
|
-
color: var(--ui-fg, var(--text));
|
|
3649
|
-
font-weight: var(--font-weight-bold);
|
|
3650
|
-
text-decoration: none;
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
|
-
.navbar-brand:focus-visible {
|
|
3654
|
-
border-radius: var(--radius-sm);
|
|
3655
|
-
}
|
|
3656
|
-
|
|
3657
|
-
.navbar-nav {
|
|
3658
|
-
display: flex;
|
|
3659
|
-
flex-wrap: wrap;
|
|
3660
|
-
align-items: center;
|
|
3661
|
-
gap: var(--space-10);
|
|
3662
|
-
margin: 0;
|
|
3663
|
-
padding: 0;
|
|
3664
|
-
list-style: none;
|
|
3665
|
-
}
|
|
3666
|
-
|
|
3667
|
-
.nav-link {
|
|
3668
|
-
display: inline-flex;
|
|
3669
|
-
align-items: center;
|
|
3670
|
-
min-block-size: var(--control-size-sm);
|
|
3671
|
-
padding-inline: var(--space-20);
|
|
3672
|
-
border-radius: var(--radius);
|
|
3673
|
-
color: var(--ui-fg, var(--text-muted));
|
|
3674
|
-
font-weight: var(--font-weight-medium);
|
|
3675
|
-
text-decoration: none;
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
.nav-link:where([aria-current]:not([aria-current="false"])) {
|
|
3679
|
-
background: var(--surface-subtle);
|
|
3680
|
-
color: var(--text);
|
|
3681
|
-
}
|
|
3682
|
-
|
|
3683
|
-
.nav-link:hover {
|
|
3684
|
-
background: var(--surface-subtle);
|
|
3685
|
-
color: var(--text);
|
|
3686
|
-
}
|
|
3687
|
-
|
|
3688
|
-
/* Nav list — a vertical navigation list. Used by .drawer for stacked links. */
|
|
3689
|
-
.nav-list {
|
|
3690
|
-
display: grid;
|
|
3691
|
-
--gap: var(--space-20);
|
|
3692
|
-
gap: var(--gap);
|
|
3693
|
-
margin: 0;
|
|
3694
|
-
padding: 0;
|
|
3695
|
-
list-style: none;
|
|
3696
|
-
}
|
|
3697
|
-
|
|
3698
|
-
.nav-list > li {
|
|
3699
|
-
margin: 0;
|
|
3700
|
-
}
|
|
3701
|
-
|
|
3702
|
-
@media (forced-colors: active) {
|
|
3703
|
-
.nav-link:where([aria-current]:not([aria-current="false"])) {
|
|
3704
|
-
outline: var(--border-width) solid Highlight;
|
|
3705
|
-
background: Highlight;
|
|
3706
|
-
color: HighlightText;
|
|
3707
|
-
forced-color-adjust: none;
|
|
3708
|
-
}
|
|
3709
|
-
|
|
3710
|
-
.nav-link:hover:not(:where([aria-current]:not([aria-current="false"]))) {
|
|
3711
|
-
outline: var(--border-width) solid ButtonText;
|
|
3712
|
-
outline-offset: 2px;
|
|
3713
|
-
background: Canvas;
|
|
3714
|
-
color: CanvasText;
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
|
|
3718
|
-
/*
|
|
3719
|
-
* Breadcrumb — current-location trail from the site root.
|
|
3720
|
-
*
|
|
3721
|
-
* Inline list separated by "/". Mark the current page with `aria-current="page"`
|
|
3722
|
-
* either on the item or on the current link.
|
|
3723
|
-
*/
|
|
3724
|
-
.breadcrumb {
|
|
3725
|
-
display: flex;
|
|
3726
|
-
flex-wrap: wrap;
|
|
3727
|
-
align-items: center;
|
|
3728
|
-
gap: var(--space-10);
|
|
3729
|
-
margin: 0;
|
|
3730
|
-
padding: 0;
|
|
3731
|
-
color: var(--text-subtle);
|
|
3732
|
-
font-size: var(--font-size-sm);
|
|
3733
|
-
list-style: none;
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
.breadcrumb li {
|
|
3737
|
-
display: inline-flex;
|
|
3738
|
-
align-items: center;
|
|
3739
|
-
gap: var(--space-10);
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
|
-
.breadcrumb li + li::before {
|
|
3743
|
-
content: "/";
|
|
3744
|
-
color: var(--text-subtle);
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
.breadcrumb a {
|
|
3748
|
-
color: var(--text-muted);
|
|
3749
|
-
text-decoration: none;
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
.breadcrumb a:not([aria-current]):hover,
|
|
3753
|
-
.breadcrumb a[aria-current="false"]:hover {
|
|
3754
|
-
color: var(--text);
|
|
3755
|
-
text-decoration: underline;
|
|
3756
|
-
}
|
|
3757
|
-
|
|
3758
|
-
.breadcrumb a:focus-visible {
|
|
3759
|
-
border-radius: var(--radius-sm);
|
|
3760
|
-
}
|
|
3761
|
-
|
|
3762
|
-
.breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
|
|
3763
|
-
color: var(--text);
|
|
3764
|
-
font-weight: var(--font-weight-medium);
|
|
3765
|
-
}
|
|
3766
|
-
|
|
3767
|
-
/*
|
|
3768
|
-
* Pagination — page navigation for paginated lists.
|
|
3769
|
-
*
|
|
3770
|
-
* Render .btn elements inside a .pagination list. The active page is marked
|
|
3771
|
-
* with aria-current="page"; .btn owns the active state styling.
|
|
3772
|
-
*
|
|
3773
|
-
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
3774
|
-
* — they are decorative, not actionable. They still get the same box sizing
|
|
3775
|
-
* as .btn below so the row stays visually aligned.
|
|
3776
|
-
*/
|
|
3777
|
-
.pagination {
|
|
3778
|
-
display: flex;
|
|
3779
|
-
flex-wrap: wrap;
|
|
3780
|
-
align-items: center;
|
|
3781
|
-
gap: var(--space-10);
|
|
3782
|
-
margin: 0;
|
|
3783
|
-
padding: 0;
|
|
3784
|
-
list-style: none;
|
|
3785
|
-
}
|
|
3786
|
-
|
|
3787
|
-
.pagination .btn {
|
|
3788
|
-
min-inline-size: var(--control-size);
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
.pagination > li > [aria-hidden="true"] {
|
|
3792
|
-
display: inline-flex;
|
|
3793
|
-
align-items: center;
|
|
3794
|
-
justify-content: center;
|
|
3795
|
-
min-inline-size: var(--control-size);
|
|
3796
|
-
}
|
|
3797
|
-
|
|
3798
|
-
/*
|
|
3799
|
-
* Tabs — in-place panel switcher with real tab semantics.
|
|
3800
|
-
*
|
|
3801
|
-
* JavaScript owns roving tabindex, aria-selected, panel visibility,
|
|
3802
|
-
* and arrow-key navigation. CSS owns the tab strip appearance and
|
|
3803
|
-
* active indicator.
|
|
3804
|
-
*
|
|
3805
|
-
* Public hooks:
|
|
3806
|
-
* --tab-gap
|
|
3807
|
-
*/
|
|
3808
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3809
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3810
|
-
:where(.tab) {
|
|
3811
|
-
--intent: initial;
|
|
3812
|
-
}
|
|
3813
|
-
|
|
3814
|
-
.tabs {
|
|
3815
|
-
display: flex;
|
|
3816
|
-
flex-wrap: wrap;
|
|
3817
|
-
gap: var(--space-10);
|
|
3818
|
-
margin: 0;
|
|
3819
|
-
padding: 0;
|
|
3820
|
-
border-block-end: var(--border-width) solid var(--border);
|
|
3821
|
-
list-style: none;
|
|
3822
|
-
}
|
|
3823
|
-
|
|
3824
|
-
.tabs[aria-orientation="vertical"] {
|
|
3825
|
-
display: grid;
|
|
3826
|
-
align-content: start;
|
|
3827
|
-
justify-items: stretch;
|
|
3828
|
-
border-block-end: 0;
|
|
3829
|
-
border-inline-end: var(--border-width) solid var(--border);
|
|
3830
|
-
}
|
|
3831
|
-
|
|
3832
|
-
.tab {
|
|
3833
|
-
display: inline-flex;
|
|
3834
|
-
align-items: center;
|
|
3835
|
-
gap: var(--tab-gap, 0.375em);
|
|
3836
|
-
min-block-size: var(--control-size);
|
|
3837
|
-
padding-inline: var(--space-30);
|
|
3838
|
-
border: 0;
|
|
3839
|
-
border-block-end: 2px solid transparent;
|
|
3840
|
-
border-radius: 0;
|
|
3841
|
-
background: transparent;
|
|
3842
|
-
color: var(--text-muted);
|
|
3843
|
-
font: inherit;
|
|
3844
|
-
font-weight: var(--font-weight-strong);
|
|
3845
|
-
text-decoration: none;
|
|
3846
|
-
cursor: pointer;
|
|
3847
|
-
}
|
|
3848
|
-
|
|
3849
|
-
.tabs[aria-orientation="vertical"] .tab {
|
|
3850
|
-
justify-content: flex-start;
|
|
3851
|
-
border-block-end: 0;
|
|
3852
|
-
border-inline-end: 2px solid transparent;
|
|
3853
|
-
padding-inline-end: var(--space-40);
|
|
3854
|
-
}
|
|
3855
|
-
|
|
3856
|
-
.tab:is(:disabled, [aria-disabled="true"]) {
|
|
3857
|
-
cursor: not-allowed;
|
|
3858
|
-
opacity: var(--disabled-opacity);
|
|
3859
|
-
}
|
|
3860
|
-
|
|
3861
|
-
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3862
|
-
color: var(--text);
|
|
3863
|
-
}
|
|
3864
|
-
|
|
3865
|
-
.tab:focus-visible {
|
|
3866
|
-
outline: var(--focus-outline);
|
|
3867
|
-
outline-color: var(--focus);
|
|
3868
|
-
outline-offset: -2px;
|
|
3869
|
-
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
3870
|
-
}
|
|
3871
|
-
|
|
3872
|
-
.tab[aria-selected="true"] {
|
|
3873
|
-
border-block-end-color: var(--intent, var(--state-selected));
|
|
3874
|
-
color: var(--intent, var(--state-selected));
|
|
3875
|
-
}
|
|
3876
|
-
|
|
3877
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3878
|
-
border-block-end-color: transparent;
|
|
3879
|
-
border-inline-end-color: var(--intent, var(--state-selected));
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
@media (forced-colors: active) {
|
|
3883
|
-
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3884
|
-
text-decoration: underline;
|
|
3885
|
-
text-underline-offset: 0.18em;
|
|
3886
|
-
}
|
|
3887
|
-
|
|
3888
|
-
.tab[aria-selected="true"] {
|
|
3889
|
-
border-block-end-color: Highlight;
|
|
3890
|
-
color: Highlight;
|
|
3891
|
-
}
|
|
3892
|
-
|
|
3893
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3894
|
-
border-block-end-color: transparent;
|
|
3895
|
-
border-inline-end-color: Highlight;
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
|
|
3899
|
-
/*
|
|
3900
|
-
* Scrollspy — behavior hook for navigation that marks the active section
|
|
3901
|
-
* while the page scrolls.
|
|
3902
|
-
*
|
|
3903
|
-
* Does not add visual styles by itself. scrollspy.js watches
|
|
3904
|
-
* <nav class="scrollspy">
|
|
3905
|
-
* containing <a href="#section"> links and toggles aria-current="location" on
|
|
3906
|
-
* the link whose target is in view. Pair with .nav-list and .nav-link, or style
|
|
3907
|
-
* [aria-current] yourself.
|
|
3908
|
-
*
|
|
3909
|
-
* Progressive enhancement: on browsers that support :target-current,
|
|
3910
|
-
* this file enables the native target group and marks the active link.
|
|
3911
|
-
*
|
|
3912
|
-
* Falls back gracefully: links still work when :target-current is unavailable
|
|
3913
|
-
* and when the JavaScript enhancer is not loaded at all.
|
|
3914
|
-
*/
|
|
3915
|
-
|
|
3916
|
-
@supports selector(.scrollspy a:target-current) {
|
|
3917
|
-
.scrollspy {
|
|
3918
|
-
scroll-target-group: auto;
|
|
3919
|
-
}
|
|
3920
|
-
|
|
3921
|
-
.scrollspy a:target-current {
|
|
3922
|
-
color: var(--text);
|
|
3923
|
-
font-weight: var(--font-weight-medium);
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
/* Data */
|
|
3929
|
-
/*
|
|
3930
|
-
* Table — structured tabular data.
|
|
3931
|
-
*
|
|
3932
|
-
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
3933
|
-
* and cell rules. Tokens are sourced from the shared scale.
|
|
3934
|
-
*
|
|
3935
|
-
* Public hooks:
|
|
3936
|
-
* --table-min
|
|
3937
|
-
* --table-cell-pad
|
|
3938
|
-
*/
|
|
3939
|
-
.table-wrap {
|
|
3940
|
-
inline-size: 100%;
|
|
3941
|
-
max-inline-size: 100%;
|
|
3942
|
-
min-inline-size: 0;
|
|
3943
|
-
overflow-x: auto;
|
|
3944
|
-
border: var(--border-width) solid var(--border);
|
|
3945
|
-
border-radius: var(--radius-lg);
|
|
3946
|
-
background: var(--surface-raised);
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
.table {
|
|
3950
|
-
--table-min: 100%;
|
|
3951
|
-
--table-cell-pad: var(--space-30);
|
|
3952
|
-
|
|
3953
|
-
inline-size: max(var(--table-min), 100%);
|
|
3954
|
-
border-collapse: collapse;
|
|
3955
|
-
background: transparent;
|
|
3956
|
-
color: var(--text);
|
|
3957
|
-
font-size: var(--font-size-md);
|
|
3958
|
-
}
|
|
3959
|
-
|
|
3960
|
-
.table caption {
|
|
3961
|
-
padding: var(--table-cell-pad);
|
|
3962
|
-
color: var(--text-muted);
|
|
3963
|
-
text-align: var(--text-align, start);
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
.table th,
|
|
3967
|
-
.table td {
|
|
3968
|
-
padding: var(--table-cell-pad);
|
|
3969
|
-
border-block-end: var(--border-width) solid var(--border);
|
|
3970
|
-
text-align: var(--text-align, start);
|
|
3971
|
-
vertical-align: baseline;
|
|
3972
|
-
}
|
|
3973
|
-
|
|
3974
|
-
.table thead th {
|
|
3975
|
-
vertical-align: bottom;
|
|
3976
|
-
}
|
|
3977
|
-
|
|
3978
|
-
.table th {
|
|
3979
|
-
color: var(--text-muted);
|
|
3980
|
-
font-weight: var(--font-weight-strong);
|
|
3981
|
-
}
|
|
3982
|
-
|
|
3983
|
-
.table thead {
|
|
3984
|
-
background: var(--surface-subtle);
|
|
3985
|
-
}
|
|
3986
|
-
|
|
3987
|
-
.table tbody tr:hover {
|
|
3988
|
-
background-color: var(--hover-overlay);
|
|
3989
|
-
}
|
|
3990
|
-
|
|
3991
|
-
.table > tr:last-child > *,
|
|
3992
|
-
.table > :last-child > tr:last-child > * {
|
|
3993
|
-
border-block-end: 0;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
/*
|
|
3997
|
-
* Meter — native gauge for known-range values.
|
|
3998
|
-
*
|
|
3999
|
-
* Maps the three standard meter zones (optimum / suboptimum / even-less-good)
|
|
4000
|
-
* to the shared success / warning / danger intent colors.
|
|
4001
|
-
*/
|
|
4002
|
-
|
|
4003
|
-
.meter {
|
|
4004
|
-
appearance: none;
|
|
4005
|
-
-webkit-appearance: none;
|
|
4006
|
-
display: block;
|
|
4007
|
-
inline-size: 100%;
|
|
4008
|
-
block-size: var(--bar-height);
|
|
4009
|
-
overflow: hidden;
|
|
4010
|
-
border: 0;
|
|
4011
|
-
border-radius: var(--radius-full);
|
|
4012
|
-
background: var(--surface-subtle);
|
|
4013
|
-
}
|
|
4014
|
-
|
|
4015
|
-
.meter::-webkit-meter-bar {
|
|
4016
|
-
-webkit-appearance: none;
|
|
4017
|
-
block-size: var(--bar-height);
|
|
4018
|
-
border: 0;
|
|
4019
|
-
border-radius: var(--radius-full);
|
|
4020
|
-
background: var(--surface-subtle);
|
|
4021
|
-
}
|
|
4022
|
-
|
|
4023
|
-
.meter::-webkit-meter-optimum-value {
|
|
4024
|
-
block-size: var(--bar-height);
|
|
4025
|
-
border-radius: var(--radius-full);
|
|
4026
|
-
background: var(--success);
|
|
4027
|
-
}
|
|
4028
|
-
|
|
4029
|
-
.meter::-webkit-meter-suboptimum-value {
|
|
4030
|
-
block-size: var(--bar-height);
|
|
4031
|
-
border-radius: var(--radius-full);
|
|
4032
|
-
background: var(--warning);
|
|
4033
|
-
}
|
|
4034
|
-
|
|
4035
|
-
.meter::-webkit-meter-even-less-good-value {
|
|
4036
|
-
block-size: var(--bar-height);
|
|
4037
|
-
border-radius: var(--radius-full);
|
|
4038
|
-
background: var(--danger);
|
|
4039
|
-
}
|
|
4040
|
-
|
|
4041
|
-
.meter::-moz-meter-bar {
|
|
4042
|
-
block-size: var(--bar-height);
|
|
4043
|
-
border-radius: var(--radius-full);
|
|
4044
|
-
}
|
|
4045
|
-
|
|
4046
|
-
@media (forced-colors: active) {
|
|
4047
|
-
.meter::-webkit-meter-optimum-value,
|
|
4048
|
-
.meter::-webkit-meter-suboptimum-value,
|
|
4049
|
-
.meter::-webkit-meter-even-less-good-value,
|
|
4050
|
-
.meter::-moz-meter-bar {
|
|
4051
|
-
background: Highlight;
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
4054
|
-
|
|
4055
|
-
/* Forms — import order matters: switch.css follows choice.css so its rules
|
|
4056
|
-
land after the shared choice recipe. validation.css is part of the default
|
|
4057
|
-
forms bundle, but custom builds may omit it when validation styling is
|
|
4058
|
-
app-owned. */
|
|
4059
|
-
/*
|
|
4060
|
-
* Form layout and supporting text.
|
|
4061
|
-
*
|
|
4062
|
-
* Individual controls keep their own files; .choice label layout lives in
|
|
4063
|
-
* choice.css; .form-actions lives in form-actions.css.
|
|
4064
|
-
*
|
|
4065
|
-
* Public API: .field, .field-label, .field-help, .field-error, .field-group,
|
|
4066
|
-
* .required-mark.
|
|
4067
|
-
*
|
|
4068
|
-
* Public hooks:
|
|
4069
|
-
* --fieldset-legend-padding-inline
|
|
4070
|
-
*/
|
|
4071
|
-
.field {
|
|
4072
|
-
display: grid;
|
|
4073
|
-
gap: var(--space-10);
|
|
4074
|
-
color: var(--text);
|
|
4075
|
-
}
|
|
4076
|
-
|
|
4077
|
-
.field-group {
|
|
4078
|
-
min-inline-size: 0;
|
|
4079
|
-
margin: 0;
|
|
4080
|
-
padding: var(--space-30);
|
|
4081
|
-
border: var(--border-width) solid var(--border);
|
|
4082
|
-
border-radius: var(--radius);
|
|
4083
|
-
}
|
|
4084
|
-
|
|
4085
|
-
.field-group + .field-group {
|
|
4086
|
-
margin-block-start: var(--space-40);
|
|
4087
|
-
}
|
|
4088
|
-
|
|
4089
|
-
.field-label,
|
|
4090
|
-
.field-group > legend {
|
|
4091
|
-
color: var(--text);
|
|
4092
|
-
font-weight: var(--font-weight-strong);
|
|
4093
|
-
}
|
|
4094
|
-
|
|
4095
|
-
.field-group > legend {
|
|
4096
|
-
padding-block: 0;
|
|
4097
|
-
padding-inline: var(--fieldset-legend-padding-inline, var(--space-10));
|
|
4098
|
-
font-size: var(--font-size-xs);
|
|
4099
|
-
}
|
|
4100
|
-
|
|
4101
|
-
.field-help {
|
|
4102
|
-
color: var(--text-muted);
|
|
4103
|
-
font-size: var(--font-size-sm);
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
|
-
.field-error {
|
|
4107
|
-
display: none;
|
|
4108
|
-
color: var(--danger);
|
|
4109
|
-
font-size: var(--font-size-sm);
|
|
4110
|
-
}
|
|
4111
|
-
|
|
4112
|
-
.required-mark {
|
|
4113
|
-
color: var(--danger);
|
|
4114
|
-
}
|
|
4115
|
-
|
|
4116
|
-
.field.danger .field-help {
|
|
4117
|
-
color: var(--danger);
|
|
4118
|
-
}
|
|
4119
|
-
|
|
4120
|
-
/*
|
|
4121
|
-
* Text controls — input, textarea, select.
|
|
4122
|
-
*
|
|
4123
|
-
* Neutral geometry and interaction states shared across all text-like
|
|
4124
|
-
* controls. select shares this base recipe; its chevron/appearance layer
|
|
4125
|
-
* lives in select.css. checkbox, radio, range, switch, and file have their
|
|
4126
|
-
* own files.
|
|
4127
|
-
*
|
|
4128
|
-
* --control-* are the per-state control tokens, reserved for control geometry.
|
|
4129
|
-
* State selectors only flip tokens; non-text controls do not consume them, so
|
|
4130
|
-
* matching those selectors is a no-op. The .field wrapper uses --field-*
|
|
4131
|
-
* (form.css).
|
|
4132
|
-
*
|
|
4133
|
-
* --control-border-block-end-color/-width are unset extension points, mirroring
|
|
4134
|
-
* --btn-border-block-end-* (button.css), for themes that give text controls an
|
|
4135
|
-
* underline-style lower edge instead of a uniform border.
|
|
4136
|
-
*
|
|
4137
|
-
* Public hooks:
|
|
4138
|
-
* --control-border
|
|
4139
|
-
* --control-bg
|
|
4140
|
-
* --control-fg
|
|
4141
|
-
* --control-cursor
|
|
4142
|
-
* --control-border-block-end-color
|
|
4143
|
-
* --control-border-block-end-width
|
|
4144
|
-
*/
|
|
4145
|
-
.input,
|
|
4146
|
-
.textarea,
|
|
4147
|
-
.select {
|
|
4148
|
-
--control-border: var(--border);
|
|
4149
|
-
--control-bg: var(--surface);
|
|
4150
|
-
--control-fg: var(--text);
|
|
4151
|
-
--control-cursor: auto;
|
|
4152
|
-
|
|
4153
|
-
appearance: none;
|
|
4154
|
-
inline-size: 100%;
|
|
4155
|
-
min-block-size: var(--control-size);
|
|
4156
|
-
padding-inline: var(--space-30);
|
|
4157
|
-
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
4158
|
-
border-block-end-color: var(
|
|
4159
|
-
--form-invalid-border,
|
|
4160
|
-
var(--control-border-block-end-color, var(--control-border))
|
|
4161
|
-
);
|
|
4162
|
-
border-block-end-width: var(--control-border-block-end-width, var(--border-width));
|
|
4163
|
-
border-radius: var(--radius);
|
|
4164
|
-
background: var(--control-bg);
|
|
4165
|
-
color: var(--control-fg);
|
|
4166
|
-
font-size: var(--control-font-size);
|
|
4167
|
-
cursor: var(--control-cursor);
|
|
4168
|
-
outline: none;
|
|
4169
|
-
transition:
|
|
4170
|
-
border-color var(--duration),
|
|
4171
|
-
box-shadow var(--duration),
|
|
4172
|
-
background-color var(--duration),
|
|
4173
|
-
color var(--duration);
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
.textarea {
|
|
4177
|
-
min-block-size: 6rem;
|
|
4178
|
-
padding-block: var(--space-20);
|
|
4179
|
-
resize: vertical;
|
|
4180
|
-
}
|
|
4181
|
-
|
|
4182
|
-
.input:hover:not(:disabled),
|
|
4183
|
-
.textarea:hover:not(:disabled) {
|
|
4184
|
-
--control-border: var(--text-subtle);
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
.select:hover:not(:disabled) {
|
|
4188
|
-
--control-bg: var(--surface-subtle);
|
|
4189
|
-
}
|
|
4190
|
-
|
|
4191
|
-
.input:focus-visible,
|
|
4192
|
-
.textarea:focus-visible,
|
|
4193
|
-
.select:focus-visible {
|
|
4194
|
-
--control-border: var(--focus);
|
|
4195
|
-
outline: 2px solid transparent;
|
|
4196
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4197
|
-
}
|
|
4198
|
-
|
|
4199
|
-
.input[readonly],
|
|
4200
|
-
.textarea[readonly] {
|
|
4201
|
-
--control-bg: var(--surface-subtle);
|
|
4202
|
-
--control-border: var(--border);
|
|
4203
|
-
}
|
|
4204
|
-
|
|
4205
|
-
.input:disabled,
|
|
4206
|
-
.textarea:disabled,
|
|
4207
|
-
.select:disabled {
|
|
4208
|
-
--control-bg: var(--surface-subtle);
|
|
4209
|
-
--control-fg: var(--state-disabled);
|
|
4210
|
-
--control-border: var(--border);
|
|
4211
|
-
--control-cursor: not-allowed;
|
|
4212
|
-
}
|
|
4213
|
-
|
|
4214
|
-
.input::placeholder,
|
|
4215
|
-
.textarea::placeholder {
|
|
4216
|
-
color: var(--text-muted);
|
|
4217
|
-
}
|
|
4218
|
-
|
|
4219
|
-
/*
|
|
4220
|
-
* Input Icon — decorative icon inside a relative wrapper, leading or trailing.
|
|
4221
|
-
*
|
|
4222
|
-
* Icon-library agnostic: detects position by DOM order. First child that is
|
|
4223
|
-
* not .input = leading icon; icon after .input = trailing. The icon keeps a
|
|
4224
|
-
* fixed size; density contexts shrink the control geometry around it, not
|
|
4225
|
-
* the pictogram.
|
|
4226
|
-
*
|
|
4227
|
-
* Public hooks:
|
|
4228
|
-
* --input-icon-size
|
|
4229
|
-
*/
|
|
4230
|
-
.input-icon {
|
|
4231
|
-
--input-icon-size: 1.25rem;
|
|
4232
|
-
|
|
4233
|
-
display: block;
|
|
4234
|
-
position: relative;
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4237
|
-
/* Icon — shared by leading and trailing */
|
|
4238
|
-
.input-icon > :where(:not(.input)) {
|
|
4239
|
-
position: absolute;
|
|
4240
|
-
inset-block-start: 50%;
|
|
4241
|
-
transform: translateY(-50%);
|
|
4242
|
-
font-size: var(--input-icon-size);
|
|
4243
|
-
color: var(--text-muted);
|
|
4244
|
-
pointer-events: none;
|
|
4245
|
-
}
|
|
4246
|
-
|
|
4247
|
-
/* Leading: icon is the first child */
|
|
4248
|
-
.input-icon > :first-child:where(:not(.input)) {
|
|
4249
|
-
inset-inline-start: var(--space-30);
|
|
4250
|
-
}
|
|
4251
|
-
|
|
4252
|
-
/* Trailing: input is the first child, icon follows */
|
|
4253
|
-
.input-icon > .input:first-child ~ :where(:not(.input)) {
|
|
4254
|
-
inset-inline-end: var(--space-30);
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
4258
|
-
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
4259
|
-
.input-icon > svg:where(:not(.input)) {
|
|
4260
|
-
inline-size: var(--input-icon-size);
|
|
4261
|
-
block-size: var(--input-icon-size);
|
|
4262
|
-
}
|
|
4263
|
-
|
|
4264
|
-
/* Leading: input gets start padding to clear the icon */
|
|
4265
|
-
.input-icon > :first-child:where(:not(.input)) ~ .input {
|
|
4266
|
-
padding-inline-start: calc(var(--space-30) * 2 + var(--input-icon-size));
|
|
4267
|
-
}
|
|
4268
|
-
|
|
4269
|
-
/* Trailing: input is first child and has a sibling → end padding */
|
|
4270
|
-
.input-icon > .input:first-child:where(:not(:last-child)) {
|
|
4271
|
-
padding-inline-end: calc(var(--space-30) * 2 + var(--input-icon-size));
|
|
4272
|
-
}
|
|
4273
|
-
|
|
4274
|
-
/*
|
|
4275
|
-
* Checkbox and radio controls.
|
|
4276
|
-
*
|
|
4277
|
-
* Native inputs keep their behavior while sharing custom geometry, checked,
|
|
4278
|
-
* focus, and disabled states across semantic forms and classed aliases. This
|
|
4279
|
-
* file also owns the .choice label layout (input + label text) so the whole
|
|
4280
|
-
* concept lives in one file.
|
|
4281
|
-
*
|
|
4282
|
-
* Switches (input[role="switch"]) are excluded so switch.css owns their
|
|
4283
|
-
* geometry and states. switch.css is imported after this file.
|
|
4284
|
-
*
|
|
4285
|
-
* Public hooks:
|
|
4286
|
-
* --choice-control-offset
|
|
4287
|
-
* --choice-border
|
|
4288
|
-
* --choice-bg
|
|
4289
|
-
* --choice-mark
|
|
4290
|
-
*/
|
|
4291
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
4292
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
4293
|
-
:where(.choice, .check, .radio) {
|
|
4294
|
-
--intent: initial;
|
|
4295
|
-
--intent-fg: initial;
|
|
4296
|
-
}
|
|
4297
|
-
|
|
4298
|
-
/* A classed .choice is the component surface. Let its direct input consume a
|
|
4299
|
-
local intent without reopening inherited page-level intents. */
|
|
4300
|
-
:where(.choice) > :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
4301
|
-
--intent: inherit;
|
|
4302
|
-
--intent-fg: inherit;
|
|
4303
|
-
}
|
|
4304
|
-
|
|
4305
|
-
.choice {
|
|
4306
|
-
--choice-control-offset: 0.25em;
|
|
4307
|
-
|
|
4308
|
-
display: inline-flex;
|
|
4309
|
-
align-items: flex-start;
|
|
4310
|
-
gap: var(--space-20);
|
|
4311
|
-
color: var(--text);
|
|
4312
|
-
font-size: var(--control-font-size);
|
|
4313
|
-
cursor: pointer;
|
|
4314
|
-
}
|
|
4315
|
-
|
|
4316
|
-
/* Optical alignment: nudge the control down so the checkbox/radio sits
|
|
4317
|
-
visually centered with the first line of label text, not flush with the
|
|
4318
|
-
top of the line-box. Default 0.25em (~4px at 16px) places the control just
|
|
4319
|
-
below the cap-height, matching what Bootstrap, Material, and most polished
|
|
4320
|
-
form libraries ship. Multi-line labels still keep the control at the top
|
|
4321
|
-
thanks to align-items: flex-start. Override per-instance via
|
|
4322
|
-
--choice-control-offset for themes with a different line-height or control
|
|
4323
|
-
size. */
|
|
4324
|
-
.choice > :is(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
4325
|
-
flex: 0 0 auto;
|
|
4326
|
-
margin-block-start: var(--choice-control-offset);
|
|
4327
|
-
}
|
|
4328
|
-
|
|
4329
|
-
.choice > span > .field-help {
|
|
4330
|
-
display: block;
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
/* Progressive enhancement: dim the whole row when one of its controls is
|
|
4334
|
-
disabled. Without :has() the native disabled control keeps its own state
|
|
4335
|
-
styling — the row-level dim is lost but the field stays clearly disabled. */
|
|
4336
|
-
.choice:has(> :disabled) {
|
|
4337
|
-
color: var(--state-disabled);
|
|
4338
|
-
cursor: not-allowed;
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
|
-
/* Control geometry. --choice-* are the per-state tokens. */
|
|
4342
|
-
.check,
|
|
4343
|
-
.radio {
|
|
4344
|
-
--choice-border: var(--border);
|
|
4345
|
-
--choice-bg: var(--surface);
|
|
4346
|
-
--choice-mark: var(--primary-fg);
|
|
4347
|
-
|
|
4348
|
-
position: relative;
|
|
4349
|
-
vertical-align: middle;
|
|
4350
|
-
display: inline-grid;
|
|
4351
|
-
place-content: center;
|
|
4352
|
-
inline-size: 1.125em;
|
|
4353
|
-
block-size: 1.125em;
|
|
4354
|
-
margin: 0;
|
|
4355
|
-
border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
|
|
4356
|
-
background: var(--choice-bg);
|
|
4357
|
-
color: var(--choice-mark);
|
|
4358
|
-
appearance: none;
|
|
4359
|
-
cursor: pointer;
|
|
4360
|
-
}
|
|
4361
|
-
|
|
4362
|
-
.check {
|
|
4363
|
-
border-radius: var(--radius-sm);
|
|
4364
|
-
}
|
|
4365
|
-
|
|
4366
|
-
.radio {
|
|
4367
|
-
border-radius: var(--radius-full);
|
|
4368
|
-
}
|
|
4369
|
-
|
|
4370
|
-
.check::before,
|
|
4371
|
-
.radio::before {
|
|
4372
|
-
content: "";
|
|
4373
|
-
position: absolute;
|
|
4374
|
-
inset: 0;
|
|
4375
|
-
margin: auto;
|
|
4376
|
-
transform: scale(0);
|
|
4377
|
-
transition: transform var(--duration);
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
.check::before {
|
|
4381
|
-
inline-size: 0.58em;
|
|
4382
|
-
block-size: 0.34em;
|
|
4383
|
-
border-inline-start: 2px solid currentColor;
|
|
4384
|
-
border-block-end: 2px solid currentColor;
|
|
4385
|
-
transform: translateY(-0.06em) rotate(-45deg) scale(0);
|
|
4386
|
-
}
|
|
4387
|
-
|
|
4388
|
-
.radio::before {
|
|
4389
|
-
inline-size: 0.5em;
|
|
4390
|
-
block-size: 0.5em;
|
|
4391
|
-
border-radius: var(--radius-full);
|
|
4392
|
-
background: currentColor;
|
|
4393
|
-
}
|
|
4394
|
-
|
|
4395
|
-
.check:is(:checked, :indeterminate),
|
|
4396
|
-
.radio:checked {
|
|
4397
|
-
--choice-border: var(--intent, var(--primary));
|
|
4398
|
-
--choice-bg: var(--intent, var(--primary));
|
|
4399
|
-
--choice-mark: var(--intent-fg, var(--primary-fg));
|
|
4400
|
-
}
|
|
4401
|
-
|
|
4402
|
-
.check:checked::before {
|
|
4403
|
-
transform: translateY(-0.06em) rotate(-45deg) scale(1);
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
|
-
.check:indeterminate::before {
|
|
4407
|
-
border-inline-start: 0;
|
|
4408
|
-
inline-size: 0.6em;
|
|
4409
|
-
block-size: 0;
|
|
4410
|
-
border-block-end: 2px solid currentColor;
|
|
4411
|
-
transform: scale(1);
|
|
4412
|
-
}
|
|
4413
|
-
|
|
4414
|
-
.radio:checked::before {
|
|
4415
|
-
transform: scale(1);
|
|
4416
|
-
}
|
|
4417
|
-
|
|
4418
|
-
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
4419
|
-
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
4420
|
-
--choice-border: var(--text-subtle);
|
|
4421
|
-
}
|
|
4422
|
-
|
|
4423
|
-
.check:focus-visible,
|
|
4424
|
-
.radio:focus-visible {
|
|
4425
|
-
--choice-border: var(--focus);
|
|
4426
|
-
outline: 2px solid transparent;
|
|
4427
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4428
|
-
}
|
|
4429
|
-
|
|
4430
|
-
.check:disabled,
|
|
4431
|
-
.radio:disabled {
|
|
4432
|
-
--choice-bg: var(--surface-subtle);
|
|
4433
|
-
--choice-border: var(--border);
|
|
4434
|
-
--choice-mark: var(--state-disabled);
|
|
4435
|
-
cursor: not-allowed;
|
|
4436
|
-
opacity: 1;
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
@media (forced-colors: active) {
|
|
4440
|
-
.check,
|
|
4441
|
-
.radio {
|
|
4442
|
-
--choice-border: ButtonText;
|
|
4443
|
-
--choice-bg: Canvas;
|
|
4444
|
-
--choice-mark: ButtonText;
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
.check:checked,
|
|
4448
|
-
.radio:checked,
|
|
4449
|
-
.check:indeterminate {
|
|
4450
|
-
--choice-border: Highlight;
|
|
4451
|
-
--choice-bg: Highlight;
|
|
4452
|
-
--choice-mark: HighlightText;
|
|
4453
|
-
forced-color-adjust: none;
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
|
-
.check:disabled,
|
|
4457
|
-
.radio:disabled {
|
|
4458
|
-
--choice-border: GrayText;
|
|
4459
|
-
--choice-bg: Canvas;
|
|
4460
|
-
--choice-mark: GrayText;
|
|
4461
|
-
}
|
|
4462
|
-
}
|
|
4463
|
-
|
|
4464
|
-
/*
|
|
4465
|
-
* Choice cards — card-style radio and checkbox labels.
|
|
4466
|
-
*
|
|
4467
|
-
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
4468
|
-
* :has() is supported the input is visually hidden and the card mirrors its
|
|
4469
|
-
* state: checked border and tint, checkmark badge (round for radio, rounded
|
|
4470
|
-
* square for checkbox), focus ring, disabled dimming. Without :has() the
|
|
4471
|
-
* native input stays visible so selection state never disappears.
|
|
4472
|
-
*
|
|
4473
|
-
* The badge overhangs the top-right corner by 0.375em; ancestors with
|
|
4474
|
-
* overflow: hidden will clip it. Pair with grid or flex utilities for group
|
|
4475
|
-
* layout.
|
|
4476
|
-
*
|
|
4477
|
-
* Public hooks:
|
|
4478
|
-
* --choice-card-pad
|
|
4479
|
-
* --choice-card-radius
|
|
4480
|
-
* --choice-card-border
|
|
4481
|
-
* --choice-card-bg
|
|
4482
|
-
* --choice-card-check-size
|
|
4483
|
-
*/
|
|
4484
|
-
:where(.choice-card) {
|
|
4485
|
-
--intent: initial;
|
|
4486
|
-
--intent-fg: initial;
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
|
-
.choice-card {
|
|
4490
|
-
--choice-card-pad: var(--density-space, var(--space-40));
|
|
4491
|
-
--choice-card-radius: var(--radius);
|
|
4492
|
-
--choice-card-border: var(--border);
|
|
4493
|
-
--choice-card-bg: var(--surface);
|
|
4494
|
-
--choice-card-check-size: 1.5em;
|
|
4495
|
-
|
|
4496
|
-
position: relative;
|
|
4497
|
-
display: block;
|
|
4498
|
-
padding: var(--choice-card-pad);
|
|
4499
|
-
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
4500
|
-
border-radius: var(--choice-card-radius);
|
|
4501
|
-
background: var(--choice-card-bg);
|
|
4502
|
-
color: var(--text);
|
|
4503
|
-
cursor: pointer;
|
|
4504
|
-
transition:
|
|
4505
|
-
border-color var(--duration),
|
|
4506
|
-
background-color var(--duration),
|
|
4507
|
-
box-shadow var(--duration);
|
|
4508
|
-
}
|
|
4509
|
-
|
|
4510
|
-
/* The native input is visually hidden but stays focusable and participates in
|
|
4511
|
-
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
4512
|
-
:has() the card cannot mirror the input's state, so the visible native
|
|
4513
|
-
control is the fallback affordance. */
|
|
4514
|
-
@supports selector(:has(*)) {
|
|
4515
|
-
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
4516
|
-
position: absolute;
|
|
4517
|
-
inline-size: 1px;
|
|
4518
|
-
block-size: 1px;
|
|
4519
|
-
padding: 0;
|
|
4520
|
-
margin: -1px;
|
|
4521
|
-
overflow: hidden;
|
|
4522
|
-
clip: rect(0, 0, 0, 0);
|
|
4523
|
-
white-space: nowrap;
|
|
4524
|
-
border: 0;
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
|
|
4528
|
-
/* Focus ring on the label follows the hidden input's focus. */
|
|
4529
|
-
.choice-card:has(:focus-visible) {
|
|
4530
|
-
outline: 2px solid transparent;
|
|
4531
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4532
|
-
}
|
|
4533
|
-
|
|
4534
|
-
.choice-card:has(:disabled) {
|
|
4535
|
-
opacity: var(--disabled-opacity);
|
|
4536
|
-
cursor: not-allowed;
|
|
4537
|
-
}
|
|
4538
|
-
|
|
4539
|
-
.choice-card:hover:not(:has(:disabled)) {
|
|
4540
|
-
--choice-card-border: var(--text-subtle);
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
/* Checked border — shared by radio and checkbox */
|
|
4544
|
-
.choice-card:has(:checked) {
|
|
4545
|
-
--choice-card-border: var(--intent, var(--primary));
|
|
4546
|
-
}
|
|
4547
|
-
|
|
4548
|
-
/* Subtle background tint on checked — modern browsers only */
|
|
4549
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
4550
|
-
.choice-card:has(:checked) {
|
|
4551
|
-
--choice-card-bg: color-mix(in oklch, var(--surface) 96%, var(--intent, var(--primary)));
|
|
4552
|
-
}
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
/* Checkmark badge — always rendered at scale(0) and scaled in on check, so
|
|
4556
|
-
the entrance can transition (a pseudo created on :checked could not). */
|
|
4557
|
-
.choice-card::before,
|
|
4558
|
-
.choice-card::after {
|
|
4559
|
-
content: "";
|
|
4560
|
-
position: absolute;
|
|
4561
|
-
inset-inline-end: -0.375em;
|
|
4562
|
-
inset-block-start: -0.375em;
|
|
4563
|
-
inline-size: var(--choice-card-check-size);
|
|
4564
|
-
block-size: var(--choice-card-check-size);
|
|
4565
|
-
transform: scale(0);
|
|
4566
|
-
transition: transform var(--duration);
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
|
-
/* Badge disc — circle for radio, rounded square for checkbox */
|
|
4570
|
-
.choice-card::before {
|
|
4571
|
-
background: var(--intent, var(--primary));
|
|
4572
|
-
box-shadow: 0 0 0 2px var(--surface);
|
|
4573
|
-
z-index: 1;
|
|
4574
|
-
}
|
|
4575
|
-
|
|
4576
|
-
.choice-card:has(input[type="radio"])::before {
|
|
4577
|
-
border-radius: var(--radius-full);
|
|
4578
|
-
}
|
|
4579
|
-
|
|
4580
|
-
.choice-card:has(input[type="checkbox"])::before {
|
|
4581
|
-
border-radius: var(--radius-sm);
|
|
4582
|
-
}
|
|
4583
|
-
|
|
4584
|
-
/* Check mark */
|
|
4585
|
-
.choice-card::after {
|
|
4586
|
-
background: var(--intent-fg, var(--primary-fg));
|
|
4587
|
-
-webkit-mask: var(--icon-check) center / 60% no-repeat;
|
|
4588
|
-
mask: var(--icon-check) center / 60% no-repeat;
|
|
4589
|
-
z-index: 2;
|
|
4590
|
-
}
|
|
4591
|
-
|
|
4592
|
-
.choice-card:has(:checked)::before,
|
|
4593
|
-
.choice-card:has(:checked)::after {
|
|
4594
|
-
transform: scale(1);
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
@media (forced-colors: active) {
|
|
4598
|
-
.choice-card {
|
|
4599
|
-
--choice-card-border: ButtonText;
|
|
4600
|
-
--choice-card-bg: Canvas;
|
|
4601
|
-
}
|
|
4602
|
-
|
|
4603
|
-
.choice-card:has(:checked) {
|
|
4604
|
-
--choice-card-border: Highlight;
|
|
4605
|
-
/* Pin the background: with forced-color-adjust: none the color-mix tint
|
|
4606
|
-
above would actually render, uncontrolled in high-contrast themes. */
|
|
4607
|
-
--choice-card-bg: Canvas;
|
|
4608
|
-
forced-color-adjust: none;
|
|
4609
|
-
}
|
|
4610
|
-
|
|
4611
|
-
.choice-card:has(:checked)::before {
|
|
4612
|
-
background: Highlight;
|
|
4613
|
-
box-shadow: 0 0 0 2px Canvas;
|
|
4614
|
-
}
|
|
4615
|
-
|
|
4616
|
-
.choice-card:has(:checked)::after {
|
|
4617
|
-
background: HighlightText;
|
|
4618
|
-
}
|
|
4619
|
-
}
|
|
4620
|
-
|
|
4621
|
-
/*
|
|
4622
|
-
* Select — chevron and appearance layer on top of the shared text-control
|
|
4623
|
-
* recipe.
|
|
4624
|
-
*
|
|
4625
|
-
* Base geometry and interaction states come from control.css (select shares
|
|
4626
|
-
* the --control-* tokens). This file only removes the native arrow and draws
|
|
4627
|
-
* the CSS chevron, so the picker (appearance: base-select) enhancement in
|
|
4628
|
-
* custom-select.css can be opted out independently. Add .native to a specific
|
|
4629
|
-
* select to opt out of the enhancement.
|
|
4630
|
-
*/
|
|
4631
|
-
.select:not([multiple], [size]:not([size="1"])) {
|
|
4632
|
-
appearance: none;
|
|
4633
|
-
padding-inline-end: calc(var(--space-30) + var(--space-60));
|
|
4634
|
-
background-image: var(--icon-chevron);
|
|
4635
|
-
background-position: center right var(--space-30);
|
|
4636
|
-
background-size: 1rem auto;
|
|
4637
|
-
background-repeat: no-repeat;
|
|
4638
|
-
}
|
|
4639
|
-
|
|
4640
|
-
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
4641
|
-
the padding-inline-end space reserved for it. */
|
|
4642
|
-
.select:not([multiple], [size]:not([size="1"])):dir(rtl) {
|
|
4643
|
-
background-position: center left var(--space-30);
|
|
4644
|
-
}
|
|
4645
|
-
|
|
4646
|
-
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
4647
|
-
browser instead of guessing a higher-contrast recolor of our own icon —
|
|
4648
|
-
simpler than maintaining contrast variants, and native rendering already
|
|
4649
|
-
matches whatever contrast mode the user picked. */
|
|
4650
|
-
@media (forced-colors: active), (prefers-contrast: more) {
|
|
4651
|
-
.select:not([multiple], [size]:not([size="1"])) {
|
|
4652
|
-
appearance: auto;
|
|
4653
|
-
background-image: none;
|
|
4654
|
-
padding-inline-end: var(--space-30);
|
|
4655
|
-
}
|
|
4656
|
-
}
|
|
4657
|
-
|
|
4658
|
-
/*
|
|
4659
|
-
* Customizable select — appearance: base-select picker enhancement.
|
|
4660
|
-
*
|
|
4661
|
-
* Progressive enhancement on top of the native .select baseline in select.css.
|
|
4662
|
-
* Wrapped in @supports and gated on fine pointer devices so it never blocks a
|
|
4663
|
-
* layout. Add .native to a specific select to opt out.
|
|
4664
|
-
*
|
|
4665
|
-
* No layout or interaction contract may depend on appearance: base-select.
|
|
4666
|
-
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
4667
|
-
*
|
|
4668
|
-
* Internal:
|
|
4669
|
-
* --control-border
|
|
4670
|
-
* --control-bg
|
|
4671
|
-
*/
|
|
4672
|
-
@media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
|
|
4673
|
-
pointer: fine
|
|
4674
|
-
) {
|
|
4675
|
-
@supports (appearance: base-select) {
|
|
4676
|
-
.select:not([multiple], .native, [size]:not([size="1"])) {
|
|
4677
|
-
appearance: base-select;
|
|
4678
|
-
align-items: center;
|
|
4679
|
-
|
|
4680
|
-
&::picker(select) {
|
|
4681
|
-
appearance: base-select;
|
|
4682
|
-
max-height: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
4683
|
-
margin-block: var(--space-20);
|
|
4684
|
-
padding: 0;
|
|
4685
|
-
border: var(--border-width) solid var(--border);
|
|
4686
|
-
border-radius: var(--radius);
|
|
4687
|
-
background-color: var(--surface-raised);
|
|
4688
|
-
color: inherit;
|
|
4689
|
-
box-shadow: var(--shadow-popout);
|
|
4690
|
-
}
|
|
4691
|
-
|
|
4692
|
-
&:open {
|
|
4693
|
-
--control-border: var(--border);
|
|
4694
|
-
--control-bg: var(--surface-raised);
|
|
4695
|
-
outline: none;
|
|
4696
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4697
|
-
}
|
|
4698
|
-
|
|
4699
|
-
&::picker-icon {
|
|
4700
|
-
display: none;
|
|
4701
|
-
}
|
|
4702
|
-
|
|
4703
|
-
& option {
|
|
4704
|
-
position: relative;
|
|
4705
|
-
padding-block: var(--space-20);
|
|
4706
|
-
padding-inline: var(--space-30);
|
|
4707
|
-
white-space: normal;
|
|
4708
|
-
transition-property: color, background-color;
|
|
4709
|
-
transition-duration: var(--duration);
|
|
4710
|
-
|
|
4711
|
-
&:not(:disabled):hover {
|
|
4712
|
-
background-color: var(--surface-subtle);
|
|
4713
|
-
color: var(--text);
|
|
4714
|
-
cursor: pointer;
|
|
4715
|
-
}
|
|
4716
|
-
|
|
4717
|
-
&:not(:disabled):focus-visible {
|
|
4718
|
-
background-color: var(--primary);
|
|
4719
|
-
color: var(--primary-fg);
|
|
4720
|
-
outline: none;
|
|
4721
|
-
}
|
|
4722
|
-
|
|
4723
|
-
&:active {
|
|
4724
|
-
background-color: var(--surface-subtle);
|
|
4725
|
-
color: var(--text);
|
|
4726
|
-
}
|
|
4727
|
-
|
|
4728
|
-
&::checkmark {
|
|
4729
|
-
content: "";
|
|
4730
|
-
display: block;
|
|
4731
|
-
inline-size: 1rem;
|
|
4732
|
-
block-size: 1rem;
|
|
4733
|
-
background-color: currentColor;
|
|
4734
|
-
-webkit-mask: var(--icon-check) center / contain no-repeat;
|
|
4735
|
-
mask: var(--icon-check) center / contain no-repeat;
|
|
4736
|
-
}
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
|
-
& optgroup {
|
|
4740
|
-
padding-block-start: 0.5em;
|
|
4741
|
-
|
|
4742
|
-
& option:first-child {
|
|
4743
|
-
margin-block-start: 0.5em;
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
}
|
|
4748
|
-
}
|
|
4749
|
-
|
|
4750
|
-
/*
|
|
4751
|
-
* Native-only control polish: the file input and color picker mostly defeat
|
|
4752
|
-
* custom styling, so the framework stays out of the way and just keeps them
|
|
4753
|
-
* readable and theme-aware.
|
|
4754
|
-
*
|
|
4755
|
-
* Range has its own custom-styled file (range.css) so the focus ring can
|
|
4756
|
-
* target the thumb pseudo-element rather than the rectangular CSS box.
|
|
4757
|
-
*/
|
|
4758
|
-
.file {
|
|
4759
|
-
color: var(--text-muted);
|
|
4760
|
-
}
|
|
4761
|
-
|
|
4762
|
-
.file::file-selector-button {
|
|
4763
|
-
margin-inline-end: var(--space-30);
|
|
4764
|
-
padding-block: var(--space-10);
|
|
4765
|
-
padding-inline: var(--space-20);
|
|
4766
|
-
border: var(--border-width) solid var(--border);
|
|
4767
|
-
border-radius: var(--radius-sm);
|
|
4768
|
-
background: var(--surface-subtle);
|
|
4769
|
-
color: var(--text);
|
|
4770
|
-
font: inherit;
|
|
4771
|
-
font-size: var(--font-size-sm);
|
|
4772
|
-
font-weight: var(--font-weight-medium);
|
|
4773
|
-
cursor: pointer;
|
|
4774
|
-
transition:
|
|
4775
|
-
background-color var(--duration),
|
|
4776
|
-
border-color var(--duration),
|
|
4777
|
-
color var(--duration);
|
|
4778
|
-
}
|
|
4779
|
-
|
|
4780
|
-
.file:disabled::file-selector-button {
|
|
4781
|
-
cursor: not-allowed;
|
|
4782
|
-
}
|
|
4783
|
-
|
|
4784
|
-
.file:not(:disabled)::file-selector-button:hover {
|
|
4785
|
-
background: var(--border);
|
|
4786
|
-
}
|
|
4787
|
-
|
|
4788
|
-
.file:focus-visible {
|
|
4789
|
-
outline: 2px solid transparent;
|
|
4790
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4791
|
-
}
|
|
4792
|
-
|
|
4793
|
-
/* Native color picker — keep the browser's dialog and selected-color UI. */
|
|
4794
|
-
.color {
|
|
4795
|
-
inline-size: var(--control-size);
|
|
4796
|
-
block-size: var(--control-size);
|
|
4797
|
-
padding: var(--space-10);
|
|
4798
|
-
border: var(--border-width) solid var(--border);
|
|
4799
|
-
border-radius: var(--radius);
|
|
4800
|
-
background: var(--surface);
|
|
4801
|
-
cursor: pointer;
|
|
4802
|
-
}
|
|
4803
|
-
|
|
4804
|
-
.color::-webkit-color-swatch-wrapper {
|
|
4805
|
-
padding: 0;
|
|
4806
|
-
}
|
|
4807
|
-
|
|
4808
|
-
.color::-webkit-color-swatch {
|
|
4809
|
-
border: 0;
|
|
4810
|
-
}
|
|
4811
|
-
|
|
4812
|
-
.color::-moz-color-swatch {
|
|
4813
|
-
border: 0;
|
|
4814
|
-
}
|
|
4815
|
-
|
|
4816
|
-
.color:disabled {
|
|
4817
|
-
cursor: not-allowed;
|
|
4818
|
-
opacity: var(--disabled-opacity);
|
|
4819
|
-
}
|
|
4820
|
-
|
|
4821
|
-
.color:focus-visible {
|
|
4822
|
-
outline: 2px solid transparent;
|
|
4823
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4824
|
-
}
|
|
4825
|
-
|
|
4826
|
-
@media (forced-colors: active) {
|
|
4827
|
-
.color {
|
|
4828
|
-
border-color: ButtonText;
|
|
4829
|
-
background: ButtonFace;
|
|
4830
|
-
forced-color-adjust: none;
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
|
|
4834
|
-
/*
|
|
4835
|
-
* Range slider — custom-styled so the focus ring always targets the
|
|
4836
|
-
* user-manipulated surface (the thumb) rather than the CSS box.
|
|
4837
|
-
*
|
|
4838
|
-
* appearance: none gives us full control over track and thumb; without it,
|
|
4839
|
-
* pseudo-element rules for ::-webkit-slider-thumb and ::-moz-range-thumb
|
|
4840
|
-
* are not reliably applied across browsers.
|
|
4841
|
-
*
|
|
4842
|
-
* Public hooks:
|
|
4843
|
-
* --range-thumb-size
|
|
4844
|
-
* --range-track-height
|
|
4845
|
-
* --range-thumb-bg
|
|
4846
|
-
* --range-track-bg
|
|
4847
|
-
*/
|
|
4848
|
-
|
|
4849
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
4850
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
4851
|
-
:where(.range) {
|
|
4852
|
-
--intent: initial;
|
|
4853
|
-
}
|
|
4854
|
-
|
|
4855
|
-
.range {
|
|
4856
|
-
--range-thumb-size: 1.25rem;
|
|
4857
|
-
--range-track-height: 0.25rem;
|
|
4858
|
-
--range-thumb-bg: var(--intent, var(--primary));
|
|
4859
|
-
--range-track-bg: var(--border);
|
|
4860
|
-
|
|
4861
|
-
appearance: none;
|
|
4862
|
-
inline-size: 100%;
|
|
4863
|
-
block-size: var(--range-thumb-size);
|
|
4864
|
-
background: transparent;
|
|
4865
|
-
cursor: pointer;
|
|
4866
|
-
}
|
|
4867
|
-
|
|
4868
|
-
.range::-webkit-slider-runnable-track {
|
|
4869
|
-
block-size: var(--range-track-height);
|
|
4870
|
-
border-radius: var(--radius-full);
|
|
4871
|
-
background: var(--range-track-bg);
|
|
4872
|
-
}
|
|
4873
|
-
|
|
4874
|
-
.range::-moz-range-track {
|
|
4875
|
-
block-size: var(--range-track-height);
|
|
4876
|
-
border-radius: var(--radius-full);
|
|
4877
|
-
background: var(--range-track-bg);
|
|
4878
|
-
}
|
|
4879
|
-
|
|
4880
|
-
.range::-webkit-slider-thumb {
|
|
4881
|
-
appearance: none;
|
|
4882
|
-
inline-size: var(--range-thumb-size);
|
|
4883
|
-
block-size: var(--range-thumb-size);
|
|
4884
|
-
border-radius: var(--radius-full);
|
|
4885
|
-
border: 0;
|
|
4886
|
-
background: var(--range-thumb-bg);
|
|
4887
|
-
margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
|
|
4888
|
-
cursor: inherit;
|
|
4889
|
-
transition: box-shadow var(--duration);
|
|
4890
|
-
}
|
|
4891
|
-
|
|
4892
|
-
.range::-moz-range-thumb {
|
|
4893
|
-
inline-size: var(--range-thumb-size);
|
|
4894
|
-
block-size: var(--range-thumb-size);
|
|
4895
|
-
border-radius: var(--radius-full);
|
|
4896
|
-
border: 0;
|
|
4897
|
-
background: var(--range-thumb-bg);
|
|
4898
|
-
cursor: inherit;
|
|
4899
|
-
transition: box-shadow var(--duration);
|
|
4900
|
-
}
|
|
4901
|
-
|
|
4902
|
-
.range:focus-visible {
|
|
4903
|
-
outline: 2px solid transparent;
|
|
4904
|
-
}
|
|
4905
|
-
|
|
4906
|
-
/*
|
|
4907
|
-
* The 1px surface-coloured separator keeps the ring visually distinct from
|
|
4908
|
-
* the small, coloured thumb — unlike a full-size input where the same gap
|
|
4909
|
-
* would read as an unwanted void.
|
|
4910
|
-
*/
|
|
4911
|
-
.range:focus-visible::-webkit-slider-thumb {
|
|
4912
|
-
box-shadow:
|
|
4913
|
-
0 0 0 1px var(--surface),
|
|
4914
|
-
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
|
|
4915
|
-
}
|
|
4916
|
-
|
|
4917
|
-
.range:focus-visible::-moz-range-thumb {
|
|
4918
|
-
box-shadow:
|
|
4919
|
-
0 0 0 1px var(--surface),
|
|
4920
|
-
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
|
|
4921
|
-
}
|
|
4922
|
-
|
|
4923
|
-
.range:disabled {
|
|
4924
|
-
--range-thumb-bg: var(--state-disabled);
|
|
4925
|
-
cursor: not-allowed;
|
|
4926
|
-
opacity: var(--disabled-opacity);
|
|
4927
|
-
}
|
|
4928
|
-
|
|
4929
|
-
/* Forced colors: map the custom thumb/track to system colors. The focus
|
|
4930
|
-
indicator stays visible through the transparent outline on :focus-visible. */
|
|
4931
|
-
@media (forced-colors: active) {
|
|
4932
|
-
.range {
|
|
4933
|
-
--range-thumb-bg: ButtonText;
|
|
4934
|
-
--range-track-bg: Canvas;
|
|
4935
|
-
}
|
|
4936
|
-
}
|
|
4937
|
-
|
|
4938
|
-
/*
|
|
4939
|
-
* Switch controls for checkbox inputs with switch semantics.
|
|
4940
|
-
*
|
|
4941
|
-
* The switch remains an input control, not a decorative toggle. Checked, hover,
|
|
4942
|
-
* focus-visible, and disabled states stay clear.
|
|
4943
|
-
*
|
|
4944
|
-
* Public hooks:
|
|
4945
|
-
* --switch-bg
|
|
4946
|
-
* --switch-border
|
|
4947
|
-
* --switch-knob
|
|
4948
|
-
* --switch-width
|
|
4949
|
-
* --switch-block-size
|
|
4950
|
-
* --switch-knob-margin
|
|
4951
|
-
* --switch-knob-size
|
|
4952
|
-
* --switch-travel
|
|
4953
|
-
*/
|
|
4954
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
4955
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
4956
|
-
:where(.switch) {
|
|
4957
|
-
--intent: initial;
|
|
4958
|
-
--intent-fg: initial;
|
|
4959
|
-
}
|
|
4960
|
-
|
|
4961
|
-
.switch {
|
|
4962
|
-
--switch-bg: var(--border);
|
|
4963
|
-
--switch-border: var(--border);
|
|
4964
|
-
--switch-knob: var(--surface-raised);
|
|
4965
|
-
--switch-width: calc(var(--control-size) * 1.16);
|
|
4966
|
-
--switch-block-size: calc(var(--control-size) * 0.63);
|
|
4967
|
-
--switch-knob-margin: calc(var(--switch-block-size) * 0.12);
|
|
4968
|
-
--switch-knob-size: calc(var(--switch-block-size) - 2 * var(--switch-knob-margin));
|
|
4969
|
-
--switch-travel: calc(
|
|
4970
|
-
var(--switch-width) -
|
|
4971
|
-
var(--switch-knob-size) -
|
|
4972
|
-
2 *
|
|
4973
|
-
var(--border-width) -
|
|
4974
|
-
2 *
|
|
4975
|
-
var(--switch-knob-margin)
|
|
4976
|
-
);
|
|
4977
|
-
|
|
4978
|
-
position: relative;
|
|
4979
|
-
display: inline-grid;
|
|
4980
|
-
align-items: center;
|
|
4981
|
-
justify-content: start;
|
|
4982
|
-
inline-size: var(--switch-width);
|
|
4983
|
-
block-size: var(--switch-block-size);
|
|
4984
|
-
margin: 0;
|
|
4985
|
-
border: var(--border-width) solid var(--form-invalid-border, var(--switch-border));
|
|
4986
|
-
border-radius: var(--radius-full);
|
|
4987
|
-
background: var(--switch-bg);
|
|
4988
|
-
appearance: none;
|
|
4989
|
-
cursor: pointer;
|
|
4990
|
-
}
|
|
4991
|
-
|
|
4992
|
-
.switch::before {
|
|
4993
|
-
content: "";
|
|
4994
|
-
inline-size: var(--switch-knob-size);
|
|
4995
|
-
block-size: var(--switch-knob-size);
|
|
4996
|
-
margin-inline: var(--switch-knob-margin);
|
|
4997
|
-
border-radius: var(--radius-full);
|
|
4998
|
-
background: var(--switch-knob);
|
|
4999
|
-
box-shadow: var(--shadow);
|
|
5000
|
-
transition:
|
|
5001
|
-
transform var(--duration),
|
|
5002
|
-
background-color var(--duration),
|
|
5003
|
-
box-shadow var(--duration);
|
|
5004
|
-
}
|
|
5005
|
-
|
|
5006
|
-
.switch:hover:not(:disabled) {
|
|
5007
|
-
--switch-border: var(--intent, var(--primary));
|
|
5008
|
-
}
|
|
5009
|
-
|
|
5010
|
-
.switch:checked {
|
|
5011
|
-
--switch-bg: var(--intent, var(--primary));
|
|
5012
|
-
--switch-border: var(--intent, var(--primary));
|
|
5013
|
-
--switch-knob: var(--intent-fg, var(--primary-fg));
|
|
5014
|
-
}
|
|
5015
|
-
|
|
5016
|
-
.switch:checked::before {
|
|
5017
|
-
transform: translateX(var(--switch-travel));
|
|
5018
|
-
}
|
|
5019
|
-
|
|
5020
|
-
/* The knob travels toward inline-end; in RTL that is leftward, so the
|
|
5021
|
-
translateX sign flips. translateX is physical, not logical. */
|
|
5022
|
-
.switch:checked:dir(rtl)::before {
|
|
5023
|
-
transform: translateX(calc(var(--switch-travel) * -1));
|
|
5024
|
-
}
|
|
5025
|
-
|
|
5026
|
-
.switch:focus-visible {
|
|
5027
|
-
--switch-border: var(--focus);
|
|
5028
|
-
outline: 2px solid transparent;
|
|
5029
|
-
box-shadow: var(--focus-ring-shadow);
|
|
5030
|
-
}
|
|
5031
|
-
|
|
5032
|
-
.switch:disabled {
|
|
5033
|
-
--switch-bg: var(--surface-subtle);
|
|
5034
|
-
--switch-border: var(--border);
|
|
5035
|
-
--switch-knob: var(--state-disabled);
|
|
5036
|
-
opacity: 1;
|
|
5037
|
-
cursor: not-allowed;
|
|
5038
|
-
}
|
|
5039
|
-
|
|
5040
|
-
@media (forced-colors: active) {
|
|
5041
|
-
/* --switch-knob must be set on the element, not on ::before: a custom
|
|
5042
|
-
property declared on the pseudo would shadow the :checked override. */
|
|
5043
|
-
.switch {
|
|
5044
|
-
--switch-border: ButtonText;
|
|
5045
|
-
--switch-knob: ButtonText;
|
|
5046
|
-
}
|
|
5047
|
-
|
|
5048
|
-
.switch::before {
|
|
5049
|
-
box-shadow: none;
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5052
|
-
.switch:checked {
|
|
5053
|
-
--switch-bg: Highlight;
|
|
5054
|
-
--switch-border: Highlight;
|
|
5055
|
-
--switch-knob: HighlightText;
|
|
5056
|
-
forced-color-adjust: none;
|
|
5057
|
-
}
|
|
5058
|
-
|
|
5059
|
-
.switch:disabled {
|
|
5060
|
-
--switch-bg: Canvas;
|
|
5061
|
-
--switch-border: GrayText;
|
|
5062
|
-
--switch-knob: GrayText;
|
|
5063
|
-
forced-color-adjust: auto;
|
|
5064
|
-
}
|
|
5065
|
-
}
|
|
5066
|
-
|
|
5067
|
-
/*
|
|
5068
|
-
* Form validation states.
|
|
5069
|
-
*
|
|
5070
|
-
* Imported by default through forms/index.css. Custom builds may omit this
|
|
5071
|
-
* file when an app wants to own validation styling completely.
|
|
5072
|
-
*
|
|
5073
|
-
* State coverage (which states each component ships) is audited in
|
|
5074
|
-
* tests/validation.test.js and summarized in docs/pages/forms/validation.md —
|
|
5075
|
-
* not kept as a source comment that can drift from the implementation.
|
|
5076
|
-
*
|
|
5077
|
-
* Public hooks:
|
|
5078
|
-
* --form-invalid-border
|
|
5079
|
-
*
|
|
5080
|
-
* Internal:
|
|
5081
|
-
* --choice-bg
|
|
5082
|
-
* --switch-bg
|
|
5083
|
-
*/
|
|
5084
|
-
|
|
5085
|
-
.field.danger {
|
|
5086
|
-
--form-invalid-border: var(--danger);
|
|
5087
|
-
}
|
|
5088
|
-
|
|
5089
|
-
.field.danger > .field-error {
|
|
5090
|
-
display: block;
|
|
5091
|
-
}
|
|
5092
|
-
|
|
5093
|
-
@supports selector(:has(*)) {
|
|
5094
|
-
.field:has([aria-invalid="true"]) > .field-error,
|
|
5095
|
-
.needs-validation.was-validated .field:has(:invalid) > .field-error {
|
|
5096
|
-
display: block;
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
|
|
5100
|
-
:where(.input, .textarea, .select, .check, .radio, .switch)[aria-invalid="true"],
|
|
5101
|
-
.needs-validation.was-validated :is(.input, .textarea, .select, .check, .radio, .switch):invalid {
|
|
5102
|
-
--form-invalid-border: var(--danger);
|
|
5103
|
-
}
|
|
5104
|
-
|
|
5105
|
-
/* Override checked/indeterminate fill so both border and fill signal danger
|
|
5106
|
-
instead of a red border around a primary-colored control. All three
|
|
5107
|
-
invalid routes must converge: aria-invalid, was-validated :invalid, and
|
|
5108
|
-
:user-invalid. */
|
|
5109
|
-
.check:is(:checked, :indeterminate)[aria-invalid="true"],
|
|
5110
|
-
.needs-validation.was-validated .check:is(:checked, :indeterminate):invalid,
|
|
5111
|
-
.radio:checked[aria-invalid="true"],
|
|
5112
|
-
.needs-validation.was-validated .radio:checked:invalid {
|
|
5113
|
-
--choice-bg: var(--danger);
|
|
5114
|
-
}
|
|
5115
|
-
|
|
5116
|
-
.switch:checked[aria-invalid="true"],
|
|
5117
|
-
.needs-validation.was-validated .switch:checked:invalid {
|
|
5118
|
-
--switch-bg: var(--danger);
|
|
5119
|
-
}
|
|
5120
|
-
|
|
5121
|
-
@supports selector(:user-invalid) {
|
|
5122
|
-
.needs-validation .check:is(:checked, :indeterminate):user-invalid,
|
|
5123
|
-
.needs-validation .radio:checked:user-invalid {
|
|
5124
|
-
--choice-bg: var(--danger);
|
|
5125
|
-
}
|
|
5126
|
-
|
|
5127
|
-
.needs-validation .switch:checked:user-invalid {
|
|
5128
|
-
--switch-bg: var(--danger);
|
|
5129
|
-
}
|
|
5130
|
-
}
|
|
5131
|
-
|
|
5132
|
-
/* Choice cards hide their native input, so invalid state must surface on the
|
|
5133
|
-
card itself. Sets the same --form-invalid-border hook the card's border
|
|
5134
|
-
consumes. */
|
|
5135
|
-
@supports selector(:has(*)) {
|
|
5136
|
-
.choice-card:has([aria-invalid="true"]),
|
|
5137
|
-
.needs-validation.was-validated .choice-card:has(:invalid) {
|
|
5138
|
-
--form-invalid-border: var(--danger);
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
5141
|
-
|
|
5142
|
-
@supports selector(:user-invalid) {
|
|
5143
|
-
.needs-validation :is(.input, .textarea, .select, .check, .radio, .switch):user-invalid {
|
|
5144
|
-
--form-invalid-border: var(--danger);
|
|
5145
|
-
}
|
|
5146
|
-
}
|
|
5147
|
-
|
|
5148
|
-
@supports selector(:has(:user-invalid)) {
|
|
5149
|
-
.needs-validation .field:has(:user-invalid) > .field-error {
|
|
5150
|
-
display: block;
|
|
5151
|
-
}
|
|
5152
|
-
|
|
5153
|
-
.needs-validation .choice-card:has(:user-invalid) {
|
|
5154
|
-
--form-invalid-border: var(--danger);
|
|
5155
|
-
}
|
|
5156
|
-
}
|
|
5157
|
-
|
|
5158
|
-
/*
|
|
5159
|
-
* Form actions — bottom form bar with sticky variant.
|
|
5160
|
-
*
|
|
5161
|
-
* A flex cluster of buttons/actions that closes a form. The sticky variant
|
|
5162
|
-
* docks to the viewport bottom with a translucent blur where supported; at
|
|
5163
|
-
* rest it stays visually neutral so it does not double as a separator.
|
|
5164
|
-
*
|
|
5165
|
-
* Public hooks:
|
|
5166
|
-
* --form-actions-align
|
|
5167
|
-
* --form-actions-justify
|
|
5168
|
-
* --form-actions-margin-block-start
|
|
5169
|
-
*
|
|
5170
|
-
* Internal:
|
|
5171
|
-
* --form-actions-sticky-block-offset (owned by .form-actions.sticky)
|
|
5172
|
-
* --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
|
|
5173
|
-
* --form-actions-sticky-padding (owned by .form-actions.sticky)
|
|
5174
|
-
*/
|
|
5175
|
-
.form-actions {
|
|
5176
|
-
display: flex;
|
|
5177
|
-
flex-wrap: wrap;
|
|
5178
|
-
gap: var(--gap);
|
|
5179
|
-
align-items: var(--form-actions-align, center);
|
|
5180
|
-
justify-content: var(--form-actions-justify, flex-start);
|
|
5181
|
-
margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
|
|
5182
|
-
}
|
|
5183
|
-
|
|
5184
|
-
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
5185
|
-
surface and backdrop-blur are visually neutral. Content scrolling behind
|
|
5186
|
-
the bar reveals the blur as a natural separator instead of relying on a
|
|
5187
|
-
shadow. */
|
|
5188
|
-
.form-actions.sticky {
|
|
5189
|
-
position: sticky;
|
|
5190
|
-
inset-block-end: var(--form-actions-sticky-block-offset, 0px);
|
|
5191
|
-
z-index: var(--z-sticky);
|
|
5192
|
-
isolation: isolate;
|
|
5193
|
-
|
|
5194
|
-
margin-inline: calc(var(--form-actions-sticky-inline-offset, 0px) * -1);
|
|
5195
|
-
padding: var(--form-actions-sticky-padding, var(--space-30));
|
|
5196
|
-
|
|
5197
|
-
border-block-start: var(--border-width) solid var(--border);
|
|
5198
|
-
background: var(--surface);
|
|
5199
|
-
-webkit-backdrop-filter: blur(1rem);
|
|
5200
|
-
backdrop-filter: blur(1rem);
|
|
5201
|
-
}
|
|
5202
|
-
|
|
5203
|
-
/* The translucent surface needs @supports, not a double declaration: the
|
|
5204
|
-
value contains var(), so a browser without color-mix() cannot reject it at
|
|
5205
|
-
parse time — it wins the cascade, then fails at computed-value time and
|
|
5206
|
-
the background disappears instead of falling back to the opaque line. */
|
|
5207
|
-
@supports (background: color-mix(in oklch, red 50%, transparent)) {
|
|
5208
|
-
.form-actions.sticky {
|
|
5209
|
-
background: color-mix(in oklch, var(--surface) 88%, transparent);
|
|
5210
|
-
}
|
|
5211
|
-
}
|
|
5212
|
-
|
|
5213
|
-
/*
|
|
5214
|
-
* Small, single-purpose utilities for common escape hatches.
|
|
5215
|
-
*
|
|
5216
|
-
* These support components and layout primitives without becoming a
|
|
5217
|
-
* utility-first API.
|
|
5218
|
-
*/
|
|
5219
|
-
.sr-only {
|
|
5220
|
-
position: absolute;
|
|
5221
|
-
inline-size: 1px;
|
|
5222
|
-
block-size: 1px;
|
|
5223
|
-
padding: 0;
|
|
5224
|
-
margin: -1px;
|
|
5225
|
-
overflow: hidden;
|
|
5226
|
-
clip: rect(0, 0, 0, 0);
|
|
5227
|
-
white-space: nowrap;
|
|
5228
|
-
border: 0;
|
|
5229
|
-
}
|
|
5230
|
-
|
|
5231
|
-
.overflow-auto {
|
|
5232
|
-
overflow: auto;
|
|
5233
|
-
}
|
|
5234
|
-
|
|
5235
|
-
.fit {
|
|
5236
|
-
inline-size: fit-content;
|
|
5237
|
-
max-inline-size: 100%;
|
|
5238
|
-
}
|
|
5239
|
-
|
|
5240
|
-
.grow {
|
|
5241
|
-
flex: 1 1 auto;
|
|
5242
|
-
min-inline-size: 0;
|
|
5243
|
-
}
|
|
5244
|
-
|
|
5245
|
-
.text-nowrap {
|
|
5246
|
-
white-space: nowrap;
|
|
5247
|
-
}
|
|
5248
|
-
|
|
5249
|
-
.truncate {
|
|
5250
|
-
min-inline-size: 0;
|
|
5251
|
-
overflow: hidden;
|
|
5252
|
-
text-overflow: ellipsis;
|
|
5253
|
-
white-space: nowrap;
|
|
5254
|
-
}
|
|
5255
|
-
|
|
5256
|
-
.items-start {
|
|
5257
|
-
--items-align: start;
|
|
5258
|
-
align-items: start;
|
|
5259
|
-
}
|
|
5260
|
-
|
|
5261
|
-
.items-center {
|
|
5262
|
-
--items-align: center;
|
|
5263
|
-
align-items: center;
|
|
5264
|
-
}
|
|
5265
|
-
|
|
5266
|
-
.items-end {
|
|
5267
|
-
--items-align: end;
|
|
5268
|
-
align-items: end;
|
|
5269
|
-
}
|
|
5270
|
-
|
|
5271
|
-
.muted {
|
|
5272
|
-
color: var(--text-muted);
|
|
5273
|
-
}
|
|
5274
|
-
|
|
5275
|
-
/* Circle: forces a fully round shape without mutating global radius tokens.
|
|
5276
|
-
Works on any component because utilities load after components. .join is
|
|
5277
|
-
the one exception: it writes corner longhands on its children, so
|
|
5278
|
-
.join.circle keeps its own --join-radius hook (join.css). */
|
|
5279
|
-
.circle {
|
|
5280
|
-
border-radius: var(--radius-full);
|
|
5281
|
-
}
|
|
5282
|
-
|
|
5283
|
-
/* Text alignment — uses logical properties so it follows writing direction. */
|
|
5284
|
-
.text-start {
|
|
5285
|
-
--text-align: start;
|
|
5286
|
-
text-align: start;
|
|
5287
|
-
}
|
|
5288
|
-
|
|
5289
|
-
.text-center {
|
|
5290
|
-
--text-align: center;
|
|
5291
|
-
text-align: center;
|
|
5292
|
-
}
|
|
5293
|
-
|
|
5294
|
-
.text-end {
|
|
5295
|
-
--text-align: end;
|
|
5296
|
-
text-align: end;
|
|
5297
|
-
}
|
|
5298
|
-
|
|
5299
|
-
/* Text wrapping utilities. Unsupported values are ignored by the browser. */
|
|
5300
|
-
.text-balance {
|
|
5301
|
-
text-wrap: balance;
|
|
5302
|
-
}
|
|
5303
|
-
|
|
5304
|
-
.text-pretty {
|
|
5305
|
-
text-wrap: pretty;
|
|
5306
|
-
}
|
|
5307
|
-
|
|
5308
|
-
/* Generic list chrome reset. Use for any list (tags, footer columns, related
|
|
5309
|
-
items). For navigation lists, prefer .nav-list for semantic intent.
|
|
5310
|
-
components/{actions,navbar}.css apply their own resets where needed. */
|
|
5311
|
-
.list-reset {
|
|
5312
|
-
margin: 0;
|
|
5313
|
-
padding: 0;
|
|
5314
|
-
list-style: none;
|
|
5315
|
-
}
|
|
5316
|
-
|
|
5317
|
-
/* Separator dot for inline metadata rows (byline, timestamp, read time,
|
|
5318
|
-
breadcrumb-like facts). Pair with a flex/cluster container; flex: none
|
|
5319
|
-
keeps a flex parent from shrinking it to nothing. */
|
|
5320
|
-
.dot {
|
|
5321
|
-
flex: none;
|
|
5322
|
-
inline-size: 0.25rem;
|
|
5323
|
-
block-size: 0.25rem;
|
|
5324
|
-
border-radius: var(--radius-full);
|
|
5325
|
-
background: var(--text-subtle);
|
|
5326
|
-
}
|
|
5327
|
-
|
|
5328
|
-
/* Link variants for chrome that should not look like prose links. Use in
|
|
5329
|
-
navigation, tags, footer, tables of contents, and similar surfaces. */
|
|
5330
|
-
.link-plain {
|
|
5331
|
-
color: inherit;
|
|
5332
|
-
text-decoration: none;
|
|
5333
|
-
}
|
|
5334
|
-
|
|
5335
|
-
.link-muted {
|
|
5336
|
-
color: var(--text-muted);
|
|
5337
|
-
text-decoration: none;
|
|
5338
|
-
}
|
|
5339
|
-
|
|
5340
|
-
.link-muted:hover {
|
|
5341
|
-
color: var(--text);
|
|
5342
|
-
}
|
|
5343
|
-
|
|
5344
|
-
/* Spacing step helpers — semantic scale (sm / default / lg).
|
|
5345
|
-
Names map to logical properties for writing-direction safety. The default
|
|
5346
|
-
step uses --space-40, which matches the framework's built-in padding on
|
|
5347
|
-
cards, dialogs, and form-actions. */
|
|
5348
|
-
|
|
5349
|
-
/* Gap */
|
|
5350
|
-
.gap-none {
|
|
5351
|
-
gap: 0;
|
|
5352
|
-
}
|
|
5353
|
-
|
|
5354
|
-
/* Padding — block (vertical) */
|
|
5355
|
-
.py {
|
|
5356
|
-
padding-block: var(--density-space);
|
|
5357
|
-
}
|
|
5358
|
-
|
|
5359
|
-
/* Padding — inline (horizontal) */
|
|
5360
|
-
.px {
|
|
5361
|
-
padding-inline: var(--density-space);
|
|
5362
|
-
}
|
|
5363
|
-
|
|
5364
|
-
/* Margin — block-start */
|
|
5365
|
-
.mbs {
|
|
5366
|
-
margin-block-start: var(--density-space);
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
/* Margin — block-end */
|
|
5370
|
-
.mbe {
|
|
5371
|
-
margin-block-end: var(--density-space);
|
|
5372
|
-
}
|
|
5373
|
-
|
|
5374
|
-
/* Joined controls are a component, but must load after the controls it groups. */
|
|
5375
|
-
/*
|
|
5376
|
-
* Join — visually groups adjacent controls into a single unit.
|
|
5377
|
-
*
|
|
5378
|
-
* This is a cross-cutting control primitive, not a forms-only component:
|
|
5379
|
-
* it applies equally to inputs, selects, buttons, and button-like links.
|
|
5380
|
-
*
|
|
5381
|
-
* Cascade note: .join writes border-radius longhands on its direct children.
|
|
5382
|
-
* Keep this file loaded after control/component styles so those corners win at
|
|
5383
|
-
* equal specificity.
|
|
5384
|
-
*
|
|
5385
|
-
* Public hooks:
|
|
5386
|
-
* --join-radius
|
|
5387
|
-
*
|
|
5388
|
-
* Internal:
|
|
5389
|
-
* --join-ss
|
|
5390
|
-
* --join-se
|
|
5391
|
-
* --join-es
|
|
5392
|
-
* --join-ee
|
|
5393
|
-
* --control-border
|
|
5394
|
-
* --control-bg
|
|
5395
|
-
* --control-fg
|
|
5396
|
-
*/
|
|
5397
|
-
.join {
|
|
5398
|
-
--join-radius: var(--radius);
|
|
5399
|
-
--join-ss: 0;
|
|
5400
|
-
--join-se: 0;
|
|
5401
|
-
--join-es: 0;
|
|
5402
|
-
--join-ee: 0;
|
|
5403
|
-
|
|
5404
|
-
align-items: stretch;
|
|
5405
|
-
display: inline-flex;
|
|
5406
|
-
}
|
|
5407
|
-
|
|
5408
|
-
/* The .circle utility cannot round a join: corners are written as longhands
|
|
5409
|
-
on the children below, so the group radius must flow through the token. */
|
|
5410
|
-
.join.circle {
|
|
5411
|
-
--join-radius: var(--radius-full);
|
|
5412
|
-
}
|
|
5413
|
-
|
|
5414
|
-
.join-addon {
|
|
5415
|
-
--control-border: var(--border);
|
|
5416
|
-
--control-bg: var(--surface-subtle);
|
|
5417
|
-
--control-fg: var(--text-muted);
|
|
5418
|
-
|
|
5419
|
-
align-items: center;
|
|
5420
|
-
display: inline-flex;
|
|
5421
|
-
justify-content: center;
|
|
5422
|
-
min-block-size: var(--control-size);
|
|
5423
|
-
padding-inline: var(--space-30);
|
|
5424
|
-
border: var(--border-width) solid var(--control-border);
|
|
5425
|
-
border-radius: var(--radius);
|
|
5426
|
-
background: var(--control-bg);
|
|
5427
|
-
color: var(--control-fg);
|
|
5428
|
-
font-size: var(--control-font-size);
|
|
5429
|
-
line-height: 1;
|
|
5430
|
-
white-space: nowrap;
|
|
5431
|
-
}
|
|
5432
|
-
|
|
5433
|
-
.join > * {
|
|
5434
|
-
border-start-start-radius: var(--join-ss);
|
|
5435
|
-
border-start-end-radius: var(--join-se);
|
|
5436
|
-
border-end-end-radius: var(--join-ee);
|
|
5437
|
-
border-end-start-radius: var(--join-es);
|
|
5438
|
-
}
|
|
5439
|
-
|
|
5440
|
-
.join > :first-child {
|
|
5441
|
-
--join-ss: var(--join-radius);
|
|
5442
|
-
--join-es: var(--join-radius);
|
|
5443
|
-
}
|
|
5444
|
-
|
|
5445
|
-
.join > :last-child {
|
|
5446
|
-
--join-se: var(--join-radius);
|
|
5447
|
-
--join-ee: var(--join-radius);
|
|
5448
|
-
}
|
|
5449
|
-
|
|
5450
|
-
.join > :only-child {
|
|
5451
|
-
--join-ss: var(--join-radius);
|
|
5452
|
-
--join-se: var(--join-radius);
|
|
5453
|
-
--join-es: var(--join-radius);
|
|
5454
|
-
--join-ee: var(--join-radius);
|
|
5455
|
-
}
|
|
5456
|
-
|
|
5457
|
-
.join > * + * {
|
|
5458
|
-
margin-inline-start: calc(var(--border-width) * -1);
|
|
5459
|
-
}
|
|
5460
|
-
|
|
5461
|
-
.join > :is(:hover, :focus, :focus-within) {
|
|
5462
|
-
z-index: 1;
|
|
5463
|
-
}
|
|
5464
|
-
|
|
5465
|
-
/* Keep an active focus ring above an adjacent hovered control. This matters
|
|
5466
|
-
on platforms where clicking a button does not move focus from the input. */
|
|
5467
|
-
.join > :is(:focus, :focus-within) {
|
|
5468
|
-
z-index: 2;
|
|
5469
|
-
}
|
|
5470
|
-
|
|
5471
|
-
/* An input's border must read as the "primary" edge against an attached
|
|
5472
|
-
action (e.g. a password-reveal button) at all times, not only while
|
|
5473
|
-
hovered or focused — otherwise the shared edge flips ownership as the
|
|
5474
|
-
user's pointer moves, which reads as a rendering glitch. */
|
|
5475
|
-
.join > :is(.input, .textarea, .select) {
|
|
5476
|
-
z-index: 3;
|
|
5477
|
-
}
|
|
5478
|
-
|
|
5479
|
-
/* An input/select with an attached action reads as one composite field. Move
|
|
5480
|
-
its outer focus treatment to the join; button-only groups keep per-button
|
|
5481
|
-
focus, and unsupported :has() browsers retain the control's normal ring. */
|
|
5482
|
-
.join:has(> :is(.input, .textarea, .select):focus-visible) {
|
|
5483
|
-
border-radius: var(--join-radius);
|
|
5484
|
-
outline: 2px solid transparent;
|
|
5485
|
-
box-shadow: var(--focus-ring-shadow);
|
|
5486
|
-
}
|
|
5487
|
-
|
|
5488
|
-
.join:has(> :is(.input, .textarea, .select)) > :is(.input, .textarea, .select):focus-visible {
|
|
5489
|
-
outline: none;
|
|
5490
|
-
box-shadow: none;
|
|
5491
|
-
}
|
|
5492
|
-
|
|
5493
|
-
/* Focus baseline — a visible outline is the guaranteed focus affordance. The
|
|
5494
|
-
:focus outline covers legacy browsers; modern browsers hide it for
|
|
5495
|
-
pointer/mouse focus and interactive components layer their ring on top.
|
|
5496
|
-
|
|
5497
|
-
Invariant: an interactive component must never rely on box-shadow alone
|
|
5498
|
-
for its keyboard focus indicator. Components that remove the native
|
|
5499
|
-
outline on :focus-visible replace it with a transparent outline — invisible
|
|
5500
|
-
normally, forced to the system focus color under forced-colors. */
|
|
5501
|
-
:focus {
|
|
5502
|
-
outline: var(--focus-outline);
|
|
5503
|
-
outline-offset: var(--focus-outline-offset);
|
|
5504
|
-
}
|
|
5505
|
-
|
|
5506
|
-
@supports selector(:focus-visible) {
|
|
5507
|
-
:focus:not(:focus-visible) {
|
|
5508
|
-
outline: none;
|
|
5509
|
-
}
|
|
5510
|
-
|
|
5511
|
-
/* Pointer focus must still signal in high-contrast mode, where the shared
|
|
5512
|
-
box-shadow rings are disabled (--focus-ring-shadow: none). */
|
|
5513
|
-
@media (forced-colors: active) {
|
|
5514
|
-
:focus:not(:focus-visible) {
|
|
5515
|
-
outline: var(--focus-outline);
|
|
5516
|
-
}
|
|
5517
|
-
}
|
|
5518
|
-
}
|
|
5519
|
-
|
|
5520
|
-
@media print {
|
|
5521
|
-
:root,
|
|
5522
|
-
body {
|
|
5523
|
-
/* Save some ink in dark mode */
|
|
5524
|
-
color-scheme: light;
|
|
5525
|
-
background: #fff;
|
|
5526
|
-
color: #000;
|
|
5527
|
-
}
|
|
5528
|
-
|
|
5529
|
-
.flyout,
|
|
5530
|
-
.tooltip,
|
|
5531
|
-
.surface-backdrop,
|
|
5532
|
-
.status-bar,
|
|
5533
|
-
.spinner,
|
|
5534
|
-
dialog.modal[open],
|
|
5535
|
-
dialog.drawer[open] {
|
|
5536
|
-
display: none !important;
|
|
5537
|
-
}
|
|
5538
|
-
|
|
5539
|
-
button.btn,
|
|
5540
|
-
input[type="button"].btn,
|
|
5541
|
-
input[type="submit"].btn,
|
|
5542
|
-
input[type="reset"].btn {
|
|
5543
|
-
display: none !important;
|
|
5544
|
-
}
|
|
5545
|
-
|
|
5546
|
-
a.btn[href^="http"]::after {
|
|
5547
|
-
content: " (" attr(href) ")";
|
|
5548
|
-
font-size: 0.875em;
|
|
5549
|
-
}
|
|
5550
|
-
|
|
5551
|
-
.card,
|
|
5552
|
-
.alert,
|
|
5553
|
-
.badge {
|
|
5554
|
-
border: var(--border-width) solid CanvasText;
|
|
5555
|
-
break-inside: avoid;
|
|
5556
|
-
}
|
|
5557
|
-
|
|
5558
|
-
h1,
|
|
5559
|
-
h2,
|
|
5560
|
-
h3,
|
|
5561
|
-
h4,
|
|
5562
|
-
h5,
|
|
5563
|
-
h6 {
|
|
5564
|
-
break-after: avoid;
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
/*
|
|
5570
|
-
* NOTE: The 'optional/' directory contains layout and utility extensions
|
|
5571
|
-
* (such as optional grids, split, visual effects, and extra utility classes
|
|
5572
|
-
* like .gap-sm, .flex-nowrap, .shadow-sm, etc.). These are intentionally not
|
|
5573
|
-
* bundled here. Two entry points ship them:
|
|
5574
|
-
*
|
|
5575
|
-
* actual-css/css/optional/index optional layer only
|
|
5576
|
-
* actual-css/css/actual.full core + optional
|
|
5577
|
-
*
|
|
5578
|
-
* Import one of these paths in your project when you need them.
|
|
5579
|
-
*/
|