maverick-wave 5.38.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +63 -47
  2. package/maverick-wave.min.css +94 -68
  3. package/maverick-wave.min.js +1 -1
  4. package/package.json +7 -7
  5. package/src/js/main.js +382 -57
  6. package/src/scss/abstracts/_mixins.scss +35 -24
  7. package/src/scss/abstracts/_variables.scss +15 -0
  8. package/src/scss/base/_base.scss +107 -64
  9. package/src/scss/base/_index.scss +1 -0
  10. package/src/scss/base/_page-transitions.scss +29 -0
  11. package/src/scss/base/_reset.scss +0 -8
  12. package/src/scss/components/_accordions.scss +23 -18
  13. package/src/scss/components/_action-bar.scss +143 -0
  14. package/src/scss/components/_alerts.scss +13 -1
  15. package/src/scss/components/_avatars.scss +17 -0
  16. package/src/scss/components/_badge.scss +12 -0
  17. package/src/scss/components/_breadcrumbs.scss +10 -2
  18. package/src/scss/components/_chat.scss +250 -0
  19. package/src/scss/components/_code.scss +3 -1
  20. package/src/scss/components/_compare.scss +135 -0
  21. package/src/scss/components/_deck.scss +8 -14
  22. package/src/scss/components/_devices.scss +323 -0
  23. package/src/scss/components/_dropdown.scss +43 -2
  24. package/src/scss/components/_empty-state.scss +43 -0
  25. package/src/scss/components/_flag.scss +16 -0
  26. package/src/scss/components/_index.scss +6 -0
  27. package/src/scss/components/_lightbox.scss +1 -1
  28. package/src/scss/components/_marquee.scss +132 -0
  29. package/src/scss/components/_modals.scss +112 -83
  30. package/src/scss/components/_occasions.scss +62 -11
  31. package/src/scss/components/_pagination.scss +91 -0
  32. package/src/scss/components/_portrait-gallery.scss +1 -1
  33. package/src/scss/components/_progress.scss +122 -2
  34. package/src/scss/components/_segmented.scss +65 -2
  35. package/src/scss/components/_skeleton.scss +8 -0
  36. package/src/scss/components/_spinners.scss +38 -0
  37. package/src/scss/components/_stat.scss +232 -0
  38. package/src/scss/components/_stepper.scss +42 -2
  39. package/src/scss/components/_stories.scss +36 -22
  40. package/src/scss/components/_tables.scss +160 -17
  41. package/src/scss/components/_tabs.scss +42 -4
  42. package/src/scss/components/_tags.scss +39 -4
  43. package/src/scss/components/_tiles.scss +1 -1
  44. package/src/scss/components/_timelines.scss +138 -1
  45. package/src/scss/components/_toasts.scss +5 -1
  46. package/src/scss/components/_tooltip.scss +13 -0
  47. package/src/scss/form-elements/_checkbox.scss +40 -7
  48. package/src/scss/form-elements/_form.scss +2 -4
  49. package/src/scss/form-elements/_input.scss +9 -6
  50. package/src/scss/form-elements/_radio.scss +32 -3
  51. package/src/scss/form-elements/_select.scss +44 -1
  52. package/src/scss/form-elements/_slider.scss +18 -0
  53. package/src/scss/form-elements/_textarea.scss +9 -6
  54. package/src/scss/form-elements/_toggle.scss +37 -4
  55. package/src/scss/layout/_app-shell.scss +661 -0
  56. package/src/scss/layout/_bento.scss +43 -0
  57. package/src/scss/layout/_footer.scss +114 -2
  58. package/src/scss/layout/_grid.scss +7 -2
  59. package/src/scss/layout/_header-reveal.scss +30 -17
  60. package/src/scss/layout/_header.scss +21 -4
  61. package/src/scss/layout/_index.scss +2 -0
  62. package/src/scss/layout/_parallax.scss +13 -11
  63. package/src/scss/utilities/_accessibility.scss +4 -19
  64. package/src/scss/utilities/_index.scss +1 -0
  65. package/src/scss/utilities/_print.scss +7 -3
  66. package/src/scss/utilities/_reveal.scss +25 -17
  67. package/src/scss/utilities/_shape.scss +24 -0
  68. package/src/scss/utilities/_touch-targets.scss +23 -0
  69. package/src/scss/utilities/_variants.scss +25 -6
