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
@@ -0,0 +1,323 @@
1
+ @use '../abstracts' as *;
2
+
3
+ // A laptop and a phone drawn in CSS, with a page on the glass: a screenshot
4
+ // that scrolls, or a live page in an iframe scaled to fit. For showing a site
5
+ // the way people meet it, next to the prose that sells it.
6
+
7
+ // Hardware is the same colour in both themes, like the stage behind a photo
8
+ $frame: #1b1d22;
9
+ $frame-edge: #3a3d45;
10
+ $frame-lip: #0c0d10;
11
+ // Black glass round the display, the metal only as a thin band outside it
12
+ $bezel: #060708;
13
+ $metal: #2e3138;
14
+ $metal-light: #5b606b;
15
+
16
+ // A window caught in the glass: one soft diagonal with a hard edge
17
+ $glare: linear-gradient(
18
+ 115deg,
19
+ rgb(255 255 255 / 0.1),
20
+ rgb(255 255 255 / 0.03) 36%,
21
+ transparent 36.2%
22
+ );
23
+
24
+ // The screenshot at its lower edge. Percentages in translate are the image's
25
+ // own height, cqh is the glass: the difference is the way down, and min()
26
+ // keeps a picture shorter than the glass where it is.
27
+ @mixin tour {
28
+ translate: 0 min(0px, calc(100cqh - 100%));
29
+ transition-duration: var(--mw-device-tour);
30
+ // A scroll has no ease: it moves at one speed until it stops
31
+ transition-timing-function: linear;
32
+ }
33
+
34
+ @layer mw.components {
35
+ @keyframes mw-device-nudge {
36
+ 0%,
37
+ 100% {
38
+ translate: -50% 0;
39
+ }
40
+
41
+ 50% {
42
+ translate: -50% 22%;
43
+ }
44
+ }
45
+
46
+ .mw-device {
47
+ // How long the screenshot takes to reach its end on hover or on a tap
48
+ --mw-device-tour: #{motion(7s)};
49
+ // The width a live page in an iframe is laid out at before it is scaled
50
+ --mw-device-viewport: 1280px;
51
+
52
+ position: relative;
53
+ display: block;
54
+ width: 100%;
55
+ container-type: inline-size;
56
+
57
+ @include active {
58
+ > .mw-device-screen > :is(img, video) {
59
+ @include tour;
60
+ }
61
+ }
62
+ }
63
+
64
+ // Written as a button the device tours on a tap. The frame is the control,
65
+ // so it takes the button reset and nothing of the button look.
66
+ button.mw-device {
67
+ @include tappable;
68
+ @include focus-ring;
69
+ appearance: none;
70
+ padding: 0;
71
+ border: 0;
72
+ background: none;
73
+ font: inherit;
74
+ color: inherit;
75
+ text-align: start;
76
+ cursor: pointer;
77
+
78
+ &:active {
79
+ translate: 0 1px;
80
+ }
81
+
82
+ @include active {
83
+ > .mw-device-screen::after {
84
+ opacity: 0;
85
+ }
86
+ }
87
+ }
88
+
89
+ // ----- Glass -------------------------------------------------------------
90
+
91
+ .mw-device-screen {
92
+ display: block;
93
+ position: relative;
94
+ overflow: hidden;
95
+ container-type: size;
96
+ background-color: $fixed-stage;
97
+
98
+ &::before {
99
+ content: '';
100
+ position: absolute;
101
+ inset: 0;
102
+ z-index: z-index('lift');
103
+ background: $glare;
104
+ pointer-events: none;
105
+ }
106
+
107
+ > img,
108
+ > video {
109
+ display: block;
110
+ width: 100%;
111
+ height: auto;
112
+ // The way back once the tour is over. The tour sets its own pace: a
113
+ // transition takes its timing from the state it moves into.
114
+ transition: translate var(--mw-duration-zoom) var(--mw-ease-out);
115
+ }
116
+
117
+ // A live page: laid out at the device's viewport, then scaled to the glass.
118
+ // tan(atan2()) is the one way CSS divides two lengths into a number.
119
+ > iframe {
120
+ --_scale: tan(atan2(100cqw, var(--mw-device-viewport)));
121
+
122
+ display: block;
123
+ width: var(--mw-device-viewport);
124
+ height: calc(100cqh / var(--_scale));
125
+ border: 0;
126
+ transform: scale(var(--_scale));
127
+ transform-origin: 0 0;
128
+ }
129
+ }
130
+
131
+ // The cue that the glass can be tapped: a chevron that bobs on the lower
132
+ // edge, gone while the tour runs. A loop on its own clock, like a spinner.
133
+ button.mw-device > .mw-device-screen::after {
134
+ content: '';
135
+ position: absolute;
136
+ bottom: 5cqh;
137
+ left: 50%;
138
+ z-index: z-index('lift');
139
+ width: clamp(1.5rem, 9cqw, 2.25rem);
140
+ aspect-ratio: 1;
141
+ border-radius: radius('full');
142
+ background:
143
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")
144
+ center / 55% no-repeat,
145
+ color-mix(in srgb, #000 55%, transparent);
146
+ pointer-events: none;
147
+ transition: opacity var(--mw-transition-fast);
148
+ animation-name: mw-device-nudge;
149
+ animation-duration: #{motion(1.8s)};
150
+ animation-timing-function: var(--mw-ease-in-out);
151
+ animation-iteration-count: infinite;
152
+ }
153
+
154
+ // ----- Laptop ------------------------------------------------------------
155
+
156
+ .mw-device-laptop {
157
+ // The camera in the bezel
158
+ &::before {
159
+ content: '';
160
+ position: absolute;
161
+ top: 0.5cqw;
162
+ left: 50%;
163
+ z-index: z-index('lift');
164
+ width: 0.6cqw;
165
+ aspect-ratio: 1;
166
+ translate: -50% 0;
167
+ border-radius: radius('full');
168
+ background-color: $frame-edge;
169
+ }
170
+
171
+ // The lid: a bezel around the glass, wider at the chin, sharp at the
172
+ // hinge where it meets the base. The base reaches past it on both sides.
173
+ > .mw-device-screen {
174
+ margin-inline: 6cqw;
175
+ aspect-ratio: 16 / 10;
176
+ border: 1.5cqw solid $bezel;
177
+ border-bottom-width: 2.4cqw;
178
+ border-radius: 1.6cqw 1.6cqw 0 0;
179
+ box-shadow:
180
+ 0 0 0 0.3cqw $metal-light,
181
+ var(--mw-elevation-4);
182
+ }
183
+
184
+ // The base: a bevel along the top edge, the lip in the middle where a
185
+ // thumb opens the lid, round on the way down
186
+ &::after {
187
+ content: '';
188
+ display: block;
189
+ height: 2.2cqw;
190
+ border-radius: 0 0 1.4cqw 1.4cqw / 0 0 100% 100%;
191
+ background:
192
+ linear-gradient($frame-lip, $frame-lip) 50% 0 / 14cqw 0.8cqw no-repeat,
193
+ linear-gradient(#4a4d55, $frame 30%, #202329);
194
+ box-shadow: var(--mw-elevation-4);
195
+ }
196
+
197
+ @include hover-move {
198
+ > .mw-device-screen > img {
199
+ @include tour;
200
+ }
201
+ }
202
+ }
203
+
204
+ // ----- Phone -------------------------------------------------------------
205
+
206
+ // The body is the glass's border, not padding on the phone: a length in cqw
207
+ // on the phone itself would measure the container around it, the one on
208
+ // its child measures the phone. The radius here only shapes the focus ring
209
+ // - percentages, for the same reason, against the phone's own box.
210
+ .mw-device-phone {
211
+ --mw-device-viewport: 390px;
212
+
213
+ max-width: 22rem;
214
+ margin-inline: auto;
215
+ aspect-ratio: 9 / 19.5;
216
+ border-radius: 14% / 6.46%;
217
+
218
+ > .mw-device-screen {
219
+ height: 100%;
220
+ border: 3cqw solid $bezel;
221
+ border-radius: 14cqw;
222
+ box-shadow:
223
+ 0 0 0 0.6cqw $metal,
224
+ 0 0 0 0.85cqw $metal-light,
225
+ var(--mw-elevation-4);
226
+ }
227
+
228
+ // The island shares the glare's pseudo: two round ends and the stretch between
229
+ > .mw-device-screen::before {
230
+ $end: radial-gradient(
231
+ circle closest-side,
232
+ #000 calc(100% - 0.5px),
233
+ transparent
234
+ );
235
+
236
+ background:
237
+ $end calc(50% - 9.75cqw) 3.5cqw / 7.5cqw 7.5cqw no-repeat,
238
+ $end calc(50% + 9.75cqw) 3.5cqw / 7.5cqw 7.5cqw no-repeat,
239
+ linear-gradient(#000, #000) 50% 3.5cqw / 19.5cqw 7.5cqw no-repeat,
240
+ $glare;
241
+ }
242
+
243
+ // Mute switch and the two volume keys on the left, power on the right.
244
+ // One strip each; the gaps between the keys are transparent.
245
+ &::before,
246
+ &::after {
247
+ content: '';
248
+ position: absolute;
249
+ width: 1.4cqw;
250
+ border-radius: 0.7cqw;
251
+ }
252
+
253
+ &::before {
254
+ top: 22cqw;
255
+ left: -2.1cqw;
256
+ height: 34cqw;
257
+ background: linear-gradient(
258
+ $frame-edge 0 18%,
259
+ transparent 18% 30%,
260
+ $frame-edge 30% 62%,
261
+ transparent 62% 68%,
262
+ $frame-edge 68% 100%
263
+ );
264
+ }
265
+
266
+ &::after {
267
+ top: 32cqw;
268
+ right: -2.1cqw;
269
+ height: 18cqw;
270
+ background-color: $frame-edge;
271
+ }
272
+ }
273
+
274
+ // ----- Both together -----------------------------------------------------
275
+
276
+ // The phone stands over the laptop's lower right corner and hangs a little
277
+ // below the base - the laptop's margin is the room for that overhang
278
+ .mw-devices {
279
+ position: relative;
280
+ container-type: inline-size;
281
+
282
+ > .mw-device-laptop {
283
+ margin-bottom: 4cqw;
284
+ }
285
+
286
+ > .mw-device-phone {
287
+ position: absolute;
288
+ right: 0;
289
+ bottom: 0;
290
+ z-index: z-index('lift');
291
+ width: 20cqw;
292
+ max-width: none;
293
+ }
294
+
295
+ // Too narrow for a phone anyone could read: it stands under the laptop
296
+ @container (width < 30rem) {
297
+ > .mw-device-laptop {
298
+ margin-bottom: 0;
299
+ }
300
+
301
+ // Relative and not static: the keys on its sides are placed against
302
+ // the phone, and static would hand them to the container
303
+ > .mw-device-phone {
304
+ position: relative;
305
+ inset: auto;
306
+ width: 60%;
307
+ margin: spacing('7') auto 0;
308
+ }
309
+ }
310
+ }
311
+
312
+ .mw-device {
313
+ @include motion-reduced {
314
+ --mw-device-tour: 1ms;
315
+ }
316
+ }
317
+
318
+ button.mw-device > .mw-device-screen::after {
319
+ @include motion-reduced {
320
+ animation: none;
321
+ }
322
+ }
323
+ }
@@ -33,7 +33,9 @@
33
33
  transition: transform var(--mw-duration-fast) var(--mw-ease-out);
34
34
  }
35
35
 
36
- &[open] .mw-dropdown-caret {
36
+ // aria-expanded too, for a trigger an app opens itself instead of <details>
37
+ &[open] .mw-dropdown-caret,
38
+ > [aria-expanded='true'] .mw-dropdown-caret {
37
39
  transform: rotate(180deg);
38
40
  }
39
41
  }
@@ -128,7 +130,8 @@
128
130
 
129
131
  // The current choice, for a menu that picks rather than acts
130
132
  &.mw-active,
131
- &[aria-checked='true'] {
133
+ &[aria-checked='true'],
134
+ &[aria-current='page'] {
132
135
  background: var(--mw-primary-background);
133
136
  color: var(--mw-primary-text-color);
134
137
  font-weight: font-weight('medium');
@@ -290,4 +293,42 @@
290
293
  font-size: font-size('base');
291
294
  }
292
295
  }
296
+
297
+ // A shortcut at the end of the row, the way a desktop menu shows it
298
+ .mw-dropdown-item > :is(kbd, .mw-dropdown-shortcut) {
299
+ margin-left: auto;
300
+ padding-left: spacing('4');
301
+ color: var(--mw-text-muted-color);
302
+ font-size: font-size('2xs');
303
+ white-space: nowrap;
304
+ }
305
+
306
+ // A <kbd> brings its key cap; in a menu row the text alone is enough
307
+ .mw-dropdown-item > kbd {
308
+ padding-block: 0;
309
+ padding-right: 0;
310
+ border: 0;
311
+ background: none;
312
+ box-shadow: none;
313
+ font-family: inherit;
314
+ }
315
+
316
+ // Touch has no keyboard to use them on
317
+ @include touch-context('sm') {
318
+ .mw-dropdown-item > :is(kbd, .mw-dropdown-shortcut) {
319
+ display: none;
320
+ }
321
+ }
322
+
323
+ @media (forced-colors: active) {
324
+ .mw-dropdown-item:is(
325
+ .mw-active,
326
+ [aria-checked='true'],
327
+ [aria-current='page']
328
+ ) {
329
+ forced-color-adjust: none;
330
+ background: Highlight;
331
+ color: HighlightText;
332
+ }
333
+ }
293
334
  }
@@ -70,4 +70,47 @@
70
70
  opacity: 0.7;
71
71
  }
