actual-css 0.10.0 → 0.11.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 +73 -1
- package/README.md +2 -2
- package/components.json +2 -1
- package/dist/actual.full.css +314 -106
- 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 +2 -0
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +10 -0
- package/docs/pages/components/button.md +1 -1
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/dialog.md +5 -2
- 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/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/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 +5 -1
- package/reserved-classes.json +1 -0
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/flyout.css +4 -1
- package/src/css/components/meter.css +1 -1
- 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/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/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),
|
|
@@ -2889,6 +2912,10 @@ small {
|
|
|
2889
2912
|
* --range-track-height
|
|
2890
2913
|
* --range-thumb-bg
|
|
2891
2914
|
* --range-track-bg
|
|
2915
|
+
* --range-fill-bg
|
|
2916
|
+
*
|
|
2917
|
+
* Runtime-written (by actual-css/js/range, never set by hand):
|
|
2918
|
+
* --range-progress
|
|
2892
2919
|
*/
|
|
2893
2920
|
|
|
2894
2921
|
/* Intent boundary — inherited intents do not tint this component.
|
|
@@ -2898,10 +2925,17 @@ small {
|
|
|
2898
2925
|
}
|
|
2899
2926
|
|
|
2900
2927
|
.range {
|
|
2901
|
-
--range-thumb-size: 1.
|
|
2902
|
-
--range-track-height: 0.
|
|
2928
|
+
--range-thumb-size: 1.125rem;
|
|
2929
|
+
--range-track-height: 0.625rem;
|
|
2903
2930
|
--range-thumb-bg: var(--intent, var(--primary));
|
|
2904
2931
|
--range-track-bg: var(--border);
|
|
2932
|
+
--range-fill-bg: var(--intent, var(--primary));
|
|
2933
|
+
/* Fill share, written by actual-css/js/range. 0% with no runtime means a
|
|
2934
|
+
plain track — the control stays fully usable without JavaScript. */
|
|
2935
|
+
--range-progress: 0%;
|
|
2936
|
+
/* Logical fill direction: the gradient below cannot use flow-relative
|
|
2937
|
+
keywords, so RTL flips the angle through this hook instead. */
|
|
2938
|
+
--range-fill-from: to right;
|
|
2905
2939
|
|
|
2906
2940
|
appearance: none;
|
|
2907
2941
|
-webkit-appearance: none;
|
|
@@ -2911,16 +2945,29 @@ small {
|
|
|
2911
2945
|
cursor: pointer;
|
|
2912
2946
|
}
|
|
2913
2947
|
|
|
2948
|
+
:where([dir="rtl"]) .range,
|
|
2949
|
+
.range:dir(rtl) {
|
|
2950
|
+
--range-fill-from: to left;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2914
2953
|
.range::-webkit-slider-runnable-track {
|
|
2915
2954
|
block-size: var(--range-track-height);
|
|
2916
2955
|
border-radius: var(--radius-full);
|
|
2917
|
-
background:
|
|
2956
|
+
background: linear-gradient(
|
|
2957
|
+
var(--range-fill-from),
|
|
2958
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2959
|
+
var(--range-track-bg) var(--range-progress)
|
|
2960
|
+
);
|
|
2918
2961
|
}
|
|
2919
2962
|
|
|
2920
2963
|
.range::-moz-range-track {
|
|
2921
2964
|
block-size: var(--range-track-height);
|
|
2922
2965
|
border-radius: var(--radius-full);
|
|
2923
|
-
background:
|
|
2966
|
+
background: linear-gradient(
|
|
2967
|
+
var(--range-fill-from),
|
|
2968
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2969
|
+
var(--range-track-bg) var(--range-progress)
|
|
2970
|
+
);
|
|
2924
2971
|
}
|
|
2925
2972
|
|
|
2926
2973
|
.range::-webkit-slider-thumb {
|
|
@@ -2933,6 +2980,10 @@ small {
|
|
|
2933
2980
|
background: var(--range-thumb-bg);
|
|
2934
2981
|
margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
|
|
2935
2982
|
cursor: inherit;
|
|
2983
|
+
/* Permanent surface separator: the fill defaults to the thumb color, so
|
|
2984
|
+
without this the thumb would melt into the filled part of the track.
|
|
2985
|
+
Focus-visible keeps this ring and adds the --focus ring outside it. */
|
|
2986
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
2936
2987
|
transition: box-shadow var(--duration);
|
|
2937
2988
|
}
|
|
2938
2989
|
|
|
@@ -2943,6 +2994,7 @@ small {
|
|
|
2943
2994
|
border: 0;
|
|
2944
2995
|
background: var(--range-thumb-bg);
|
|
2945
2996
|
cursor: inherit;
|
|
2997
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
2946
2998
|
transition: box-shadow var(--duration);
|
|
2947
2999
|
}
|
|
2948
3000
|
|
|
@@ -2953,20 +3005,21 @@ small {
|
|
|
2953
3005
|
/*
|
|
2954
3006
|
* A thumb pseudo-element takes no reliable outline, so its ring is a solid
|
|
2955
3007
|
* --focus shadow (the transparent outline above covers forced colors). The
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
* read as an unwanted void.
|
|
3008
|
+
* surface-coloured separator keeps the ring visually distinct from the
|
|
3009
|
+
* coloured thumb — unlike a full-size input where the same gap would
|
|
3010
|
+
* read as an unwanted void. It doubles as the resting separator between
|
|
3011
|
+
* the thumb and the same-coloured fill.
|
|
2959
3012
|
*/
|
|
2960
3013
|
.range:focus-visible::-webkit-slider-thumb {
|
|
2961
3014
|
box-shadow:
|
|
2962
|
-
0 0 0
|
|
2963
|
-
0 0 0 calc(
|
|
3015
|
+
0 0 0 2px var(--surface-opaque),
|
|
3016
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
2964
3017
|
}
|
|
2965
3018
|
|
|
2966
3019
|
.range:focus-visible::-moz-range-thumb {
|
|
2967
3020
|
box-shadow:
|
|
2968
|
-
0 0 0
|
|
2969
|
-
0 0 0 calc(
|
|
3021
|
+
0 0 0 2px var(--surface-opaque),
|
|
3022
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
2970
3023
|
}
|
|
2971
3024
|
|
|
2972
3025
|
.range:disabled {
|
|
@@ -2974,28 +3027,41 @@ small {
|
|
|
2974
3027
|
cursor: not-allowed;
|
|
2975
3028
|
opacity: var(--disabled-opacity);
|
|
2976
3029
|
}
|
|
2977
|
-
|
|
2978
3030
|
/* Forced colors: the track must stay a visible line against the page (Canvas
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3031
|
+
would merge with it) and the thumb needs a real boundary to remain
|
|
3032
|
+
distinguishable from the track. The fill keeps its two-tone split with
|
|
3033
|
+
system colors so the value stays readable. Disabled keeps GrayText via
|
|
3034
|
+
:disabled. Focus stays on the transparent outline of :focus-visible. Track
|
|
3035
|
+
and thumb fills are written on the properties, not only the hooks: an
|
|
3036
|
+
application hook override at the same specificity loads after this file
|
|
3037
|
+
and would win the variable, leaving a Canvas-remapped fill.
|
|
3038
|
+
The split lives in a background-image, which forced-color-adjust: auto
|
|
3039
|
+
drops — so the tracks opt out locally. The colors stay system keywords,
|
|
3040
|
+
keeping native contrast adaptation; only the host keeps adjusting. */
|
|
2985
3041
|
@media (forced-colors: active) {
|
|
2986
3042
|
.range {
|
|
2987
3043
|
--range-track-bg: CanvasText;
|
|
2988
3044
|
--range-thumb-bg: ButtonFace;
|
|
3045
|
+
--range-fill-bg: Highlight;
|
|
2989
3046
|
}
|
|
2990
3047
|
|
|
2991
3048
|
.range::-webkit-slider-runnable-track {
|
|
2992
|
-
|
|
3049
|
+
forced-color-adjust: none;
|
|
3050
|
+
background: linear-gradient(
|
|
3051
|
+
var(--range-fill-from),
|
|
3052
|
+
Highlight var(--range-progress),
|
|
3053
|
+
CanvasText var(--range-progress)
|
|
3054
|
+
);
|
|
2993
3055
|
}
|
|
2994
3056
|
|
|
2995
3057
|
.range::-moz-range-track {
|
|
2996
|
-
|
|
3058
|
+
forced-color-adjust: none;
|
|
3059
|
+
background: linear-gradient(
|
|
3060
|
+
var(--range-fill-from),
|
|
3061
|
+
Highlight var(--range-progress),
|
|
3062
|
+
CanvasText var(--range-progress)
|
|
3063
|
+
);
|
|
2997
3064
|
}
|
|
2998
|
-
|
|
2999
3065
|
.range::-webkit-slider-thumb {
|
|
3000
3066
|
background: ButtonFace;
|
|
3001
3067
|
border: var(--border-width) solid ButtonText;
|
|
@@ -3014,13 +3080,15 @@ small {
|
|
|
3014
3080
|
*
|
|
3015
3081
|
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
3016
3082
|
* :has() is supported the input is visually hidden and the card mirrors its
|
|
3017
|
-
* state:
|
|
3018
|
-
*
|
|
3019
|
-
* native input stays visible
|
|
3083
|
+
* state: an indicator on the leading edge (a ring for radio, a box for
|
|
3084
|
+
* checkbox) that fills with a check, checked border and tint, focus ring,
|
|
3085
|
+
* disabled dimming. Without :has() the native input stays visible in the
|
|
3086
|
+
* indicator's slot, so selection state never disappears.
|
|
3020
3087
|
*
|
|
3021
|
-
* The
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3088
|
+
* The card is a two-column grid: the indicator in the first column, level
|
|
3089
|
+
* with the first row, and each child element of the label on its own row in
|
|
3090
|
+
* the second. Wrap loose text in an element. A corner badge for tiles is a
|
|
3091
|
+
* documented recipe on these same layers, not a variant.
|
|
3024
3092
|
*
|
|
3025
3093
|
* .chip is the compact pill form (filter chips, selectable tags): control
|
|
3026
3094
|
* height, and a leading dot that turns into the check in a fixed slot, so
|
|
@@ -3028,6 +3096,7 @@ small {
|
|
|
3028
3096
|
*
|
|
3029
3097
|
* Public hooks:
|
|
3030
3098
|
* --choice-card-pad
|
|
3099
|
+
* --choice-card-gap
|
|
3031
3100
|
* --choice-card-radius
|
|
3032
3101
|
* --choice-card-border
|
|
3033
3102
|
* --choice-card-bg
|
|
@@ -3042,19 +3111,25 @@ small {
|
|
|
3042
3111
|
|
|
3043
3112
|
.choice-card {
|
|
3044
3113
|
--choice-card-pad: var(--density-space, var(--space-40));
|
|
3114
|
+
--choice-card-gap: var(--space-30);
|
|
3045
3115
|
--choice-card-radius: var(--radius);
|
|
3046
3116
|
--choice-card-border: var(--border);
|
|
3047
3117
|
--choice-card-bg: var(--surface);
|
|
3048
|
-
--choice-card-check-size: 1.
|
|
3118
|
+
--choice-card-check-size: 1.25em;
|
|
3049
3119
|
|
|
3050
3120
|
position: relative;
|
|
3051
|
-
display:
|
|
3121
|
+
display: grid;
|
|
3122
|
+
grid-template-columns: var(--choice-card-check-size) minmax(0, 1fr);
|
|
3123
|
+
/* why: a card stretched by a taller grid neighbour keeps its rows at the
|
|
3124
|
+
top instead of spreading them. */
|
|
3125
|
+
align-content: start;
|
|
3126
|
+
column-gap: var(--choice-card-gap);
|
|
3052
3127
|
padding: var(--choice-card-pad);
|
|
3053
3128
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
3054
3129
|
border-radius: var(--choice-card-radius);
|
|
3055
3130
|
background: var(--choice-card-bg);
|
|
3056
3131
|
color: var(--text);
|
|
3057
|
-
/* Consumed so .sm/.lg reach the card; the
|
|
3132
|
+
/* Consumed so .sm/.lg reach the card; the indicator's em sizing follows. */
|
|
3058
3133
|
font-size: var(--control-font-size);
|
|
3059
3134
|
cursor: pointer;
|
|
3060
3135
|
transition:
|
|
@@ -3063,6 +3138,18 @@ small {
|
|
|
3063
3138
|
box-shadow var(--duration);
|
|
3064
3139
|
}
|
|
3065
3140
|
|
|
3141
|
+
.choice-card > * {
|
|
3142
|
+
grid-column: 2;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
/* The input owns the indicator's cell: visible there without :has(), hidden
|
|
3146
|
+
in it with :has(). */
|
|
3147
|
+
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
3148
|
+
grid-area: 1 / 1;
|
|
3149
|
+
place-self: center;
|
|
3150
|
+
margin: 0;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3066
3153
|
/* The native input is visually hidden but stays focusable and participates in
|
|
3067
3154
|
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
3068
3155
|
:has() the card cannot mirror the input's state, so the visible native
|
|
@@ -3108,24 +3195,31 @@ small {
|
|
|
3108
3195
|
}
|
|
3109
3196
|
}
|
|
3110
3197
|
|
|
3111
|
-
/*
|
|
3112
|
-
the
|
|
3198
|
+
/* Indicator: ::before is the ring or box, ::after the check. Both share the
|
|
3199
|
+
input's cell, centred on the first line — not the first row, which a long
|
|
3200
|
+
label wraps — and stay hidden until :has() can mirror the input. The em
|
|
3201
|
+
margin is the lh-less fallback at the default 1.5 line-height. */
|
|
3113
3202
|
.choice-card::before,
|
|
3114
3203
|
.choice-card::after {
|
|
3115
3204
|
content: "";
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3205
|
+
grid-area: 1 / 1;
|
|
3206
|
+
align-self: start;
|
|
3207
|
+
margin-block-start: calc((1.5em - var(--choice-card-check-size)) / 2);
|
|
3208
|
+
margin-block-start: calc((1lh - var(--choice-card-check-size)) / 2);
|
|
3119
3209
|
inline-size: var(--choice-card-check-size);
|
|
3120
3210
|
block-size: var(--choice-card-check-size);
|
|
3121
3211
|
transform: scale(0);
|
|
3122
|
-
transition:
|
|
3212
|
+
transition:
|
|
3213
|
+
transform var(--duration),
|
|
3214
|
+
background-color var(--duration),
|
|
3215
|
+
border-color var(--duration);
|
|
3123
3216
|
}
|
|
3124
3217
|
|
|
3125
|
-
/*
|
|
3218
|
+
/* A border, not an inset shadow: forced colors drop shadows, and the resting
|
|
3219
|
+
ring is the only sign of an unchecked option. The invalid border reaches it
|
|
3220
|
+
through the same hook as the card's. */
|
|
3126
3221
|
.choice-card::before {
|
|
3127
|
-
|
|
3128
|
-
box-shadow: 0 0 0 2px var(--surface);
|
|
3222
|
+
border: max(var(--border-width), 1.5px) solid var(--form-invalid-border, var(--text-muted));
|
|
3129
3223
|
z-index: 1;
|
|
3130
3224
|
}
|
|
3131
3225
|
|
|
@@ -3147,7 +3241,19 @@ small {
|
|
|
3147
3241
|
z-index: 2;
|
|
3148
3242
|
}
|
|
3149
3243
|
|
|
3150
|
-
|
|
3244
|
+
/* Guarded with the input's hiding above: a resting ring beside a visible
|
|
3245
|
+
native input would draw the indicator twice. */
|
|
3246
|
+
@supports selector(:has(*)) {
|
|
3247
|
+
.choice-card::before {
|
|
3248
|
+
transform: none;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
.choice-card:has(:checked)::before {
|
|
3253
|
+
border-color: var(--intent, var(--state-selected));
|
|
3254
|
+
background: var(--intent, var(--state-selected));
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3151
3257
|
.choice-card:has(:checked)::after {
|
|
3152
3258
|
transform: scale(1);
|
|
3153
3259
|
}
|
|
@@ -3173,11 +3279,16 @@ small {
|
|
|
3173
3279
|
.choice-card.chip::before {
|
|
3174
3280
|
--choice-chip-dot: calc(var(--control-pad-x) + var(--choice-card-check-size) / 2) 50%;
|
|
3175
3281
|
|
|
3282
|
+
position: absolute;
|
|
3176
3283
|
inset: 0;
|
|
3284
|
+
/* why: an absolute box honours self-alignment — the card's start would
|
|
3285
|
+
shrink this layer to 0 height and the dot with it. */
|
|
3286
|
+
align-self: auto;
|
|
3287
|
+
margin: 0;
|
|
3177
3288
|
inline-size: auto;
|
|
3178
3289
|
block-size: auto;
|
|
3290
|
+
border: 0;
|
|
3179
3291
|
border-radius: inherit;
|
|
3180
|
-
box-shadow: none;
|
|
3181
3292
|
background: var(--text-subtle);
|
|
3182
3293
|
transform: none;
|
|
3183
3294
|
clip-path: circle(0.1875em at var(--choice-chip-dot));
|
|
@@ -3191,10 +3302,11 @@ small {
|
|
|
3191
3302
|
|
|
3192
3303
|
/* The check keeps the card's scale-in, in flow ahead of the label. */
|
|
3193
3304
|
.choice-card.chip::after {
|
|
3194
|
-
position: static;
|
|
3195
3305
|
order: -1;
|
|
3196
3306
|
flex: none;
|
|
3197
|
-
|
|
3307
|
+
align-self: center;
|
|
3308
|
+
margin-block: 0;
|
|
3309
|
+
margin-inline: 0 0.375em;
|
|
3198
3310
|
background: var(--intent, var(--state-selected));
|
|
3199
3311
|
-webkit-mask-size: contain;
|
|
3200
3312
|
mask-size: contain;
|
|
@@ -3204,9 +3316,11 @@ small {
|
|
|
3204
3316
|
this repair, forced colors pushes disc and check to the same system color
|
|
3205
3317
|
and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
|
|
3206
3318
|
so no forced-color-adjust is needed. The pseudo is not focusable — the
|
|
3207
|
-
outline
|
|
3319
|
+
outline separates the disc from whatever it overlaps (the card's border,
|
|
3320
|
+
for a corner-badge recipe), not a focus channel. */
|
|
3208
3321
|
@media (forced-colors: active) {
|
|
3209
3322
|
.choice-card:has(:checked)::before {
|
|
3323
|
+
border-color: Highlight;
|
|
3210
3324
|
background: Highlight;
|
|
3211
3325
|
outline: 2px solid Canvas;
|
|
3212
3326
|
}
|
|
@@ -3502,7 +3616,7 @@ small {
|
|
|
3502
3616
|
padding-block: 0;
|
|
3503
3617
|
padding-inline: calc((var(--otp-cell-size) - 1ch) / 2) 0;
|
|
3504
3618
|
border: 0;
|
|
3505
|
-
outline:
|
|
3619
|
+
outline: none;
|
|
3506
3620
|
box-shadow: none;
|
|
3507
3621
|
background: transparent;
|
|
3508
3622
|
color: var(--text);
|
|
@@ -3527,6 +3641,14 @@ small {
|
|
|
3527
3641
|
character after it: the state rules select the cells as following siblings
|
|
3528
3642
|
(input:focus ~ span, input:disabled ~ span, …), so that order is part of the
|
|
3529
3643
|
contract. */
|
|
3644
|
+
/* The documented markup puts .input on the native input, so the field ring
|
|
3645
|
+
(.input:focus-visible:not(:disabled), control.css) would outrank the reset
|
|
3646
|
+
above and draw that group ring; this selector must stay one step above it.
|
|
3647
|
+
No transparent outline either: forced colors would paint it as a ring. */
|
|
3648
|
+
.otp > input:focus-visible:not(:disabled) {
|
|
3649
|
+
outline: none;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3530
3652
|
.otp > input:focus ~ span {
|
|
3531
3653
|
--otp-cell-border: var(--focus);
|
|
3532
3654
|
--otp-cell-border-width: calc(var(--border-width) * 2);
|
|
@@ -5186,6 +5308,7 @@ picture.bleed > img {
|
|
|
5186
5308
|
* Public hooks:
|
|
5187
5309
|
* --flyout-inline-size
|
|
5188
5310
|
* --flyout-max-inline-size
|
|
5311
|
+
* --flyout-pad
|
|
5189
5312
|
*
|
|
5190
5313
|
* Internal:
|
|
5191
5314
|
* --menu-item-size (derived from --control-size for menu rows)
|
|
@@ -5238,7 +5361,9 @@ picture.bleed > img {
|
|
|
5238
5361
|
);
|
|
5239
5362
|
overflow: auto;
|
|
5240
5363
|
margin: 0;
|
|
5241
|
-
padding
|
|
5364
|
+
/* Tight by default for action menus, whose items carry their own padding.
|
|
5365
|
+
Rich panels (forms, pickers) opt into dialog air per instance. */
|
|
5366
|
+
padding: var(--flyout-pad, var(--space-10));
|
|
5242
5367
|
border: var(--border-width) solid var(--border);
|
|
5243
5368
|
border-radius: var(--radius);
|
|
5244
5369
|
background: var(--surface-raised);
|
|
@@ -5511,7 +5636,7 @@ picture.bleed > img {
|
|
|
5511
5636
|
*/
|
|
5512
5637
|
.tooltip {
|
|
5513
5638
|
--tooltip-bg: var(--surface-solid);
|
|
5514
|
-
--tooltip-fg: var(--surface);
|
|
5639
|
+
--tooltip-fg: var(--surface-solid-fg);
|
|
5515
5640
|
--tooltip-arrow-size: 5px;
|
|
5516
5641
|
|
|
5517
5642
|
position: fixed;
|
|
@@ -6157,8 +6282,8 @@ picture.bleed > img {
|
|
|
6157
6282
|
}
|
|
6158
6283
|
|
|
6159
6284
|
.status-bar {
|
|
6160
|
-
--status-bg: var(--intent, var(--
|
|
6161
|
-
--status-fg: var(--intent-fg, var(--surface));
|
|
6285
|
+
--status-bg: var(--intent, var(--surface-solid));
|
|
6286
|
+
--status-fg: var(--intent-fg, var(--surface-solid-fg));
|
|
6162
6287
|
|
|
6163
6288
|
position: fixed;
|
|
6164
6289
|
/* Fixed positioning tracks the layout viewport, which the mobile software
|
|
@@ -6428,7 +6553,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6428
6553
|
}
|
|
6429
6554
|
|
|
6430
6555
|
.avatar-stack > .avatar {
|
|
6431
|
-
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface);
|
|
6556
|
+
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
6432
6557
|
}
|
|
6433
6558
|
|
|
6434
6559
|
.avatar-stack > .avatar + .avatar {
|
|
@@ -6854,7 +6979,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6854
6979
|
}
|
|
6855
6980
|
|
|
6856
6981
|
.rating > input[type="radio"]:focus-visible {
|
|
6857
|
-
outline: var(--focus-
|
|
6982
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
6858
6983
|
outline-offset: var(--focus-outline-offset);
|
|
6859
6984
|
background: var(--rating-color);
|
|
6860
6985
|
}
|
|
@@ -6960,7 +7085,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6960
7085
|
--step-size: 1.75rem;
|
|
6961
7086
|
--step-inline-gap: var(--space-20);
|
|
6962
7087
|
--step-line-size: var(--border-width);
|
|
6963
|
-
--step-marker-bg: var(--surface);
|
|
7088
|
+
--step-marker-bg: var(--surface-opaque);
|
|
6964
7089
|
--step-marker-border: var(--border);
|
|
6965
7090
|
--step-marker-fg: var(--text-muted);
|
|
6966
7091
|
--step-marker-radius: var(--radius-full);
|
|
@@ -7040,7 +7165,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7040
7165
|
connector leaving current goes back to neutral. Declared after
|
|
7041
7166
|
.step-complete at equal specificity: current wins on every channel. */
|
|
7042
7167
|
.steps > [aria-current="step"] {
|
|
7043
|
-
--step-marker-bg: var(--surface);
|
|
7168
|
+
--step-marker-bg: var(--surface-opaque);
|
|
7044
7169
|
--step-marker-border: var(--state-selected);
|
|
7045
7170
|
--step-marker-fg: var(--state-selected);
|
|
7046
7171
|
--step-line: var(--border);
|
|
@@ -7079,7 +7204,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7079
7204
|
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
7080
7205
|
link has a ring to protect. */
|
|
7081
7206
|
.steps-horizontal:has(a[href]) {
|
|
7082
|
-
padding-block: calc(var(--focus-outline-offset) + var(--
|
|
7207
|
+
padding-block: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
7083
7208
|
}
|
|
7084
7209
|
|
|
7085
7210
|
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
@@ -7563,8 +7688,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
7563
7688
|
*
|
|
7564
7689
|
* Docs: docs/pages/components/pagination.md
|
|
7565
7690
|
*
|
|
7566
|
-
* Render .btn elements inside a .pagination list. The active page is
|
|
7567
|
-
* with aria-current="page"; .btn owns the active state styling
|
|
7691
|
+
* Render .btn.ghost elements inside a .pagination list. The active page is
|
|
7692
|
+
* marked with aria-current="page"; .btn owns the active state styling, so the
|
|
7693
|
+
* current page is the only filled item.
|
|
7568
7694
|
*
|
|
7569
7695
|
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
7570
7696
|
* — they are decorative, not actionable. They still get the same box sizing
|
|
@@ -7682,10 +7808,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
7682
7808
|
}
|
|
7683
7809
|
|
|
7684
7810
|
/* 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.
|
|
7811
|
+
of the strip rather than a floating chip. Color and width follow the
|
|
7812
|
+
component ring contract (focus.css); only the geometry is tab-specific. */
|
|
7686
7813
|
.tab:focus-visible {
|
|
7687
|
-
outline: var(--focus-
|
|
7688
|
-
outline-color: var(--focus);
|
|
7814
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
7689
7815
|
outline-offset: -2px;
|
|
7690
7816
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
7691
7817
|
}
|
|
@@ -7938,6 +8064,12 @@ a.list-item[aria-current]::before {
|
|
|
7938
8064
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
7939
8065
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
7940
8066
|
*
|
|
8067
|
+
* Responsive strategy is horizontal overflow: the wrap scrolls, the table
|
|
8068
|
+
* keeps its columns. Restructuring rows into cards would destroy the
|
|
8069
|
+
* column scanability tables exist for, and needs data-label duplication
|
|
8070
|
+
* because CSS cannot join a cell to its header — that representation choice
|
|
8071
|
+
* belongs to the application, not the framework.
|
|
8072
|
+
*
|
|
7941
8073
|
* Public hooks:
|
|
7942
8074
|
* --table-min
|
|
7943
8075
|
* --table-cell-pad
|
|
@@ -8058,7 +8190,7 @@ a.list-item[aria-current]::before {
|
|
|
8058
8190
|
}
|
|
8059
8191
|
|
|
8060
8192
|
.meter:not(meter) > * + * {
|
|
8061
|
-
border-inline-start: var(--border-width) solid var(--surface);
|
|
8193
|
+
border-inline-start: var(--border-width) solid var(--surface-opaque);
|
|
8062
8194
|
}
|
|
8063
8195
|
|
|
8064
8196
|
.meter::-webkit-meter-bar {
|
|
@@ -8295,36 +8427,55 @@ a.list-item[aria-current]::before {
|
|
|
8295
8427
|
|
|
8296
8428
|
.aura {
|
|
8297
8429
|
--aura-color: var(--intent, var(--primary));
|
|
8298
|
-
--aura-width:
|
|
8430
|
+
--aura-width: 1px;
|
|
8299
8431
|
--aura-blur: 0.25rem;
|
|
8300
|
-
--aura-intensity:
|
|
8432
|
+
--aura-intensity: 0.4;
|
|
8301
8433
|
--aura-radius: var(--radius-lg);
|
|
8302
8434
|
--aura-duration: 6s;
|
|
8303
8435
|
|
|
8304
8436
|
position: relative;
|
|
8305
8437
|
isolation: isolate;
|
|
8306
8438
|
display: inline-grid;
|
|
8307
|
-
padding: var(--aura-width);
|
|
8308
8439
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
8309
|
-
|
|
8440
|
+
|
|
8441
|
+
/* The frame is the border itself, so --aura-width is the painted thickness.
|
|
8442
|
+
A transparent border stays for forced colors, where it becomes the visible
|
|
8443
|
+
boundary; a padding frame inside it would paint both, doubling the line. */
|
|
8444
|
+
border: var(--aura-width) solid transparent;
|
|
8310
8445
|
background: var(--aura-color);
|
|
8311
8446
|
|
|
8312
8447
|
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
8313
8448
|
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
8314
|
-
across
|
|
8449
|
+
across the frame draws an outline, the opposite of the effect. `border-box`
|
|
8450
|
+
sizes the gradient to the border box: the default padding-box origin would
|
|
8451
|
+
tile a neighbouring copy of the gradient into the border, which is now the
|
|
8452
|
+
whole frame. */
|
|
8315
8453
|
background: conic-gradient(
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
/*
|
|
8454
|
+
from var(--aura-angle),
|
|
8455
|
+
transparent 0 78%,
|
|
8456
|
+
var(--aura-color) 93%,
|
|
8457
|
+
transparent
|
|
8458
|
+
)
|
|
8459
|
+
border-box;
|
|
8460
|
+
|
|
8461
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
8462
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
8463
|
+
layer. This is the layer that stays lit at all times, so it — not the
|
|
8464
|
+
frame — is what keeps the object present between two passes of the
|
|
8465
|
+
accent. An outer shadow never paints under the box, so it stays out of a
|
|
8466
|
+
translucent child. min() because a color-mix() share above 100% is
|
|
8467
|
+
invalid, where the opacity it replaces clamped. */
|
|
8468
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 8) var(--aura-blur)
|
|
8469
|
+
color-mix(in oklab, var(--aura-color) min(100%, calc(15% * var(--aura-intensity))), transparent);
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8472
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. An
|
|
8473
|
+
absolute inset starts at the padding edge, inside the border that is the
|
|
8474
|
+
frame, so the negative inset puts the halo back on the frame. */
|
|
8324
8475
|
.aura::before {
|
|
8325
8476
|
position: absolute;
|
|
8326
8477
|
z-index: -1;
|
|
8327
|
-
inset:
|
|
8478
|
+
inset: calc(-1 * var(--aura-width));
|
|
8328
8479
|
border-radius: inherit;
|
|
8329
8480
|
background: inherit;
|
|
8330
8481
|
filter: blur(var(--aura-blur));
|
|
@@ -8333,29 +8484,22 @@ a.list-item[aria-current]::before {
|
|
|
8333
8484
|
content: "";
|
|
8334
8485
|
}
|
|
8335
8486
|
|
|
8336
|
-
/*
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
passes of the accent. */
|
|
8487
|
+
/* Occlusion — the frame and the near halo paint across the whole box, and only
|
|
8488
|
+
their edge is meant to show. This mask sits above both, under the child, so
|
|
8489
|
+
a transparent or translucent child (an .outline alert, a glass surface) is
|
|
8490
|
+
framed rather than filled with the frame color. It follows the child's
|
|
8491
|
+
shape through --aura-radius. */
|
|
8342
8492
|
.aura::after {
|
|
8343
8493
|
position: absolute;
|
|
8344
8494
|
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));
|
|
8495
|
+
inset: 0;
|
|
8496
|
+
border-radius: var(--aura-radius);
|
|
8497
|
+
background: var(--surface-opaque);
|
|
8350
8498
|
pointer-events: none;
|
|
8351
8499
|
content: "";
|
|
8352
8500
|
}
|
|
8353
8501
|
|
|
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. */
|
|
8502
|
+
/* Lifts the child above the halo and the mask. */
|
|
8359
8503
|
.aura > * {
|
|
8360
8504
|
position: relative;
|
|
8361
8505
|
}
|
|
@@ -8378,7 +8522,7 @@ a.list-item[aria-current]::before {
|
|
|
8378
8522
|
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
8379
8523
|
}
|
|
8380
8524
|
|
|
8381
|
-
.aura-glow
|
|
8525
|
+
.aura-glow {
|
|
8382
8526
|
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
8383
8527
|
}
|
|
8384
8528
|
}
|
|
@@ -8390,8 +8534,8 @@ a.list-item[aria-current]::before {
|
|
|
8390
8534
|
@media (prefers-reduced-motion: reduce) {
|
|
8391
8535
|
.aura:not(.aura-glow) {
|
|
8392
8536
|
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%);
|
|
8537
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%) border-box,
|
|
8538
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%) border-box;
|
|
8395
8539
|
}
|
|
8396
8540
|
}
|
|
8397
8541
|
|
|
@@ -8412,10 +8556,16 @@ a.list-item[aria-current]::before {
|
|
|
8412
8556
|
}
|
|
8413
8557
|
}
|
|
8414
8558
|
|
|
8559
|
+
/* The far halo is the element's own shadow: opening its spread and share
|
|
8560
|
+
breathes it without scaling the child. */
|
|
8415
8561
|
@keyframes aura-breathe-far {
|
|
8416
8562
|
50% {
|
|
8417
|
-
|
|
8418
|
-
|
|
8563
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 10) calc(var(--aura-blur) * 2)
|
|
8564
|
+
color-mix(
|
|
8565
|
+
in oklab,
|
|
8566
|
+
var(--aura-color) min(100%, calc(28% * var(--aura-intensity))),
|
|
8567
|
+
transparent
|
|
8568
|
+
);
|
|
8419
8569
|
}
|
|
8420
8570
|
}
|
|
8421
8571
|
|
|
@@ -8435,6 +8585,64 @@ a.list-item[aria-current]::before {
|
|
|
8435
8585
|
--aura-radius: var(--radius-full);
|
|
8436
8586
|
}
|
|
8437
8587
|
|
|
8588
|
+
/*
|
|
8589
|
+
* Highlight sweeping across a text label — an activity in progress.
|
|
8590
|
+
*
|
|
8591
|
+
* Docs: docs/pages/components/shimmer.md
|
|
8592
|
+
*
|
|
8593
|
+
* Presentation only: it carries no state and selects no ARIA attribute, so the
|
|
8594
|
+
* label stays whatever the author makes it (a role="status" message, a list
|
|
8595
|
+
* item, a heading).
|
|
8596
|
+
*
|
|
8597
|
+
* Public hooks:
|
|
8598
|
+
* --shimmer-color
|
|
8599
|
+
* --shimmer-highlight
|
|
8600
|
+
*/
|
|
8601
|
+
.shimmer {
|
|
8602
|
+
--shimmer-color: var(--text-muted);
|
|
8603
|
+
--shimmer-highlight: var(--text);
|
|
8604
|
+
|
|
8605
|
+
color: var(--shimmer-color);
|
|
8606
|
+
}
|
|
8607
|
+
|
|
8608
|
+
/* The sweep paints the glyphs through a clipped background, which needs the
|
|
8609
|
+
glyph fill transparent. Without background-clip: text that would erase the
|
|
8610
|
+
label, so the transparency lives under the same query as the clip. It is
|
|
8611
|
+
the fill, not `color`: a label class that sets its own color (.muted, a
|
|
8612
|
+
caption style) would otherwise win the cascade and paint opaque glyphs over
|
|
8613
|
+
the sweep. Reduced motion keeps the plain label — a frozen band would read
|
|
8614
|
+
as a random tint. */
|
|
8615
|
+
@supports (background-clip: text) {
|
|
8616
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
8617
|
+
.shimmer {
|
|
8618
|
+
-webkit-text-fill-color: transparent;
|
|
8619
|
+
background:
|
|
8620
|
+
linear-gradient(
|
|
8621
|
+
90deg,
|
|
8622
|
+
var(--shimmer-color) 40%,
|
|
8623
|
+
var(--shimmer-highlight) 50%,
|
|
8624
|
+
var(--shimmer-color) 60%
|
|
8625
|
+
)
|
|
8626
|
+
0 0 / 300% 100%;
|
|
8627
|
+
background-clip: text;
|
|
8628
|
+
animation: actual-shimmer var(--duration-shimmer) linear infinite;
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
/* The band occupies the middle third of a background three times the label's
|
|
8634
|
+
width, so each pass leaves the end of the label before the next one enters
|
|
8635
|
+
at the start. */
|
|
8636
|
+
@keyframes actual-shimmer {
|
|
8637
|
+
from {
|
|
8638
|
+
background-position: 100% 0;
|
|
8639
|
+
}
|
|
8640
|
+
|
|
8641
|
+
to {
|
|
8642
|
+
background-position: 0% 0;
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8438
8646
|
/* Utilities — small, single-purpose escape hatches. */
|
|
8439
8647
|
/*
|
|
8440
8648
|
* Small, single-purpose utilities for common escape hatches.
|