@@ -1,42 +1,6 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
3
  @layer mw.components {
4
- // Modal overlay - hidden by default
5
- .mw-modal-overlay {
6
- position: fixed;
7
- top: 0;
8
- left: 0;
9
- right: 0;
10
- bottom: 0;
11
- background: var(--mw-overlay-background);
12
- backdrop-filter: blur(3px);
13
- z-index: z-index('modal')-10;
14
- display: none;
15
- align-items: center;
16
- justify-content: center;
17
- padding: spacing('7');
18
- opacity: 0;
19
- // `display` in the list and `allow-discrete` behind it, or the overlay is
20
- // taken off the page the frame the class comes off and the fade out never
21
- // renders - the modal inside it would slide down over nothing. The dialog
22
- // version below has had this; the div version was landing hard.
23
- transition:
24
- opacity var(--mw-duration-base) var(--mw-ease-out),
25
- display var(--mw-duration-base) allow-discrete;
26
-
27
- // Show modal when class is added
28
- &.mw-modal-open {
29
- display: flex;
30
- opacity: 1;
31
-
32
- // A box that goes from `display: none` starts at its end state otherwise,
33
- // so there is nothing to fade from
34
- @starting-style {
35
- opacity: 0;
36
- }
37
- }
38
- }
39
-
40
4
  // Modal container
41
5
  .mw-modal {
42
6
  background: var(--mw-card-background);
@@ -49,12 +13,6 @@
49
13
  display: flex;
50
14
  flex-direction: column;
51
15
  z-index: z-index('modal');
52
- transform: scale(0.96) translateY(12px);
53
- transition: transform var(--mw-duration-base) var(--mw-ease-out);
54
-
55
- .mw-modal-open & {
56
- transform: scale(1) translateY(0);
57
- }
58
16
 
59
17
  // The four widths grow by a roughly even step (150 / 200 / 240px) so each
60
18
  // size is recognisably a size. Before, default and lg sat 130px apart - the
@@ -76,11 +34,6 @@
76
34
 
77
35
  // Phone: the dialog becomes a sheet
78
36
  @include media-down('sm') {
79
- .mw-modal-overlay {
80
- padding: spacing('0');
81
- align-items: flex-end;
82
- }
83
-
84
37
  .mw-modal,
85
38
  .mw-modal.mw-modal-sm,
86
39
  .mw-modal.mw-modal-lg,
@@ -89,13 +42,6 @@
89
42
  --mw-modal-max-height: 92dvh;
90
43
  border-radius: radius('xl') radius('xl') radius('none') radius('none');
91
44
  padding-bottom: env(safe-area-inset-bottom, 0px);
92
- // Straight up from the bottom edge - a sheet does not scale in
93
- transform: translateY(100%);
94
- transition: transform var(--mw-duration-slow) var(--mw-ease-out);
95
- }
96
-
97
- .mw-modal-open .mw-modal {
98
- transform: translateY(0);
99
45
  }
100
46
 
101
47
  // The grab handle. It does nothing - there is no drag-to-dismiss behind it -
@@ -136,11 +82,10 @@
136
82
  }
137
83
  }
138
84
 
139
- // The same modal as a <dialog>, which brings the focus trap, Escape, the inert
140
- // background and the scroll lock a div cannot have. Every rule above is
141
- // class-based and applies unchanged; what follows is only what the top layer
142
- // changes. `closedby="any"` dismisses on a backdrop click, and where that is
143
- // not understood the framework script handles the click itself.
85
+ // A <dialog>, which brings the focus trap, Escape and the inert background.
86
+ // What follows is what the top layer changes on top of the rules above.
87
+ // `closedby="any"` dismisses on a backdrop click, and where that is not
88
+ // understood the framework script handles the click itself.
144
89
  dialog.mw-modal {
145
90
  // The UA draws its own box - a border, padding and a white background - and
146
91
  // `.mw-modal` above already has all three.
@@ -174,7 +119,7 @@
174
119
  display var(--mw-duration-base) allow-discrete;
175
120
 
176
121
  &[open] {
177
- // A block, not the flex column of the div version and not a grid: iOS
122
+ // A block, not the flex column `.mw-modal` sets and not a grid: iOS
178
123
  // WebKit takes the intrinsic height of a scrolling flex or grid item in a
179
124
  // top-layer dialog as zero. A dialog shorter than its cap then shows its
180
125
  // header and footer and nothing between - a grid only hides it until the
@@ -223,17 +168,15 @@
223
168
  }
224
169
  }