72
72
  }
73
+
74
+ // A row for a table body or a panel: icon beside the text, the action below it
75
+ .mw-empty-state-inline {
76
+ display: grid;
77
+ grid-template-columns: auto minmax(0, 1fr);
78
+ column-gap: spacing('4');
79
+ align-items: center;
80
+ justify-items: start;
81
+ padding: spacing('4');
82
+ text-align: left;
83
+
84
+ > .mw-empty-state-icon {
85
+ grid-row: 1 / span 3;
86
+ align-self: start;
87
+ margin-bottom: 0;
88
+ font-size: font-size('2xl');
89
+ }
90
+
91
+ > .mw-empty-state-title {
92
+ margin-bottom: 0;
93
+ font-size: font-size('md');
94
+ }
95
+
96
+ > .mw-empty-state-desc {
97
+ max-width: none;
98
+ margin-bottom: 0;
99
+ font-size: font-size('sm');
100
+ }
101
+
102
+ > :not(.mw-empty-state-icon, .mw-empty-state-title, .mw-empty-state-desc) {
103
+ grid-column: 2;
104
+ margin-top: spacing('3');
105
+ }
106
+
107
+ // Without an icon the text would land in the icon's column
108
+ &:not(:has(> .mw-empty-state-icon)) {
109
+ grid-template-columns: minmax(0, 1fr);
110
+
111
+ > * {
112
+ grid-column: 1;
113
+ }
114
+ }
115
+ }
73
116
  }
