actual-css 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -1
- package/README.md +6 -2
- package/components.json +14 -3
- package/dist/actual.full.css +483 -128
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/platform-alignment.md +8 -4
- package/docs/design-notes/rich-text.md +61 -0
- package/docs/design-notes/steps.md +2 -2
- package/docs/navigation.json +4 -1
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +10 -0
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/button.md +1 -1
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +145 -13
- package/docs/pages/components/meter.md +2 -0
- package/docs/pages/components/pagination.md +15 -15
- package/docs/pages/components/shimmer.md +57 -0
- package/docs/pages/components/skeleton.md +2 -1
- package/docs/pages/components/spinner.md +11 -6
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/examples/overview.md +1 -0
- package/docs/pages/forms/choice-cards.md +77 -4
- package/docs/pages/forms/combobox.md +16 -1
- package/docs/pages/forms/overview.md +4 -1
- package/docs/pages/forms/range.md +110 -5
- package/docs/pages/forms/rich-text.md +110 -0
- package/docs/pages/forms/validation.md +5 -1
- package/docs/pages/foundations/theming.md +49 -3
- package/docs/pages/foundations/tokens.md +15 -5
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/design-tools.md +36 -0
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +13 -14
- package/docs/pages/guides/tailwind.md +4 -2
- package/llms.txt +4 -3
- package/package.json +6 -1
- package/reserved-classes.json +1 -0
- package/skills/actual-css/references/enhancements.md +35 -0
- package/src/cli/actual-css.js +49 -6
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/bleed.css +5 -5
- package/src/css/components/button.css +14 -1
- package/src/css/components/close.css +17 -6
- package/src/css/components/flyout.css +4 -1
- package/src/css/components/meter.css +6 -3
- package/src/css/components/modal.css +123 -7
- package/src/css/components/pagination.css +3 -2
- package/src/css/components/rating.css +1 -1
- package/src/css/components/status-bar.css +2 -2
- package/src/css/components/steps.css +3 -3
- package/src/css/components/tab.css +3 -3
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +1 -1
- package/src/css/core/focus.css +14 -4
- package/src/css/core/theme.css +8 -0
- package/src/css/core/tokens.css +8 -4
- package/src/css/core/variants.css +2 -1
- package/src/css/effects/aura.css +51 -33
- package/src/css/effects/index.css +1 -0
- package/src/css/effects/shimmer.css +57 -0
- package/src/css/forms/choice-card.css +71 -23
- package/src/css/forms/control.css +2 -2
- package/src/css/forms/native.css +5 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +64 -21
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +8 -23
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +84 -123
- package/src/js/full.js +1 -0
- package/src/js/password.js +1 -8
- package/src/js/range.js +151 -0
- package/src/js/scrollspy.js +4 -5
- package/src/js/status.js +2 -10
- package/src/js/tooltip.js +11 -1
- package/src/js/validation.js +4 -10
- package/src/tooling/design-components.js +286 -0
- package/src/tooling/design-export.js +69 -0
- package/src/tooling/design-tokens.js +466 -0
- package/src/tooling/penpot-plugin/manifest.json +7 -0
- package/src/tooling/penpot-plugin/plugin.js +272 -0
- package/src/tooling/penpot-plugin/ui.html +58 -0
package/dist/actual.full.css
CHANGED
|
@@ -130,9 +130,13 @@ select {
|
|
|
130
130
|
--ease-enter: cubic-bezier(0.2, 0, 0, 1);
|
|
131
131
|
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
|
132
132
|
|
|
133
|
-
/*
|
|
133
|
+
/* Generic focus fallback — an adaptive currentColor line for elements
|
|
134
|
+
without a component ring (links, summary, plain items). Components draw
|
|
135
|
+
the themed ring instead: --focus-ring-width solid --focus. See focus.css
|
|
136
|
+
for the two contracts. Both share --focus-ring-width: they differ in
|
|
137
|
+
color only. */
|
|
134
138
|
--focus-outline-color: currentColor;
|
|
135
|
-
--focus-outline:
|
|
139
|
+
--focus-outline: var(--focus-ring-width) solid var(--focus-outline-color);
|
|
136
140
|
--focus-outline-offset: 2px;
|
|
137
141
|
--focus-ring-width: 2px;
|
|
138
142
|
|
|
@@ -195,8 +199,8 @@ select {
|
|
|
195
199
|
--state-selected-fg: var(--primary-fg);
|
|
196
200
|
--state-disabled: var(--text-subtle);
|
|
197
201
|
|
|
198
|
-
/* Status dot ring —
|
|
199
|
-
--indicator-ring: var(--surface);
|
|
202
|
+
/* Status dot ring — cuts the dot out of what it overlaps. */
|
|
203
|
+
--indicator-ring: var(--surface-opaque);
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
:root {
|
|
@@ -291,6 +295,8 @@ select {
|
|
|
291
295
|
* Shadow color, Focus ring derivation.
|
|
292
296
|
* Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
|
|
293
297
|
* other tokens; hard-coded overrides on a custom theme will break the intent.
|
|
298
|
+
* Exception: --surface-opaque and --surface-solid-fg are public; a theme with
|
|
299
|
+
* translucent surfaces overrides them with opaque values.
|
|
294
300
|
* Internal: --hgroup-gap.
|
|
295
301
|
*/
|
|
296
302
|
:root {
|
|
@@ -382,6 +388,12 @@ select {
|
|
|
382
388
|
--heading: var(--text);
|
|
383
389
|
--selection-bg: var(--primary);
|
|
384
390
|
--selection-fg: var(--primary-fg);
|
|
391
|
+
/* Surface roles that must stay opaque when a theme paints translucent
|
|
392
|
+
surfaces; both equal --surface on an opaque palette. --surface-opaque
|
|
393
|
+
hides or separates what is painted beneath a ring, separator or marker —
|
|
394
|
+
never a fill. --surface-solid-fg is the ink on --surface-solid. */
|
|
395
|
+
--surface-opaque: var(--surface);
|
|
396
|
+
--surface-solid-fg: var(--surface);
|
|
385
397
|
}
|
|
386
398
|
|
|
387
399
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
@@ -719,7 +731,8 @@ small {
|
|
|
719
731
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
720
732
|
component (field, choice-card, floating-field, input-icon, join, pagination,
|
|
721
733
|
tabs, flyout). A local size declaration beats inherited density because it
|
|
722
|
-
is declared on the sized component itself.
|
|
734
|
+
is declared on the sized component itself. Guarded by check:sync
|
|
735
|
+
(size-scale-participants): .sm and .lg must list the same components. */
|
|
723
736
|
:where(
|
|
724
737
|
.btn,
|
|
725
738
|
.input,
|
|
@@ -798,12 +811,22 @@ small {
|
|
|
798
811
|
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
799
812
|
shadows.
|
|
800
813
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
814
|
+
Two contracts share the job, both --focus-ring-width wide. Text-like
|
|
815
|
+
elements, whose ink is the thing being focused (links, summary, .nav-link,
|
|
816
|
+
.menu-item, breadcrumb links, .navbar-brand), declare no outline and take
|
|
817
|
+
the generic fallback below: a currentColor line that follows whatever the
|
|
818
|
+
application painted. Boxed components, with a fill or border of their own,
|
|
819
|
+
draw the themed ring instead — a solid line in --focus: inset on fields
|
|
820
|
+
(inside their border box), offset by --focus-outline-offset on discrete
|
|
821
|
+
actions. The color never derives from an intent or a local
|
|
804
822
|
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
805
823
|
--surface-solid, so one token reads on the page, on a solid band and on
|
|
806
|
-
any nested surface.
|
|
824
|
+
any nested surface.
|
|
825
|
+
|
|
826
|
+
Documented exception: an invalid field focuses in --form-invalid-border
|
|
827
|
+
(danger), so the error stays visible at the point of correction. The border
|
|
828
|
+
carries the same hook; report:theme-contrast measures that line at 3:1
|
|
829
|
+
against the field's background alongside --focus. */
|
|
807
830
|
:focus {
|
|
808
831
|
outline: var(--focus-outline);
|
|
809
832
|
outline-offset: var(--focus-outline-offset);
|
|
@@ -2040,8 +2063,8 @@ small {
|
|
|
2040
2063
|
cursor: var(--control-cursor);
|
|
2041
2064
|
/* No outline: none here — the core :focus fallback must survive for browsers
|
|
2042
2065
|
without :focus-visible. Pointer focus is hidden by core's
|
|
2043
|
-
:focus:not(:focus-visible); keyboard focus
|
|
2044
|
-
|
|
2066
|
+
:focus:not(:focus-visible); keyboard focus draws the inset outline below
|
|
2067
|
+
(invalid fields take --form-invalid-border, the documented exception). */
|
|
2045
2068
|
transition:
|
|
2046
2069
|
border-color var(--duration),
|
|
2047
2070
|
box-shadow var(--duration),
|
|
@@ -2425,7 +2448,11 @@ small {
|
|
|
2425
2448
|
the whole control, not just the filename text. */
|
|
2426
2449
|
min-block-size: var(--control-size);
|
|
2427
2450
|
padding-block: var(--space-10);
|
|
2428
|
-
|
|
2451
|
+
/* Border-box height: both borders come out of the padding, as on .btn. */
|
|
2452
|
+
padding-block: max(
|
|
2453
|
+
var(--space-10),
|
|
2454
|
+
calc((var(--control-size) - 1lh - 2 * var(--border-width)) / 2)
|
|
2455
|
+
);
|
|
2429
2456
|
padding-inline: var(--space-20);
|
|
2430
2457
|
border: var(--border-width) solid var(--border);
|
|
2431
2458
|
border-radius: var(--radius-sm);
|
|
@@ -2889,6 +2916,10 @@ small {
|
|
|
2889
2916
|
* --range-track-height
|
|
2890
2917
|
* --range-thumb-bg
|
|
2891
2918
|
* --range-track-bg
|
|
2919
|
+
* --range-fill-bg
|
|
2920
|
+
*
|
|
2921
|
+
* Runtime-written (by actual-css/js/range, never set by hand):
|
|
2922
|
+
* --range-progress
|
|
2892
2923
|
*/
|
|
2893
2924
|
|
|
2894
2925
|
/* Intent boundary — inherited intents do not tint this component.
|
|
@@ -2898,10 +2929,17 @@ small {
|
|
|
2898
2929
|
}
|
|
2899
2930
|
|
|
2900
2931
|
.range {
|
|
2901
|
-
--range-thumb-size: 1.
|
|
2902
|
-
--range-track-height: 0.
|
|
2932
|
+
--range-thumb-size: 1.125rem;
|
|
2933
|
+
--range-track-height: 0.625rem;
|
|
2903
2934
|
--range-thumb-bg: var(--intent, var(--primary));
|
|
2904
2935
|
--range-track-bg: var(--border);
|
|
2936
|
+
--range-fill-bg: var(--intent, var(--primary));
|
|
2937
|
+
/* Fill share, written by actual-css/js/range. 0% with no runtime means a
|
|
2938
|
+
plain track — the control stays fully usable without JavaScript. */
|
|
2939
|
+
--range-progress: 0%;
|
|
2940
|
+
/* Logical fill direction: the gradient below cannot use flow-relative
|
|
2941
|
+
keywords, so RTL flips the angle through this hook instead. */
|
|
2942
|
+
--range-fill-from: to right;
|
|
2905
2943
|
|
|
2906
2944
|
appearance: none;
|
|
2907
2945
|
-webkit-appearance: none;
|
|
@@ -2911,16 +2949,29 @@ small {
|
|
|
2911
2949
|
cursor: pointer;
|
|
2912
2950
|
}
|
|
2913
2951
|
|
|
2952
|
+
:where([dir="rtl"]) .range,
|
|
2953
|
+
.range:dir(rtl) {
|
|
2954
|
+
--range-fill-from: to left;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2914
2957
|
.range::-webkit-slider-runnable-track {
|
|
2915
2958
|
block-size: var(--range-track-height);
|
|
2916
2959
|
border-radius: var(--radius-full);
|
|
2917
|
-
background:
|
|
2960
|
+
background: linear-gradient(
|
|
2961
|
+
var(--range-fill-from),
|
|
2962
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2963
|
+
var(--range-track-bg) var(--range-progress)
|
|
2964
|
+
);
|
|
2918
2965
|
}
|
|
2919
2966
|
|
|
2920
2967
|
.range::-moz-range-track {
|
|
2921
2968
|
block-size: var(--range-track-height);
|
|
2922
2969
|
border-radius: var(--radius-full);
|
|
2923
|
-
background:
|
|
2970
|
+
background: linear-gradient(
|
|
2971
|
+
var(--range-fill-from),
|
|
2972
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2973
|
+
var(--range-track-bg) var(--range-progress)
|
|
2974
|
+
);
|
|
2924
2975
|
}
|
|
2925
2976
|
|
|
2926
2977
|
.range::-webkit-slider-thumb {
|
|
@@ -2933,6 +2984,10 @@ small {
|
|
|
2933
2984
|
background: var(--range-thumb-bg);
|
|
2934
2985
|
margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
|
|
2935
2986
|
cursor: inherit;
|
|
2987
|
+
/* Permanent surface separator: the fill defaults to the thumb color, so
|
|
2988
|
+
without this the thumb would melt into the filled part of the track.
|
|
2989
|
+
Focus-visible keeps this ring and adds the --focus ring outside it. */
|
|
2990
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
2936
2991
|
transition: box-shadow var(--duration);
|
|
2937
2992
|
}
|
|
2938
2993
|
|
|
@@ -2943,6 +2998,7 @@ small {
|
|
|
2943
2998
|
border: 0;
|
|
2944
2999
|
background: var(--range-thumb-bg);
|
|
2945
3000
|
cursor: inherit;
|
|
3001
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
2946
3002
|
transition: box-shadow var(--duration);
|
|
2947
3003
|
}
|
|
2948
3004
|
|
|
@@ -2953,20 +3009,21 @@ small {
|
|
|
2953
3009
|
/*
|
|
2954
3010
|
* A thumb pseudo-element takes no reliable outline, so its ring is a solid
|
|
2955
3011
|
* --focus shadow (the transparent outline above covers forced colors). The
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
* read as an unwanted void.
|
|
3012
|
+
* surface-coloured separator keeps the ring visually distinct from the
|
|
3013
|
+
* coloured thumb — unlike a full-size input where the same gap would
|
|
3014
|
+
* read as an unwanted void. It doubles as the resting separator between
|
|
3015
|
+
* the thumb and the same-coloured fill.
|
|
2959
3016
|
*/
|
|
2960
3017
|
.range:focus-visible::-webkit-slider-thumb {
|
|
2961
3018
|
box-shadow:
|
|
2962
|
-
0 0 0
|
|
2963
|
-
0 0 0 calc(
|
|
3019
|
+
0 0 0 2px var(--surface-opaque),
|
|
3020
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
2964
3021
|
}
|
|
2965
3022
|
|
|
2966
3023
|
.range:focus-visible::-moz-range-thumb {
|
|
2967
3024
|
box-shadow:
|
|
2968
|
-
0 0 0
|
|
2969
|
-
0 0 0 calc(
|
|
3025
|
+
0 0 0 2px var(--surface-opaque),
|
|
3026
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
2970
3027
|
}
|
|
2971
3028
|
|
|
2972
3029
|
.range:disabled {
|
|
@@ -2974,28 +3031,41 @@ small {
|
|
|
2974
3031
|
cursor: not-allowed;
|
|
2975
3032
|
opacity: var(--disabled-opacity);
|
|
2976
3033
|
}
|
|
2977
|
-
|
|
2978
3034
|
/* Forced colors: the track must stay a visible line against the page (Canvas
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3035
|
+
would merge with it) and the thumb needs a real boundary to remain
|
|
3036
|
+
distinguishable from the track. The fill keeps its two-tone split with
|
|
3037
|
+
system colors so the value stays readable. Disabled keeps GrayText via
|
|
3038
|
+
:disabled. Focus stays on the transparent outline of :focus-visible. Track
|
|
3039
|
+
and thumb fills are written on the properties, not only the hooks: an
|
|
3040
|
+
application hook override at the same specificity loads after this file
|
|
3041
|
+
and would win the variable, leaving a Canvas-remapped fill.
|
|
3042
|
+
The split lives in a background-image, which forced-color-adjust: auto
|
|
3043
|
+
drops — so the tracks opt out locally. The colors stay system keywords,
|
|
3044
|
+
keeping native contrast adaptation; only the host keeps adjusting. */
|
|
2985
3045
|
@media (forced-colors: active) {
|
|
2986
3046
|
.range {
|
|
2987
3047
|
--range-track-bg: CanvasText;
|
|
2988
3048
|
--range-thumb-bg: ButtonFace;
|
|
3049
|
+
--range-fill-bg: Highlight;
|
|
2989
3050
|
}
|
|
2990
3051
|
|
|
2991
3052
|
.range::-webkit-slider-runnable-track {
|
|
2992
|
-
|
|
3053
|
+
forced-color-adjust: none;
|
|
3054
|
+
background: linear-gradient(
|
|
3055
|
+
var(--range-fill-from),
|
|
3056
|
+
Highlight var(--range-progress),
|
|
3057
|
+
CanvasText var(--range-progress)
|
|
3058
|
+
);
|
|
2993
3059
|
}
|
|
2994
3060
|
|
|
2995
3061
|
.range::-moz-range-track {
|
|
2996
|
-
|
|
3062
|
+
forced-color-adjust: none;
|
|
3063
|
+
background: linear-gradient(
|
|
3064
|
+
var(--range-fill-from),
|
|
3065
|
+
Highlight var(--range-progress),
|
|
3066
|
+
CanvasText var(--range-progress)
|
|
3067
|
+
);
|
|
2997
3068
|
}
|
|
2998
|
-
|
|
2999
3069
|
.range::-webkit-slider-thumb {
|
|
3000
3070
|
background: ButtonFace;
|
|
3001
3071
|
border: var(--border-width) solid ButtonText;
|
|
@@ -3014,13 +3084,15 @@ small {
|
|
|
3014
3084
|
*
|
|
3015
3085
|
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
3016
3086
|
* :has() is supported the input is visually hidden and the card mirrors its
|
|
3017
|
-
* state:
|
|
3018
|
-
*
|
|
3019
|
-
* native input stays visible
|
|
3087
|
+
* state: an indicator on the leading edge (a ring for radio, a box for
|
|
3088
|
+
* checkbox) that fills with a check, checked border and tint, focus ring,
|
|
3089
|
+
* disabled dimming. Without :has() the native input stays visible in the
|
|
3090
|
+
* indicator's slot, so selection state never disappears.
|
|
3020
3091
|
*
|
|
3021
|
-
* The
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3092
|
+
* The card is a two-column grid: the indicator in the first column, level
|
|
3093
|
+
* with the first row, and each child element of the label on its own row in
|
|
3094
|
+
* the second. Wrap loose text in an element. A corner badge for tiles is a
|
|
3095
|
+
* documented recipe on these same layers, not a variant.
|
|
3024
3096
|
*
|
|
3025
3097
|
* .chip is the compact pill form (filter chips, selectable tags): control
|
|
3026
3098
|
* height, and a leading dot that turns into the check in a fixed slot, so
|
|
@@ -3028,6 +3100,7 @@ small {
|
|
|
3028
3100
|
*
|
|
3029
3101
|
* Public hooks:
|
|
3030
3102
|
* --choice-card-pad
|
|
3103
|
+
* --choice-card-gap
|
|
3031
3104
|
* --choice-card-radius
|
|
3032
3105
|
* --choice-card-border
|
|
3033
3106
|
* --choice-card-bg
|
|
@@ -3042,19 +3115,25 @@ small {
|
|
|
3042
3115
|
|
|
3043
3116
|
.choice-card {
|
|
3044
3117
|
--choice-card-pad: var(--density-space, var(--space-40));
|
|
3118
|
+
--choice-card-gap: var(--space-30);
|
|
3045
3119
|
--choice-card-radius: var(--radius);
|
|
3046
3120
|
--choice-card-border: var(--border);
|
|
3047
3121
|
--choice-card-bg: var(--surface);
|
|
3048
|
-
--choice-card-check-size: 1.
|
|
3122
|
+
--choice-card-check-size: 1.25em;
|
|
3049
3123
|
|
|
3050
3124
|
position: relative;
|
|
3051
|
-
display:
|
|
3125
|
+
display: grid;
|
|
3126
|
+
grid-template-columns: var(--choice-card-check-size) minmax(0, 1fr);
|
|
3127
|
+
/* why: a card stretched by a taller grid neighbour keeps its rows at the
|
|
3128
|
+
top instead of spreading them. */
|
|
3129
|
+
align-content: start;
|
|
3130
|
+
column-gap: var(--choice-card-gap);
|
|
3052
3131
|
padding: var(--choice-card-pad);
|
|
3053
3132
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
3054
3133
|
border-radius: var(--choice-card-radius);
|
|
3055
3134
|
background: var(--choice-card-bg);
|
|
3056
3135
|
color: var(--text);
|
|
3057
|
-
/* Consumed so .sm/.lg reach the card; the
|
|
3136
|
+
/* Consumed so .sm/.lg reach the card; the indicator's em sizing follows. */
|
|
3058
3137
|
font-size: var(--control-font-size);
|
|
3059
3138
|
cursor: pointer;
|
|
3060
3139
|
transition:
|
|
@@ -3063,6 +3142,18 @@ small {
|
|
|
3063
3142
|
box-shadow var(--duration);
|
|
3064
3143
|
}
|
|
3065
3144
|
|
|
3145
|
+
.choice-card > * {
|
|
3146
|
+
grid-column: 2;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
/* The input owns the indicator's cell: visible there without :has(), hidden
|
|
3150
|
+
in it with :has(). */
|
|
3151
|
+
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
3152
|
+
grid-area: 1 / 1;
|
|
3153
|
+
place-self: center;
|
|
3154
|
+
margin: 0;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3066
3157
|
/* The native input is visually hidden but stays focusable and participates in
|
|
3067
3158
|
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
3068
3159
|
:has() the card cannot mirror the input's state, so the visible native
|
|
@@ -3108,24 +3199,31 @@ small {
|
|
|
3108
3199
|
}
|
|
3109
3200
|
}
|
|
3110
3201
|
|
|
3111
|
-
/*
|
|
3112
|
-
the
|
|
3202
|
+
/* Indicator: ::before is the ring or box, ::after the check. Both share the
|
|
3203
|
+
input's cell, centred on the first line — not the first row, which a long
|
|
3204
|
+
label wraps — and stay hidden until :has() can mirror the input. The em
|
|
3205
|
+
margin is the lh-less fallback at the default 1.5 line-height. */
|
|
3113
3206
|
.choice-card::before,
|
|
3114
3207
|
.choice-card::after {
|
|
3115
3208
|
content: "";
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3209
|
+
grid-area: 1 / 1;
|
|
3210
|
+
align-self: start;
|
|
3211
|
+
margin-block-start: calc((1.5em - var(--choice-card-check-size)) / 2);
|
|
3212
|
+
margin-block-start: calc((1lh - var(--choice-card-check-size)) / 2);
|
|
3119
3213
|
inline-size: var(--choice-card-check-size);
|
|
3120
3214
|
block-size: var(--choice-card-check-size);
|
|
3121
3215
|
transform: scale(0);
|
|
3122
|
-
transition:
|
|
3216
|
+
transition:
|
|
3217
|
+
transform var(--duration),
|
|
3218
|
+
background-color var(--duration),
|
|
3219
|
+
border-color var(--duration);
|
|
3123
3220
|
}
|
|
3124
3221
|
|
|
3125
|
-
/*
|
|
3222
|
+
/* A border, not an inset shadow: forced colors drop shadows, and the resting
|
|
3223
|
+
ring is the only sign of an unchecked option. The invalid border reaches it
|
|
3224
|
+
through the same hook as the card's. */
|
|
3126
3225
|
.choice-card::before {
|
|
3127
|
-
|
|
3128
|
-
box-shadow: 0 0 0 2px var(--surface);
|
|
3226
|
+
border: max(var(--border-width), 1.5px) solid var(--form-invalid-border, var(--text-muted));
|
|
3129
3227
|
z-index: 1;
|
|
3130
3228
|
}
|
|
3131
3229
|
|
|
@@ -3147,7 +3245,19 @@ small {
|
|
|
3147
3245
|
z-index: 2;
|
|
3148
3246
|
}
|
|
3149
3247
|
|
|
3150
|
-
|
|
3248
|
+
/* Guarded with the input's hiding above: a resting ring beside a visible
|
|
3249
|
+
native input would draw the indicator twice. */
|
|
3250
|
+
@supports selector(:has(*)) {
|
|
3251
|
+
.choice-card::before {
|
|
3252
|
+
transform: none;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
.choice-card:has(:checked)::before {
|
|
3257
|
+
border-color: var(--intent, var(--state-selected));
|
|
3258
|
+
background: var(--intent, var(--state-selected));
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3151
3261
|
.choice-card:has(:checked)::after {
|
|
3152
3262
|
transform: scale(1);
|
|
3153
3263
|
}
|
|
@@ -3173,11 +3283,16 @@ small {
|
|
|
3173
3283
|
.choice-card.chip::before {
|
|
3174
3284
|
--choice-chip-dot: calc(var(--control-pad-x) + var(--choice-card-check-size) / 2) 50%;
|
|
3175
3285
|
|
|
3286
|
+
position: absolute;
|
|
3176
3287
|
inset: 0;
|
|
3288
|
+
/* why: an absolute box honours self-alignment — the card's start would
|
|
3289
|
+
shrink this layer to 0 height and the dot with it. */
|
|
3290
|
+
align-self: auto;
|
|
3291
|
+
margin: 0;
|
|
3177
3292
|
inline-size: auto;
|
|
3178
3293
|
block-size: auto;
|
|
3294
|
+
border: 0;
|
|
3179
3295
|
border-radius: inherit;
|
|
3180
|
-
box-shadow: none;
|
|
3181
3296
|
background: var(--text-subtle);
|
|
3182
3297
|
transform: none;
|
|
3183
3298
|
clip-path: circle(0.1875em at var(--choice-chip-dot));
|
|
@@ -3191,10 +3306,11 @@ small {
|
|
|
3191
3306
|
|
|
3192
3307
|
/* The check keeps the card's scale-in, in flow ahead of the label. */
|
|
3193
3308
|
.choice-card.chip::after {
|
|
3194
|
-
position: static;
|
|
3195
3309
|
order: -1;
|
|
3196
3310
|
flex: none;
|
|
3197
|
-
|
|
3311
|
+
align-self: center;
|
|
3312
|
+
margin-block: 0;
|
|
3313
|
+
margin-inline: 0 0.375em;
|
|
3198
3314
|
background: var(--intent, var(--state-selected));
|
|
3199
3315
|
-webkit-mask-size: contain;
|
|
3200
3316
|
mask-size: contain;
|
|
@@ -3204,9 +3320,11 @@ small {
|
|
|
3204
3320
|
this repair, forced colors pushes disc and check to the same system color
|
|
3205
3321
|
and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
|
|
3206
3322
|
so no forced-color-adjust is needed. The pseudo is not focusable — the
|
|
3207
|
-
outline
|
|
3323
|
+
outline separates the disc from whatever it overlaps (the card's border,
|
|
3324
|
+
for a corner-badge recipe), not a focus channel. */
|
|
3208
3325
|
@media (forced-colors: active) {
|
|
3209
3326
|
.choice-card:has(:checked)::before {
|
|
3327
|
+
border-color: Highlight;
|
|
3210
3328
|
background: Highlight;
|
|
3211
3329
|
outline: 2px solid Canvas;
|
|
3212
3330
|
}
|
|
@@ -3502,7 +3620,7 @@ small {
|
|
|
3502
3620
|
padding-block: 0;
|
|
3503
3621
|
padding-inline: calc((var(--otp-cell-size) - 1ch) / 2) 0;
|
|
3504
3622
|
border: 0;
|
|
3505
|
-
outline:
|
|
3623
|
+
outline: none;
|
|
3506
3624
|
box-shadow: none;
|
|
3507
3625
|
background: transparent;
|
|
3508
3626
|
color: var(--text);
|
|
@@ -3527,6 +3645,14 @@ small {
|
|
|
3527
3645
|
character after it: the state rules select the cells as following siblings
|
|
3528
3646
|
(input:focus ~ span, input:disabled ~ span, …), so that order is part of the
|
|
3529
3647
|
contract. */
|
|
3648
|
+
/* The documented markup puts .input on the native input, so the field ring
|
|
3649
|
+
(.input:focus-visible:not(:disabled), control.css) would outrank the reset
|
|
3650
|
+
above and draw that group ring; this selector must stay one step above it.
|
|
3651
|
+
No transparent outline either: forced colors would paint it as a ring. */
|
|
3652
|
+
.otp > input:focus-visible:not(:disabled) {
|
|
3653
|
+
outline: none;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3530
3656
|
.otp > input:focus ~ span {
|
|
3531
3657
|
--otp-cell-border: var(--focus);
|
|
3532
3658
|
--otp-cell-border-width: calc(var(--border-width) * 2);
|
|
@@ -3613,6 +3739,7 @@ small {
|
|
|
3613
3739
|
* --btn-default-hover-bg
|
|
3614
3740
|
* --btn-border-block-end-color
|
|
3615
3741
|
* --btn-border-block-end-width
|
|
3742
|
+
* --btn-border-block
|
|
3616
3743
|
* --spinner-width
|
|
3617
3744
|
*/
|
|
3618
3745
|
|
|
@@ -3638,6 +3765,10 @@ small {
|
|
|
3638
3765
|
--btn-default-hover-bg: var(--btn-bg);
|
|
3639
3766
|
--btn-radius: var(--radius);
|
|
3640
3767
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
3768
|
+
--btn-border-block: calc(
|
|
3769
|
+
var(--border-width) +
|
|
3770
|
+
var(--btn-border-block-end-width, var(--border-width))
|
|
3771
|
+
);
|
|
3641
3772
|
|
|
3642
3773
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
3643
3774
|
position: relative;
|
|
@@ -3647,7 +3778,13 @@ small {
|
|
|
3647
3778
|
gap: var(--btn-gap, 0.375em);
|
|
3648
3779
|
min-block-size: var(--btn-min-size);
|
|
3649
3780
|
padding-block: var(--space-20);
|
|
3650
|
-
|
|
3781
|
+
/* why: centres one line in --btn-min-size and keeps that rhythm when the
|
|
3782
|
+
label wraps. min-block-size is a border-box length, so both borders come
|
|
3783
|
+
out of it; without them .btn stood 2px taller than .input. */
|
|
3784
|
+
padding-block: max(
|
|
3785
|
+
var(--space-10),
|
|
3786
|
+
calc((var(--btn-min-size) - 1lh - var(--btn-border-block)) / 2)
|
|
3787
|
+
);
|
|
3651
3788
|
padding-inline: var(--btn-pad-x);
|
|
3652
3789
|
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
3653
3790
|
border-block-end-color: var(
|
|
@@ -3744,6 +3881,8 @@ small {
|
|
|
3744
3881
|
--ui-fg: var(--intent, currentColor);
|
|
3745
3882
|
--ui-border: transparent;
|
|
3746
3883
|
--btn-radius: var(--radius-sm);
|
|
3884
|
+
/* border: 0 below; the padding must not subtract a border it lacks. */
|
|
3885
|
+
--btn-border-block: 0px;
|
|
3747
3886
|
|
|
3748
3887
|
min-block-size: auto;
|
|
3749
3888
|
padding-inline: 0;
|
|
@@ -3835,8 +3974,17 @@ a.btn[aria-disabled="true"] {
|
|
|
3835
3974
|
* Public hooks:
|
|
3836
3975
|
* --close-size
|
|
3837
3976
|
* --close-icon-size
|
|
3977
|
+
* --close-bg
|
|
3978
|
+
* --close-color
|
|
3979
|
+
* --close-hover-color
|
|
3980
|
+
* --close-hover-overlay
|
|
3838
3981
|
*/
|
|
3839
3982
|
.close {
|
|
3983
|
+
/* Routes the hover/active wash, as --btn-hover-overlay does for .btn. A
|
|
3984
|
+
context that wants ink-only feedback (the dialog corner) sets it
|
|
3985
|
+
transparent. */
|
|
3986
|
+
--close-hover-overlay: var(--hover-overlay);
|
|
3987
|
+
|
|
3840
3988
|
display: inline-grid;
|
|
3841
3989
|
place-items: center;
|
|
3842
3990
|
flex: none;
|
|
@@ -3863,12 +4011,12 @@ a.btn[aria-disabled="true"] {
|
|
|
3863
4011
|
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
3864
4012
|
@media (hover: hover) {
|
|
3865
4013
|
.close:hover {
|
|
3866
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4014
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
3867
4015
|
}
|
|
3868
4016
|
}
|
|
3869
4017
|
|
|
3870
4018
|
.close:active {
|
|
3871
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4019
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
3872
4020
|
}
|
|
3873
4021
|
|
|
3874
4022
|
.close:focus-visible {
|
|
@@ -3880,23 +4028,25 @@ a.btn[aria-disabled="true"] {
|
|
|
3880
4028
|
surface rather than to the header or a scroll container inside it. Matched
|
|
3881
4029
|
on the documented positions (the dialog, its form, its header, or a form
|
|
3882
4030
|
inside that header) so a .close inside content — an alert in the body —
|
|
3883
|
-
keeps its own place. */
|
|
4031
|
+
keeps its own place. The corner drops the wash: only the ink changes. */
|
|
3884
4032
|
:where(dialog):is(.modal, .drawer) > .close,
|
|
3885
4033
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
3886
4034
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
4035
|
+
--close-hover-overlay: transparent;
|
|
4036
|
+
|
|
3887
4037
|
position: absolute;
|
|
3888
4038
|
z-index: 1;
|
|
3889
4039
|
inset-block-start: var(--space-20);
|
|
3890
4040
|
inset-inline-end: var(--space-20);
|
|
3891
|
-
background: var(--surface-subtle);
|
|
3892
|
-
color: var(--text-muted);
|
|
4041
|
+
background: var(--close-bg, var(--surface-subtle));
|
|
4042
|
+
color: var(--close-color, var(--text-muted));
|
|
3893
4043
|
}
|
|
3894
4044
|
|
|
3895
4045
|
@media (hover: hover) {
|
|
3896
4046
|
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
3897
4047
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
3898
4048
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
3899
|
-
color: var(--text);
|
|
4049
|
+
color: var(--close-hover-color, var(--text));
|
|
3900
4050
|
}
|
|
3901
4051
|
}
|
|
3902
4052
|
|
|
@@ -4581,6 +4731,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4581
4731
|
*
|
|
4582
4732
|
* Public hooks:
|
|
4583
4733
|
* --modal-size
|
|
4734
|
+
* --modal-pad
|
|
4735
|
+
* --modal-header-bg
|
|
4584
4736
|
* --dialog-viewport-gap
|
|
4585
4737
|
* --dialog-icon-size
|
|
4586
4738
|
* --dialog-icon-glyph-size
|
|
@@ -4588,6 +4740,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4588
4740
|
* The corner close control is .close (components/close.css).
|
|
4589
4741
|
*
|
|
4590
4742
|
* Internal:
|
|
4743
|
+
* --dialog-inner-radius
|
|
4591
4744
|
* --dialog-focus-bleed
|
|
4592
4745
|
* --dialog-viewport-block
|
|
4593
4746
|
* --dialog-viewport-gap-default
|
|
@@ -4606,6 +4759,11 @@ a.badge[aria-disabled="true"] {
|
|
|
4606
4759
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
4607
4760
|
);
|
|
4608
4761
|
|
|
4762
|
+
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
4763
|
+
as one name for the padded wrapper and any edge band (header,
|
|
4764
|
+
confirmation footer) so none of them drifts from the corner. */
|
|
4765
|
+
--dialog-inner-radius: var(--radius-lg);
|
|
4766
|
+
|
|
4609
4767
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
4610
4768
|
provides the containing block for the absolutely positioned close button. */
|
|
4611
4769
|
position: fixed;
|
|
@@ -4619,7 +4777,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4619
4777
|
dynamic token; only the modal pins the small viewport. */
|
|
4620
4778
|
--dialog-viewport-block: var(--viewport-block);
|
|
4621
4779
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4622
|
-
|
|
4780
|
+
/* Zero rather than absent: the UA stylesheet draws `border: solid` on
|
|
4781
|
+
dialog, and an authored edge reads as a hard seam against .bleed bands. */
|
|
4782
|
+
border: 0;
|
|
4623
4783
|
border-radius: var(--radius-lg);
|
|
4624
4784
|
background: var(--surface-raised);
|
|
4625
4785
|
color: var(--text);
|
|
@@ -4641,14 +4801,73 @@ a.badge[aria-disabled="true"] {
|
|
|
4641
4801
|
transform var(--duration) var(--ease-exit);
|
|
4642
4802
|
}
|
|
4643
4803
|
|
|
4804
|
+
@media (forced-colors: active) {
|
|
4805
|
+
/* With no authored border the panel edge disappears once colors collapse.
|
|
4806
|
+
The browser forces the border color, so only the width is authored. */
|
|
4807
|
+
:where(dialog).modal {
|
|
4808
|
+
border: var(--border-width) solid CanvasText;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4644
4812
|
:where(dialog).modal > form,
|
|
4645
4813
|
:where(dialog).modal > .stack {
|
|
4814
|
+
--surface-pad: var(--modal-pad, var(--space-40));
|
|
4815
|
+
|
|
4646
4816
|
display: grid;
|
|
4647
4817
|
gap: var(--space-40);
|
|
4648
4818
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4819
|
+
border-radius: var(--dialog-inner-radius);
|
|
4649
4820
|
overflow: auto;
|
|
4650
4821
|
overscroll-behavior: contain;
|
|
4651
|
-
padding: var(--
|
|
4822
|
+
padding: var(--surface-pad);
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
/* Optional dialog chrome lives beside the padded body wrapper. Its leading
|
|
4826
|
+
region and trailing controls share one center axis; wrapping the close in
|
|
4827
|
+
the trailing cluster keeps it in flow instead of invoking the absolute
|
|
4828
|
+
corner treatment from close.css. */
|
|
4829
|
+
:where(dialog).modal > header {
|
|
4830
|
+
display: flex;
|
|
4831
|
+
align-items: center;
|
|
4832
|
+
justify-content: space-between;
|
|
4833
|
+
gap: var(--space-30);
|
|
4834
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
4835
|
+
padding: var(--modal-pad, var(--space-40));
|
|
4836
|
+
border-start-start-radius: var(--dialog-inner-radius);
|
|
4837
|
+
border-start-end-radius: var(--dialog-inner-radius);
|
|
4838
|
+
background: var(--modal-header-bg, var(--surface-subtle));
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
:where(dialog).modal > header > .media {
|
|
4842
|
+
flex: 1 1 auto;
|
|
4843
|
+
min-inline-size: 0;
|
|
4844
|
+
}
|
|
4845
|
+
|
|
4846
|
+
:where(dialog).modal > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
4847
|
+
display: flex;
|
|
4848
|
+
align-items: center;
|
|
4849
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
4850
|
+
margin-block: 0;
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
:where(dialog).modal > header > .cluster {
|
|
4854
|
+
--cluster-wrap: nowrap;
|
|
4855
|
+
|
|
4856
|
+
flex: none;
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
@supports selector(dialog:has(> header)) {
|
|
4860
|
+
/* The header stays fixed while the body wrapper owns overflow. Without
|
|
4861
|
+
:has(), compact dialogs keep ordinary block flow as the fallback. */
|
|
4862
|
+
:where(dialog).modal[open]:has(> header) {
|
|
4863
|
+
display: grid;
|
|
4864
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
4868
|
+
min-block-size: 0;
|
|
4869
|
+
max-block-size: none;
|
|
4870
|
+
}
|
|
4652
4871
|
}
|
|
4653
4872
|
|
|
4654
4873
|
@supports (block-size: 100svh) {
|
|
@@ -4700,12 +4919,32 @@ a.badge[aria-disabled="true"] {
|
|
|
4700
4919
|
margin-block-end: 0;
|
|
4701
4920
|
}
|
|
4702
4921
|
|
|
4922
|
+
/* A media row inside the structural header is title anatomy, so its heading
|
|
4923
|
+
does not bring prose margins into the icon alignment. */
|
|
4924
|
+
:where(dialog).modal > :where(form, .stack) > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
4925
|
+
margin-block: 0;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4703
4928
|
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
4704
4929
|
content flow; headers reserve inline room so titles never run under it.
|
|
4705
4930
|
The form/.stack anatomy is structural, so it stays in :where() and never
|
|
4706
4931
|
lends .stack's specificity to a bare <form>. */
|
|
4707
4932
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
4708
|
-
padding-inline-end: calc(
|
|
4933
|
+
padding-inline-end: calc(
|
|
4934
|
+
var(--close-size, var(--control-size)) +
|
|
4935
|
+
var(--space-20) +
|
|
4936
|
+
var(--space-30)
|
|
4937
|
+
);
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
/* A corner .close that is a direct child of the chrome header needs the same
|
|
4941
|
+
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
4942
|
+
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
4943
|
+
:where(dialog).modal > header:has(> .close) {
|
|
4944
|
+
padding-inline-end: max(
|
|
4945
|
+
var(--modal-pad, var(--space-40)),
|
|
4946
|
+
calc(var(--close-size, var(--control-size)) + var(--space-20) + var(--space-30))
|
|
4947
|
+
);
|
|
4709
4948
|
}
|
|
4710
4949
|
|
|
4711
4950
|
:where(dialog).modal > :where(form, .stack) > footer {
|
|
@@ -4719,19 +4958,25 @@ a.badge[aria-disabled="true"] {
|
|
|
4719
4958
|
action band. The media primitive owns icon/content alignment; this variant
|
|
4720
4959
|
only removes the standard form inset so both regions can own their padding. */
|
|
4721
4960
|
:where(dialog).modal.dialog-confirmation > form {
|
|
4961
|
+
--surface-pad: 0px;
|
|
4962
|
+
|
|
4722
4963
|
gap: 0;
|
|
4723
4964
|
padding: 0;
|
|
4724
4965
|
}
|
|
4725
4966
|
|
|
4726
|
-
:where(dialog).modal.dialog-confirmation > form > .media {
|
|
4967
|
+
:where(dialog).modal.dialog-confirmation > form > :is(.media, header) {
|
|
4727
4968
|
gap: var(--space-40);
|
|
4728
4969
|
padding: var(--space-50);
|
|
4729
4970
|
}
|
|
4730
4971
|
|
|
4972
|
+
/* The action band splits a decision pair: the cancel-style action holds the
|
|
4973
|
+
leading edge and the committing action the trailing one, so the destructive
|
|
4974
|
+
choice is never adjacent to the dismiss. */
|
|
4731
4975
|
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
4976
|
+
justify-content: space-between;
|
|
4732
4977
|
padding: var(--space-40) var(--space-50);
|
|
4733
|
-
border-end-start-radius:
|
|
4734
|
-
border-end-end-radius:
|
|
4978
|
+
border-end-start-radius: var(--dialog-inner-radius);
|
|
4979
|
+
border-end-end-radius: var(--dialog-inner-radius);
|
|
4735
4980
|
background: var(--surface-subtle);
|
|
4736
4981
|
}
|
|
4737
4982
|
|
|
@@ -4768,8 +5013,29 @@ a.badge[aria-disabled="true"] {
|
|
|
4768
5013
|
line-height: 1;
|
|
4769
5014
|
}
|
|
4770
5015
|
|
|
5016
|
+
:where(dialog).modal.dialog-confirmation > form > .media > header {
|
|
5017
|
+
display: grid;
|
|
5018
|
+
gap: var(--space-20);
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
:where(dialog).modal.dialog-confirmation > form > .media > header > * {
|
|
5022
|
+
margin-block: 0;
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
/* The first heading occupies the icon row, so its text centers on the icon
|
|
5026
|
+
without a font-relative nudge. Following copy stays in the same column. */
|
|
5027
|
+
:where(dialog).modal.dialog-confirmation
|
|
5028
|
+
> form
|
|
5029
|
+
> .media
|
|
5030
|
+
> header
|
|
5031
|
+
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
5032
|
+
display: flex;
|
|
5033
|
+
align-items: center;
|
|
5034
|
+
min-block-size: var(--dialog-icon-size, 3rem);
|
|
5035
|
+
}
|
|
5036
|
+
|
|
4771
5037
|
@media (max-width: 30rem) {
|
|
4772
|
-
:where(dialog).modal.dialog-confirmation > form > .media,
|
|
5038
|
+
:where(dialog).modal.dialog-confirmation > form > :is(.media, header),
|
|
4773
5039
|
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
4774
5040
|
padding: var(--space-40);
|
|
4775
5041
|
}
|
|
@@ -5095,9 +5361,9 @@ html.has-modal-open.had-scrollbar {
|
|
|
5095
5361
|
*
|
|
5096
5362
|
* A surface that insets its direct children relays that inset to them as
|
|
5097
5363
|
* --surface-pad; a direct .bleed child cancels it and reaches the surface
|
|
5098
|
-
* edge. Card and drawer pad at the container level,
|
|
5099
|
-
*
|
|
5100
|
-
* owns it and the item does not.
|
|
5364
|
+
* edge. Card and drawer pad at the container level, while modal pads its inner
|
|
5365
|
+
* wrapper; each owns the token where that inset lives. The accordion pads its
|
|
5366
|
+
* summary and panel instead, so the panel owns it and the item does not.
|
|
5101
5367
|
*
|
|
5102
5368
|
* Why the token is handed to the children rather than declared on the surface:
|
|
5103
5369
|
* custom properties inherit, so a surface-level declaration would also reach a
|
|
@@ -5118,11 +5384,11 @@ html.has-modal-open.had-scrollbar {
|
|
|
5118
5384
|
* Framework plumbing:
|
|
5119
5385
|
* --surface-pad (declared by the surface)
|
|
5120
5386
|
*/
|
|
5121
|
-
:where(.card, .drawer, .accordion details > summary + *) > * * {
|
|
5387
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * * {
|
|
5122
5388
|
--bleed-pad: initial;
|
|
5123
5389
|
}
|
|
5124
5390
|
|
|
5125
|
-
:where(.card, .drawer, .accordion details > summary + *) > * {
|
|
5391
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * {
|
|
5126
5392
|
--bleed-pad: var(--surface-pad);
|
|
5127
5393
|
}
|
|
5128
5394
|
|
|
@@ -5186,6 +5452,7 @@ picture.bleed > img {
|
|
|
5186
5452
|
* Public hooks:
|
|
5187
5453
|
* --flyout-inline-size
|
|
5188
5454
|
* --flyout-max-inline-size
|
|
5455
|
+
* --flyout-pad
|
|
5189
5456
|
*
|
|
5190
5457
|
* Internal:
|
|
5191
5458
|
* --menu-item-size (derived from --control-size for menu rows)
|
|
@@ -5238,7 +5505,9 @@ picture.bleed > img {
|
|
|
5238
5505
|
);
|
|
5239
5506
|
overflow: auto;
|
|
5240
5507
|
margin: 0;
|
|
5241
|
-
padding
|
|
5508
|
+
/* Tight by default for action menus, whose items carry their own padding.
|
|
5509
|
+
Rich panels (forms, pickers) opt into dialog air per instance. */
|
|
5510
|
+
padding: var(--flyout-pad, var(--space-10));
|
|
5242
5511
|
border: var(--border-width) solid var(--border);
|
|
5243
5512
|
border-radius: var(--radius);
|
|
5244
5513
|
background: var(--surface-raised);
|
|
@@ -5511,7 +5780,7 @@ picture.bleed > img {
|
|
|
5511
5780
|
*/
|
|
5512
5781
|
.tooltip {
|
|
5513
5782
|
--tooltip-bg: var(--surface-solid);
|
|
5514
|
-
--tooltip-fg: var(--surface);
|
|
5783
|
+
--tooltip-fg: var(--surface-solid-fg);
|
|
5515
5784
|
--tooltip-arrow-size: 5px;
|
|
5516
5785
|
|
|
5517
5786
|
position: fixed;
|
|
@@ -6157,8 +6426,8 @@ picture.bleed > img {
|
|
|
6157
6426
|
}
|
|
6158
6427
|
|
|
6159
6428
|
.status-bar {
|
|
6160
|
-
--status-bg: var(--intent, var(--
|
|
6161
|
-
--status-fg: var(--intent-fg, var(--surface));
|
|
6429
|
+
--status-bg: var(--intent, var(--surface-solid));
|
|
6430
|
+
--status-fg: var(--intent-fg, var(--surface-solid-fg));
|
|
6162
6431
|
|
|
6163
6432
|
position: fixed;
|
|
6164
6433
|
/* Fixed positioning tracks the layout viewport, which the mobile software
|
|
@@ -6428,7 +6697,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6428
6697
|
}
|
|
6429
6698
|
|
|
6430
6699
|
.avatar-stack > .avatar {
|
|
6431
|
-
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface);
|
|
6700
|
+
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
6432
6701
|
}
|
|
6433
6702
|
|
|
6434
6703
|
.avatar-stack > .avatar + .avatar {
|
|
@@ -6854,7 +7123,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6854
7123
|
}
|
|
6855
7124
|
|
|
6856
7125
|
.rating > input[type="radio"]:focus-visible {
|
|
6857
|
-
outline: var(--focus-
|
|
7126
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
6858
7127
|
outline-offset: var(--focus-outline-offset);
|
|
6859
7128
|
background: var(--rating-color);
|
|
6860
7129
|
}
|
|
@@ -6960,7 +7229,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6960
7229
|
--step-size: 1.75rem;
|
|
6961
7230
|
--step-inline-gap: var(--space-20);
|
|
6962
7231
|
--step-line-size: var(--border-width);
|
|
6963
|
-
--step-marker-bg: var(--surface);
|
|
7232
|
+
--step-marker-bg: var(--surface-opaque);
|
|
6964
7233
|
--step-marker-border: var(--border);
|
|
6965
7234
|
--step-marker-fg: var(--text-muted);
|
|
6966
7235
|
--step-marker-radius: var(--radius-full);
|
|
@@ -7040,7 +7309,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7040
7309
|
connector leaving current goes back to neutral. Declared after
|
|
7041
7310
|
.step-complete at equal specificity: current wins on every channel. */
|
|
7042
7311
|
.steps > [aria-current="step"] {
|
|
7043
|
-
--step-marker-bg: var(--surface);
|
|
7312
|
+
--step-marker-bg: var(--surface-opaque);
|
|
7044
7313
|
--step-marker-border: var(--state-selected);
|
|
7045
7314
|
--step-marker-fg: var(--state-selected);
|
|
7046
7315
|
--step-line: var(--border);
|
|
@@ -7079,7 +7348,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7079
7348
|
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
7080
7349
|
link has a ring to protect. */
|
|
7081
7350
|
.steps-horizontal:has(a[href]) {
|
|
7082
|
-
padding-block: calc(var(--focus-outline-offset) + var(--
|
|
7351
|
+
padding-block: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
7083
7352
|
}
|
|
7084
7353
|
|
|
7085
7354
|
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
@@ -7563,8 +7832,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
7563
7832
|
*
|
|
7564
7833
|
* Docs: docs/pages/components/pagination.md
|
|
7565
7834
|
*
|
|
7566
|
-
* Render .btn elements inside a .pagination list. The active page is
|
|
7567
|
-
* with aria-current="page"; .btn owns the active state styling
|
|
7835
|
+
* Render .btn.ghost elements inside a .pagination list. The active page is
|
|
7836
|
+
* marked with aria-current="page"; .btn owns the active state styling, so the
|
|
7837
|
+
* current page is the only filled item.
|
|
7568
7838
|
*
|
|
7569
7839
|
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
7570
7840
|
* — they are decorative, not actionable. They still get the same box sizing
|
|
@@ -7682,10 +7952,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
7682
7952
|
}
|
|
7683
7953
|
|
|
7684
7954
|
/* The ring is square on the edge the tab is attached to, so it reads as part
|
|
7685
|
-
of the strip rather than a floating chip.
|
|
7955
|
+
of the strip rather than a floating chip. Color and width follow the
|
|
7956
|
+
component ring contract (focus.css); only the geometry is tab-specific. */
|
|
7686
7957
|
.tab:focus-visible {
|
|
7687
|
-
outline: var(--focus-
|
|
7688
|
-
outline-color: var(--focus);
|
|
7958
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
7689
7959
|
outline-offset: -2px;
|
|
7690
7960
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
7691
7961
|
}
|
|
@@ -7938,6 +8208,12 @@ a.list-item[aria-current]::before {
|
|
|
7938
8208
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
7939
8209
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
7940
8210
|
*
|
|
8211
|
+
* Responsive strategy is horizontal overflow: the wrap scrolls, the table
|
|
8212
|
+
* keeps its columns. Restructuring rows into cards would destroy the
|
|
8213
|
+
* column scanability tables exist for, and needs data-label duplication
|
|
8214
|
+
* because CSS cannot join a cell to its header — that representation choice
|
|
8215
|
+
* belongs to the application, not the framework.
|
|
8216
|
+
*
|
|
7941
8217
|
* Public hooks:
|
|
7942
8218
|
* --table-min
|
|
7943
8219
|
* --table-cell-pad
|
|
@@ -8026,11 +8302,14 @@ a.list-item[aria-current]::before {
|
|
|
8026
8302
|
* .sm/.lg are deliberately not supported.
|
|
8027
8303
|
*
|
|
8028
8304
|
* Public hooks:
|
|
8305
|
+
* --meter-track
|
|
8029
8306
|
* --meter-value
|
|
8030
8307
|
* --bar-height
|
|
8031
8308
|
*/
|
|
8032
8309
|
|
|
8033
8310
|
.meter {
|
|
8311
|
+
--meter-track: var(--surface-subtle);
|
|
8312
|
+
|
|
8034
8313
|
appearance: none;
|
|
8035
8314
|
-webkit-appearance: none;
|
|
8036
8315
|
display: block;
|
|
@@ -8039,7 +8318,7 @@ a.list-item[aria-current]::before {
|
|
|
8039
8318
|
overflow: hidden;
|
|
8040
8319
|
border: 0;
|
|
8041
8320
|
border-radius: var(--radius-full);
|
|
8042
|
-
background: var(--
|
|
8321
|
+
background: var(--meter-track);
|
|
8043
8322
|
}
|
|
8044
8323
|
|
|
8045
8324
|
/* A non-native meter is a weighted flex container. The element type keeps the
|
|
@@ -8058,7 +8337,7 @@ a.list-item[aria-current]::before {
|
|
|
8058
8337
|
}
|
|
8059
8338
|
|
|
8060
8339
|
.meter:not(meter) > * + * {
|
|
8061
|
-
border-inline-start: var(--border-width) solid var(--surface);
|
|
8340
|
+
border-inline-start: var(--border-width) solid var(--surface-opaque);
|
|
8062
8341
|
}
|
|
8063
8342
|
|
|
8064
8343
|
.meter::-webkit-meter-bar {
|
|
@@ -8066,7 +8345,7 @@ a.list-item[aria-current]::before {
|
|
|
8066
8345
|
block-size: var(--bar-height);
|
|
8067
8346
|
border: 0;
|
|
8068
8347
|
border-radius: var(--radius-full);
|
|
8069
|
-
background: var(--
|
|
8348
|
+
background: var(--meter-track);
|
|
8070
8349
|
}
|
|
8071
8350
|
|
|
8072
8351
|
.meter::-webkit-meter-optimum-value {
|
|
@@ -8295,36 +8574,55 @@ a.list-item[aria-current]::before {
|
|
|
8295
8574
|
|
|
8296
8575
|
.aura {
|
|
8297
8576
|
--aura-color: var(--intent, var(--primary));
|
|
8298
|
-
--aura-width:
|
|
8577
|
+
--aura-width: 1px;
|
|
8299
8578
|
--aura-blur: 0.25rem;
|
|
8300
|
-
--aura-intensity:
|
|
8579
|
+
--aura-intensity: 0.4;
|
|
8301
8580
|
--aura-radius: var(--radius-lg);
|
|
8302
8581
|
--aura-duration: 6s;
|
|
8303
8582
|
|
|
8304
8583
|
position: relative;
|
|
8305
8584
|
isolation: isolate;
|
|
8306
8585
|
display: inline-grid;
|
|
8307
|
-
padding: var(--aura-width);
|
|
8308
8586
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
8309
|
-
|
|
8587
|
+
|
|
8588
|
+
/* The frame is the border itself, so --aura-width is the painted thickness.
|
|
8589
|
+
A transparent border stays for forced colors, where it becomes the visible
|
|
8590
|
+
boundary; a padding frame inside it would paint both, doubling the line. */
|
|
8591
|
+
border: var(--aura-width) solid transparent;
|
|
8310
8592
|
background: var(--aura-color);
|
|
8311
8593
|
|
|
8312
8594
|
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
8313
8595
|
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
8314
|
-
across
|
|
8596
|
+
across the frame draws an outline, the opposite of the effect. `border-box`
|
|
8597
|
+
sizes the gradient to the border box: the default padding-box origin would
|
|
8598
|
+
tile a neighbouring copy of the gradient into the border, which is now the
|
|
8599
|
+
whole frame. */
|
|
8315
8600
|
background: conic-gradient(
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
/*
|
|
8601
|
+
from var(--aura-angle),
|
|
8602
|
+
transparent 0 78%,
|
|
8603
|
+
var(--aura-color) 93%,
|
|
8604
|
+
transparent
|
|
8605
|
+
)
|
|
8606
|
+
border-box;
|
|
8607
|
+
|
|
8608
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
8609
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
8610
|
+
layer. This is the layer that stays lit at all times, so it — not the
|
|
8611
|
+
frame — is what keeps the object present between two passes of the
|
|
8612
|
+
accent. An outer shadow never paints under the box, so it stays out of a
|
|
8613
|
+
translucent child. min() because a color-mix() share above 100% is
|
|
8614
|
+
invalid, where the opacity it replaces clamped. */
|
|
8615
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 8) var(--aura-blur)
|
|
8616
|
+
color-mix(in oklab, var(--aura-color) min(100%, calc(15% * var(--aura-intensity))), transparent);
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8619
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. An
|
|
8620
|
+
absolute inset starts at the padding edge, inside the border that is the
|
|
8621
|
+
frame, so the negative inset puts the halo back on the frame. */
|
|
8324
8622
|
.aura::before {
|
|
8325
8623
|
position: absolute;
|
|
8326
8624
|
z-index: -1;
|
|
8327
|
-
inset:
|
|
8625
|
+
inset: calc(-1 * var(--aura-width));
|
|
8328
8626
|
border-radius: inherit;
|
|
8329
8627
|
background: inherit;
|
|
8330
8628
|
filter: blur(var(--aura-blur));
|
|
@@ -8333,29 +8631,22 @@ a.list-item[aria-current]::before {
|
|
|
8333
8631
|
content: "";
|
|
8334
8632
|
}
|
|
8335
8633
|
|
|
8336
|
-
/*
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
passes of the accent. */
|
|
8634
|
+
/* Occlusion — the frame and the near halo paint across the whole box, and only
|
|
8635
|
+
their edge is meant to show. This mask sits above both, under the child, so
|
|
8636
|
+
a transparent or translucent child (an .outline alert, a glass surface) is
|
|
8637
|
+
framed rather than filled with the frame color. It follows the child's
|
|
8638
|
+
shape through --aura-radius. */
|
|
8342
8639
|
.aura::after {
|
|
8343
8640
|
position: absolute;
|
|
8344
8641
|
z-index: -1;
|
|
8345
|
-
inset:
|
|
8346
|
-
border-radius:
|
|
8347
|
-
background: var(--
|
|
8348
|
-
filter: blur(calc(var(--aura-blur) * 4));
|
|
8349
|
-
opacity: calc(0.15 * var(--aura-intensity));
|
|
8642
|
+
inset: 0;
|
|
8643
|
+
border-radius: var(--aura-radius);
|
|
8644
|
+
background: var(--surface-opaque);
|
|
8350
8645
|
pointer-events: none;
|
|
8351
8646
|
content: "";
|
|
8352
8647
|
}
|
|
8353
8648
|
|
|
8354
|
-
/* Lifts the child above
|
|
8355
|
-
across the whole box and only its edge is meant to show, so a transparent
|
|
8356
|
-
child (an .outline alert, a .ghost button) is filled with the frame color
|
|
8357
|
-
rather than framed by it. Documented as a constraint, not worked around —
|
|
8358
|
-
masking a ring would cost more than the case is worth. */
|
|
8649
|
+
/* Lifts the child above the halo and the mask. */
|
|
8359
8650
|
.aura > * {
|
|
8360
8651
|
position: relative;
|
|
8361
8652
|
}
|
|
@@ -8378,7 +8669,7 @@ a.list-item[aria-current]::before {
|
|
|
8378
8669
|
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
8379
8670
|
}
|
|
8380
8671
|
|
|
8381
|
-
.aura-glow
|
|
8672
|
+
.aura-glow {
|
|
8382
8673
|
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
8383
8674
|
}
|
|
8384
8675
|
}
|
|
@@ -8390,8 +8681,8 @@ a.list-item[aria-current]::before {
|
|
|
8390
8681
|
@media (prefers-reduced-motion: reduce) {
|
|
8391
8682
|
.aura:not(.aura-glow) {
|
|
8392
8683
|
background:
|
|
8393
|
-
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%),
|
|
8394
|
-
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%);
|
|
8684
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%) border-box,
|
|
8685
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%) border-box;
|
|
8395
8686
|
}
|
|
8396
8687
|
}
|
|
8397
8688
|
|
|
@@ -8412,10 +8703,16 @@ a.list-item[aria-current]::before {
|
|
|
8412
8703
|
}
|
|
8413
8704
|
}
|
|
8414
8705
|
|
|
8706
|
+
/* The far halo is the element's own shadow: opening its spread and share
|
|
8707
|
+
breathes it without scaling the child. */
|
|
8415
8708
|
@keyframes aura-breathe-far {
|
|
8416
8709
|
50% {
|
|
8417
|
-
|
|
8418
|
-
|
|
8710
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 10) calc(var(--aura-blur) * 2)
|
|
8711
|
+
color-mix(
|
|
8712
|
+
in oklab,
|
|
8713
|
+
var(--aura-color) min(100%, calc(28% * var(--aura-intensity))),
|
|
8714
|
+
transparent
|
|
8715
|
+
);
|
|
8419
8716
|
}
|
|
8420
8717
|
}
|
|
8421
8718
|
|
|
@@ -8435,6 +8732,64 @@ a.list-item[aria-current]::before {
|
|
|
8435
8732
|
--aura-radius: var(--radius-full);
|
|
8436
8733
|
}
|
|
8437
8734
|
|
|
8735
|
+
/*
|
|
8736
|
+
* Highlight sweeping across a text label — an activity in progress.
|
|
8737
|
+
*
|
|
8738
|
+
* Docs: docs/pages/components/shimmer.md
|
|
8739
|
+
*
|
|
8740
|
+
* Presentation only: it carries no state and selects no ARIA attribute, so the
|
|
8741
|
+
* label stays whatever the author makes it (a role="status" message, a list
|
|
8742
|
+
* item, a heading).
|
|
8743
|
+
*
|
|
8744
|
+
* Public hooks:
|
|
8745
|
+
* --shimmer-color
|
|
8746
|
+
* --shimmer-highlight
|
|
8747
|
+
*/
|
|
8748
|
+
.shimmer {
|
|
8749
|
+
--shimmer-color: var(--text-muted);
|
|
8750
|
+
--shimmer-highlight: var(--text);
|
|
8751
|
+
|
|
8752
|
+
color: var(--shimmer-color);
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8755
|
+
/* The sweep paints the glyphs through a clipped background, which needs the
|
|
8756
|
+
glyph fill transparent. Without background-clip: text that would erase the
|
|
8757
|
+
label, so the transparency lives under the same query as the clip. It is
|
|
8758
|
+
the fill, not `color`: a label class that sets its own color (.muted, a
|
|
8759
|
+
caption style) would otherwise win the cascade and paint opaque glyphs over
|
|
8760
|
+
the sweep. Reduced motion keeps the plain label — a frozen band would read
|
|
8761
|
+
as a random tint. */
|
|
8762
|
+
@supports (background-clip: text) {
|
|
8763
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
8764
|
+
.shimmer {
|
|
8765
|
+
-webkit-text-fill-color: transparent;
|
|
8766
|
+
background:
|
|
8767
|
+
linear-gradient(
|
|
8768
|
+
90deg,
|
|
8769
|
+
var(--shimmer-color) 40%,
|
|
8770
|
+
var(--shimmer-highlight) 50%,
|
|
8771
|
+
var(--shimmer-color) 60%
|
|
8772
|
+
)
|
|
8773
|
+
0 0 / 300% 100%;
|
|
8774
|
+
background-clip: text;
|
|
8775
|
+
animation: actual-shimmer var(--duration-shimmer) linear infinite;
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
|
|
8780
|
+
/* The band occupies the middle third of a background three times the label's
|
|
8781
|
+
width, so each pass leaves the end of the label before the next one enters
|
|
8782
|
+
at the start. */
|
|
8783
|
+
@keyframes actual-shimmer {
|
|
8784
|
+
from {
|
|
8785
|
+
background-position: 100% 0;
|
|
8786
|
+
}
|
|
8787
|
+
|
|
8788
|
+
to {
|
|
8789
|
+
background-position: 0% 0;
|
|
8790
|
+
}
|
|
8791
|
+
}
|
|
8792
|
+
|
|
8438
8793
|
/* Utilities — small, single-purpose escape hatches. */
|
|
8439
8794
|
/*
|
|
8440
8795
|
* Small, single-purpose utilities for common escape hatches.
|