225
170
 
226
- // Phone: the same bottom sheet the div version turns into. The width, the
227
- // height cap, the corners and the safe-area padding all come from the
228
- // `.mw-modal` rule above, which is class-based and applies here too - only
229
- // what a top-layer box needs on top of it is repeated.
171
+ // Phone: a bottom sheet. The width, the height cap, the corners and the
172
+ // safe-area padding come from the `.mw-modal` phone rule above.
230
173
  @include media-down('sm') {
231
174
  // The UA centres a dialog; a sheet sits on the bottom edge.
232
175
  margin: auto auto 0;
233
176
  width: 100%;
234
177
  // Straight up from the bottom edge - a sheet does not scale in, and it
235
178
  // travels the height of itself rather than twelve pixels, so it gets the
236
- // slow duration the div sheet above uses for the same move.
179
+ // slow duration.
237
180
  transform: translateY(100%);
238
181
  transition:
239
182
  opacity var(--mw-duration-slow) var(--mw-ease-out),
@@ -251,9 +194,9 @@
251
194
  }
252
195
  }
253
196
 
254
- // Neither shape locks the page scroll behind it - the <dialog> only makes the
255
- // page inert, which still lets a finger or a wheel move it
256
- :root:has(dialog.mw-modal[open], .mw-modal-overlay.mw-modal-open) {
197
+ // The <dialog> only makes the page inert, which still lets a finger or a
198
+ // wheel move it
199
+ :root:has(dialog.mw-modal[open]) {
257
200
  overflow: hidden;
258
201
  scrollbar-gutter: stable; // Or the page shifts by the hidden scrollbar
259
202
  }
@@ -339,21 +282,107 @@
339
282
  }
340
283
  }
341
284
 