@@ -322,4 +322,20 @@
322
322
  ),
323
323
  linear-gradient(to right, #046a38 0 40%, #da291c 40%);
324
324
  }
325
+
326
+ // The wedge is two half-diagonals, one per quarter of the hoist half
327
+ .mw-flag-cz {
328
+ background-image:
329
+ linear-gradient(to top right, #11457e 50%, transparent 50%),
330
+ linear-gradient(to bottom right, #11457e 50%, transparent 50%),
331
+ linear-gradient(to bottom, #fff 0 50%, #d7141a 50%);
332
+ background-size:
333
+ 50% 50%,
334
+ 50% 50%,
335
+ 100% 100%;
336
+ background-position:
337
+ left top,
338
+ left bottom,
339
+ 0 0;
340
+ }
325
341
  }
@@ -1,4 +1,5 @@
1
1
  @forward 'accordions';
2
+ @forward 'action-bar';
2
3
  @forward 'alerts';
3
4
  @forward 'announcement';
4
5
  @forward 'avatars';
@@ -9,13 +10,16 @@
9
10
  @forward 'breadcrumbs';
10
11
  @forward 'calendar';
11
12
  @forward 'cards';
13
+ @forward 'chat';
12
14
  @forward 'code';
13
15
  @forward 'coming-soon';
16
+ @forward 'compare';
14
17
  @forward 'contact';
15
18
  @forward 'content-slider';
16
19
  @forward 'date-stamp';
17
20
  @forward 'deck';
18
21
  @forward 'deck-frame';
22
+ @forward 'devices';
19
23
  @forward 'divider';
20
24
  @forward 'dropdown';
21
25
  @forward 'empty-state';
@@ -33,6 +37,7 @@
33
37
  @forward 'links';
34
38
  @forward 'lists';
35
39
  @forward 'localhost-indicator';
40
+ @forward 'marquee';
36
41
  @forward 'media';
37
42
  @forward 'meta-info';
38
43
  @forward 'modals';
@@ -49,6 +54,7 @@
49
54
  @forward 'segmented';
50
55
  @forward 'skeleton';
51
56
  @forward 'spinners';
57
+ @forward 'stat';
52
58
  @forward 'stepper';
53
59
  @forward 'stories';
54
60
  @forward 'tables';
@@ -56,7 +56,7 @@
56
56
  display: none;
57
57
  }
58
58
 
59
- @media (prefers-reduced-motion: no-preference) {
59
+ @include motion-safe {
60
60
  scroll-behavior: smooth;
61
61
  }
62
62
  }
@@ -0,0 +1,132 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @layer mw.components {
4
+ // Items twice, the copy inert: sliding by half the width makes a seamless loop
5
+ .mw-marquee {
6
+ --mw-marquee-duration: #{motion(40s)};
7
+ --mw-marquee-gap: #{spacing('9')};
8
+ --mw-marquee-fade: min(#{spacing('10')}, 12%);
9
+ --mw-marquee-logo-height: 2.25rem;
10
+
11
+ width: 100%;
12
+ min-width: 0;
13
+ overflow: hidden;
14
+ // The groups measure the strip in cqi to stay at least one screen wide
15
+ container-type: inline-size;
16
+ }
17
+
18
+ .mw-marquee-track {
19
+ display: flex;
20
+ width: 100%;
21
+ }
22
+
23
+ .mw-marquee-group {
24
+ display: flex;
25
+ flex-wrap: wrap;
26
+ align-items: center;
27
+ justify-content: center;
28
+ gap: spacing('5') var(--mw-marquee-gap);
29
+ width: 100%;
30
+
31
+ & ~ & {
32
+ display: none;
33
+ }
34
+ }
35
+
36
+ .mw-marquee-item {
37
+ display: inline-flex;
38
+ flex-shrink: 0;
39
+ align-items: center;
40
+ gap: spacing('3');
41
+ color: var(--mw-text-muted-color);
42
+ font-size: font-size('md');
43
+ font-weight: font-weight('medium');
44
+ white-space: nowrap;
45
+
46
+ > i {
47
+ color: var(--mw-primary-text-color);
48
+ }
49
+
50
+ img {
51
+ display: block;
52
+ width: auto;
53
+ height: var(--mw-marquee-logo-height);
54
+ filter: grayscale(1);
55
+ opacity: 0.75;
56
+ transition: var(--mw-transition);
57
+ }
58
+
59
+ @include hover {
60
+ img {
61
+ filter: none;
62
+ opacity: 1;
63
+ }
64
+ }
65
+ }
66
+
67
+ .mw-marquee {
68
+ @include motion-safe {
69
+ mask-image: linear-gradient(
70
+ to right,
71
+ transparent,
72
+ #000 var(--mw-marquee-fade),
73
+ #000 calc(100% - var(--mw-marquee-fade)),
74
+ transparent
75
+ );
76
+ }
77
+ }
78
+
79
+ // The fade would mask the focus ring away, and a held strip needs no fade
80
+ .mw-marquee:focus-visible {
81
+ @include motion-safe {
82
+ mask-image: none;
83
+ }
84
+ }
85
+
86
+ .mw-marquee-track {
87
+ @include motion-safe {
88
+ width: max-content;
89
+ animation-name: mw-marquee;
90
+ animation-duration: var(--mw-marquee-duration);
91
+ animation-timing-function: linear;
92
+ animation-iteration-count: infinite;
93
+ }
94
+ }
95
+
96
+ .mw-marquee-reverse .mw-marquee-track {
97
+ @include motion-safe {
98
+ animation-direction: reverse;
99
+ }
100
+ }
101
+
102
+ .mw-marquee:is(:hover, :focus-within) .mw-marquee-track {
103
+ @include motion-safe {
104
+ animation-play-state: paused;
105
+ }
106
+ }
107
+
108
+ // As wide as the strip, or a short list leaves a gap before the copy
109
+ .mw-marquee-group {
110
+ @include motion-safe {
111
+ flex-shrink: 0;
112
+ flex-wrap: nowrap;
113
+ justify-content: space-around;
114
+ width: auto;
115
+ min-width: 100cqi;
116
+ padding-inline-end: var(--mw-marquee-gap);
117
+ }
118
+ }
119
+
120
+ // Not nested as `& ~ &`: motion-safe would prefix both halves and never match
121
+ .mw-marquee-group ~ .mw-marquee-group {
122
+ @include motion-safe {
123
+ display: flex;
124
+ }
125
+ }
126
+
127
+ @keyframes mw-marquee {
128
+ to {
129
+ transform: translateX(-50%);
130
+ }
131
+ }
132
+ }