342
- // Backdrop for click-outside-to-close.
343
- // The cursor says it is clickable, so it should also be reachable by keyboard -
344
- // which means it wants to be a <button>. These four lines clear the browser
345
- // defaults that come with one; on a <div> they change nothing.
346
- .mw-modal-backdrop {
347
- position: absolute;
348
- top: 0;
349
- left: 0;
285
+ // Stays a side panel on a phone, where every other modal becomes a sheet
286
+ dialog.mw-modal.mw-modal-drawer {
287
+ inset-block: 0;
350
288
  right: 0;
351
- bottom: 0;
352
- z-index: z-index('modal')-5;
353
- cursor: pointer;
354
- appearance: none;
355
- padding: 0;
356
- background: none;
357
- border: 0;
289
+ left: auto;
290
+ width: min(420px, 100vw - #{spacing('9')});
291
+ max-width: none;
292
+ height: 100dvh;
293
+ max-height: none;
294
+ margin: 0;
295
+ padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px);
296
+ // Only the inner edge shows, and it keeps its half of the silhouette
297
+ border-radius: radius-sharp() radius('none') radius('none') radius('xl');
298
+ opacity: 1;
299
+ transform: translateX(100%);
300
+ transition:
301
+ transform var(--mw-duration-slow) var(--mw-ease-out),
302
+ overlay var(--mw-duration-slow) allow-discrete,
303
+ display var(--mw-duration-slow) allow-discrete;
304
+
305
+ &::after {
306
+ @include corner-accent(0, 'bottom');
307
+ }
308
+
309
+ &[open] {
310
+ // Flex is safe here: the height is fixed, so the body never sizes itself
311
+ display: flex;
312
+ flex-direction: column;
313
+ transform: none;
314
+
315
+ @starting-style {
316
+ transform: translateX(100%);
317
+ }
318
+ }
319
+
320
+ .mw-modal-body {
321
+ flex: 1 1 0;
322
+ min-height: 0;
323
+ max-height: none;
324
+ }
325
+
326
+ .mw-modal-header {
327
+ padding: spacing('3') spacing('5');
328
+
329
+ &::before {
330
+ content: none;
331
+ }
332
+ }
333
+
334
+ .mw-modal-footer {
335
+ padding: spacing('3') spacing('5');
336
+ }
337
+
338
+ // Opened with show() it is outside the top layer and needs its own z-index
339
+ &:not(:modal) {
340
+ z-index: z-index('modal');
341
+ box-shadow: var(--mw-elevation-4);
342
+ }
343
+ }
344
+
345
+ dialog.mw-modal.mw-modal-drawer-start {
346
+ right: auto;
347
+ left: 0;
348
+ border-radius: radius('none') radius('xl') radius-sharp() radius('none');
349
+ transform: translateX(-100%);
350
+
351
+ &::after {
352
+ @include corner-accent(0, 'top');
353
+ }
354
+
355
+ &[open] {
356
+ transform: none;
357
+
358
+ @starting-style {
359
+ transform: translateX(-100%);
360
+ }
361
+ }
362
+ }
363
+
364
+ :root.mw-corners-even dialog.mw-modal.mw-modal-drawer {
365
+ border-radius: radius('md') radius('none') radius('none') radius('md');
366
+
367
+ // The single mark would sit on the screen edge - mirrored to the inner corner
368
+ &::after {
369
+ transform: scaleX(-1);
370
+ }
371
+ }
372
+
373
+ :root.mw-corners-even dialog.mw-modal.mw-modal-drawer-start {
374
+ border-radius: radius('none') radius('md') radius('md') radius('none');
375
+
376
+ &::after {
377
+ transform: none;
378
+ }
379
+ }
380
+
381
+ // A non-modal drawer leaves the page usable, so it keeps the page scroll
382
+ :root:has(dialog.mw-modal-drawer[open]:not(:modal)):not(
383
+ :has(dialog.mw-modal:modal)
384
+ ) {
385
+ overflow: visible;
386
+ scrollbar-gutter: auto;
358
387
  }
359
388
  }
@@ -814,16 +814,18 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
814
814
  );
815
815
 
816
816
  @supports (animation-timeline: view()) {
817
- @media (prefers-reduced-motion: no-preference) {
818
- // Timed by the card's entry, which even the last card on a page completes;
819
- // 60vh keeps a tall card from arriving only at the top of the screen
820
- :root.mw-occasion-scroll :is(#{$_host}),
821
- .mw-occasion-scroll:is(#{$_effect-classes}):is(#{$_carriers}) {
817
+ // Timed by the card's entry, which even the last card on a page completes;
818
+ // 60vh keeps a tall card from arriving only at the top of the screen
819
+ :root.mw-occasion-scroll :is(#{$_host}),
820
+ .mw-occasion-scroll:is(#{$_effect-classes}):is(#{$_carriers}) {
821
+ @include motion-safe {
822
822
  view-timeline: --mw-occasion-arrival;
823
823
  }
824
+ }
824
825
 
825
- @each $effect, $arrival in $_arrivals {
826
- #{_on($effect, '.mw-occasion-scroll')} {
826
+ @each $effect, $arrival in $_arrivals {
827
+ #{_on($effect, '.mw-occasion-scroll')} {
828
+ @include motion-safe {
827
829
  animation-name: mw-occasion-#{$arrival};
828
830
  animation-duration: auto;
829
831
  animation-timing-function: linear;
@@ -835,6 +837,24 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
835
837
  }
836
838
  }
837
839
 
840
+ // No view timelines (Firefox): main.js marks each host and swaps it on entry
841
+ @each $effect, $arrival in $_arrivals {
842
+ #{_on($effect, '.mw-occasion-scroll', '.mw-occasion-waiting::before')} {
843
+ @include motion-safe {
844
+ opacity: 0;
845
+ }
846
+ }
847
+
848
+ #{_on($effect, '.mw-occasion-scroll', '.mw-occasion-arriving::before')} {
849
+ @include motion-safe {
850
+ animation-name: mw-occasion-#{$arrival};
851
+ animation-duration: var(--mw-duration-slower);
852
+ animation-timing-function: var(--mw-ease-out);
853
+ animation-fill-mode: backwards;
854
+ }
855
+ }
856
+ }
857
+
838
858
  @keyframes mw-occasion-appear {
839
859
  from {
840
860
  opacity: 0;
@@ -960,11 +980,17 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
960
980
  // Below the header, so the bar stays clean, above cards and sections.
961
981
  .mw-occasion-drift {
962
982
  --mw-occasion-laps: 3;
983
+ --mw-occasion-progress: 0;
963
984
 
964
985
  z-index: z-index('header') - 20;
965
986
  opacity: 0;
966
987
  }
967
988
 
989
+ .mw-occasion-drift.mw-occasion-driven {
990
+ opacity: 1;
991
+ animation-name: none;
992
+ }
993
+
968
994
  // Visible only while the scroll drives it - on a page too short to scroll the
969
995
  // timeline is inactive, and every piece would stand in the top left corner
970
996
  @supports (animation-timeline: scroll()) {
@@ -984,14 +1010,14 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
984
1010
  }
985
1011
 
986
1012
  @supports not (animation-timeline: scroll()) {
987
- .mw-occasion-drift {
1013
+ .mw-occasion-drift:not(.mw-occasion-driven) {
988
1014
  display: none;
989
1015
  }
990
1016
  }
991
1017
 
992
- @media (prefers-reduced-motion: reduce) {
993
- .mw-occasion-intro,
994
- .mw-occasion-drift {
1018
+ .mw-occasion-intro,
1019
+ .mw-occasion-drift {
1020
+ @include motion-reduced {
995
1021
  display: none;
996
1022
  }
997
1023
  }
@@ -1017,6 +1043,7 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
1017
1043
  --mw-occasion-angle: 0deg;
1018
1044
  --mw-occasion-tint: #fff;
1019
1045
  --mw-occasion-start: 0%;
1046
+ --mw-occasion-from: 0;
1020
1047
  --mw-occasion-lap: 0;
1021
1048
 
1022
1049
  position: absolute;
@@ -1079,6 +1106,30 @@ $_host: '#{$_surface}, .mw-occasion-spot:not(#{$_effect-classes})';
1079
1106
  }
1080
1107
  }
1081
1108
 
1109
+ // The same scrub fed by main.js: paused, parked at the laps travelled, 1s a lap
1110
+ .mw-occasion-driven > .mw-occasion-particle {
1111
+ $laps: calc(var(--mw-occasion-laps) + var(--mw-occasion-lap));
1112
+ $travelled: calc(
1113
+ max(0, var(--mw-occasion-progress) - var(--mw-occasion-from)) /
1114
+ (1 - var(--mw-occasion-from)) * #{$laps} * -1s
1115
+ );
1116
+
1117
+ animation-duration: 1s, 0.25s;
1118
+ animation-delay: $travelled;
1119
+ animation-iteration-count: $laps, calc(#{$laps} * 4);
1120
+ animation-play-state: paused;
1121
+ animation-timeline: auto;
1122
+ animation-range: normal;
1123
+
1124
+ &::before {
1125
+ animation-duration: 1s;
1126
+ animation-delay: $travelled;
1127
+ animation-iteration-count: $laps;
1128
+ animation-play-state: paused;
1129
+ animation-timeline: auto;
1130
+ }
1131
+ }
1132
+
1082
1133
  // Thrown from a bottom corner: sideways with drag, up and back down on
1083
1134
  // separate properties so each keeps its own easing.
1084
1135
  .mw-occasion-bursting {
@@ -96,4 +96,95 @@
96
96
  font-size: font-size('md');
97
97
  }
98
98
  }
99
+
100
+ // A phone gets "3 of 12" between the arrows instead of a row of numbers
101
+ .mw-pagination-pages {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ gap: spacing('1');
106
+ }
107
+
108
+ .mw-pagination-page,
109
+ .mw-pagination-gap {
110
+ display: none;
111
+ }
112
+
113
+ .mw-pagination-status {
114
+ padding-inline: spacing('3');
115
+ color: var(--mw-text-muted-color);
116
+ font-size: font-size('sm');
117
+ font-variant-numeric: tabular-nums;
118
+ white-space: nowrap;
119
+ }
120
+
121
+ .mw-pagination-page {
122
+ align-items: center;
123
+ justify-content: center;
124
+ min-width: 2.25rem;
125
+ height: 2.25rem;
126
+ padding-inline: spacing('2');
127
+ border: 1px solid transparent;
128
+ // The framework silhouette at button size
129
+ border-radius: radius-sharp() radius('md') radius-sharp() radius('md');
130
+ background: none;
131
+ color: var(--mw-text-color);
132
+ font: inherit;
133
+ font-size: font-size('sm');
134
+ font-variant-numeric: tabular-nums;
135
+ text-decoration: none;
136
+ cursor: pointer;
137
+ transition: var(--mw-transition-fast);
138
+ @include tappable;
139
+ @include focus-ring;
140
+ @include touch-floor {
141
+ min-width: 2.75rem;
142
+ }
143
+
144
+ &:hover {
145
+ background: var(--mw-gray-background);
146
+ }
147
+
148
+ &:is([aria-current='page'], .mw-active, .active) {
149
+ background: var(--mw-primary-color);
150
+ border-color: var(--mw-primary-text-color);
151
+ color: var(--mw-primary-accent-text-color);
152
+ font-weight: font-weight('medium');
153
+ cursor: default;
154
+ }
155
+ }
156
+
157
+ .mw-pagination-gap {
158
+ min-width: 1.5rem;
159
+ color: var(--mw-text-muted-color);
160
+ text-align: center;
161
+ }
162
+
163
+ // First and last page: an arrow that leads nowhere says so
164
+ .mw-pagination-pages [aria-disabled='true'] {
165
+ opacity: 0.45;
166
+ pointer-events: none;
167
+ }
168
+
169
+ @include media-up('sm') {
170
+ .mw-pagination-page {
171
+ display: inline-flex;
172
+ }
173
+
174
+ .mw-pagination-gap {
175
+ display: inline-block;
176
+ }
177
+
178
+ .mw-pagination-status {
179
+ display: none;
180
+ }
181
+ }
182
+
183
+ @media (forced-colors: active) {
184
+ .mw-pagination-page:is([aria-current='page'], .mw-active, .active) {
185
+ forced-color-adjust: none;
186
+ background: Highlight;
187
+ color: HighlightText;
188
+ }
189
+ }
99
190
  }
@@ -33,7 +33,7 @@
33
33
  display: none;
34
34
  }
35
35
 
36
- @media (prefers-reduced-motion: no-preference) {
36
+ @include motion-safe {
37
37
  scroll-behavior: smooth;
38
38
  }
39
39
  }
@@ -89,11 +89,11 @@
89
89
  @supports (animation-timeline: view()) {
90
90
  // Reduced motion skips the animation below, so the width has to be set
91
91
  // outright - the blanket rule in _base.scss keeps the transition instant.
92
- @media (prefers-reduced-motion: reduce) {
92
+ @include motion-reduced {
93
93
  width: var(--mw-progress-value);
94
94
  }
95
95
 
96
- @media (prefers-reduced-motion: no-preference) {
96
+ @include motion-safe {
97
97
  // Longhands and never the shorthand, which esbuild collapses into one
98
98
  // `animation:` that cannot carry a timeline.
99
99
  animation-name: mw-progress-fill;
@@ -150,4 +150,124 @@
150
150
  width: var(--mw-progress-value);
151
151
  }
152
152
  }
153
+
154
+ // Busy without a figure: a segment sweeps the track, a still hatch without motion
155
+ .mw-progress-indeterminate {
156
+ --mw-progress-indeterminate: var(--mw-secondary-color);
157
+
158
+ &::after {
159
+ content: '';
160
+ position: absolute;
161
+ inset: 0;
162
+ border-radius: inherit;
163
+ background: repeating-linear-gradient(
164
+ -45deg,
165
+ var(--mw-progress-indeterminate) 0 6px,
166
+ transparent 6px 12px
167
+ );
168
+ opacity: 0.55;
169
+
170
+ @include motion-safe {
171
+ inset: 0 auto 0 0;
172
+ width: 35%;
173
+ background: var(--mw-progress-indeterminate);
174
+ opacity: 1;
175
+ animation-name: mw-progress-sweep;
176
+ animation-duration: motion(1.6s);
177
+ animation-timing-function: var(--mw-ease-in-out);
178
+ animation-iteration-count: infinite;
179
+ }
180
+ }
181
+ }
182
+
183
+ @keyframes mw-progress-sweep {
184
+ from {
185
+ translate: -100% 0;
186
+ }
187
+
188
+ to {
189
+ translate: 300% 0;
190
+ }
191
+ }
192
+
193
+ // The same figure round; the value is a registered property, so a change animates
194
+ .mw-progress-ring {
195
+ --mw-progress-ring-color: var(--mw-secondary-color);
196
+ --mw-progress-ring-size: 4.5rem;
197
+ --mw-progress-ring-thickness: 0.5rem;
198
+
199
+ position: relative;
200
+ display: inline-grid;
201
+ place-items: center;
202
+ width: var(--mw-progress-ring-size);
203
+ aspect-ratio: 1;
204
+ color: var(--mw-text-color);
205
+ font-size: calc(var(--mw-progress-ring-size) * 0.24);
206
+ font-weight: font-weight('bold');
207
+ font-variant-numeric: tabular-nums lining-nums;
208
+ transition: --mw-progress-value var(--mw-duration-slower)
209
+ var(--mw-ease-in-out);
210
+
211
+ &::before {
212
+ content: '';
213
+ position: absolute;
214
+ inset: 0;
215
+ border-radius: radius('full');
216
+ background: conic-gradient(
217
+ var(--mw-progress-ring-color) var(--mw-progress-value),
218
+ var(--mw-gray-background) 0
219
+ );
220
+ mask: radial-gradient(
221
+ farthest-side,
222
+ transparent calc(100% - var(--mw-progress-ring-thickness)),
223
+ #000 calc(100% - var(--mw-progress-ring-thickness) + 1px)
224
+ );
225
+ }
226
+ }
227
+
228
+ .mw-progress-ring-sm {
229
+ --mw-progress-ring-size: 3rem;
230
+ --mw-progress-ring-thickness: 0.35rem;
231
+ }
232
+
233
+ .mw-progress-ring-lg {
234
+ --mw-progress-ring-size: 7rem;
235
+ --mw-progress-ring-thickness: 0.7rem;
236
+ }
237
+
238
+ @each $name, $col in $semantic-colors {
239
+ .mw-progress-ring-#{$name} {
240
+ --mw-progress-ring-color: #{$col};
241
+ }
242
+ }
243
+
244
+ // Fill, sweep and arc are all backgrounds, which forced colours would drop
245
+ @media (forced-colors: active) {
246
+ .mw-progress-bar {
247
+ outline: 1px solid CanvasText;
248
+ }
249
+
250
+ .mw-progress-fill,
251
+ .mw-progress-indeterminate::after,
252
+ .mw-progress-ring::before {
253
+ forced-color-adjust: none;
254
+ }
255
+
256
+ .mw-progress-fill {
257
+ --mw-progress-ink: HighlightText;
258
+
259
+ background: Highlight;
260
+ }
261
+
262
+ .mw-progress-indeterminate {
263
+ --mw-progress-indeterminate: Highlight;
264
+ }
265
+
266
+ .mw-progress-ring::before {
267
+ background: conic-gradient(
268
+ Highlight var(--mw-progress-value),
269
+ GrayText 0
270
+ );
271
+ }
272
+ }
153
273
  }