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,4 +1,5 @@
1
1
  @use 'sass:map';
2
+ @use 'sass:selector';
2
3
  @use 'sass:math';
3
4
  @use 'variables' as v;
4
5
  @use 'functions' as *;
@@ -109,6 +110,19 @@
109
110
  }
110
111
  }
111
112
 
113
+ // Out of sight, still read by a screen reader
114
+ @mixin visually-hidden {
115
+ position: absolute;
116
+ width: 1px;
117
+ height: 1px;
118
+ padding: 0;
119
+ margin: -1px;
120
+ overflow: hidden;
121
+ clip-path: inset(50%);
122
+ white-space: nowrap;
123
+ border: 0;
124
+ }
125
+
112
126
  // Without the `min-width: 0` that mw-text-truncate carries: that fixes the layout
113
127
  // around the text, and handing it to seven components would change how they size
114
128
  @mixin truncate {
@@ -127,6 +141,26 @@
127
141
  }
128
142
  }
129
143
 
144
+ // Motion the OS and `mw-motion-off` both allow; unify() merges a leading :root
145
+ @mixin motion-safe {
146
+ @media (prefers-reduced-motion: no-preference) {
147
+ @at-root #{selector.unify(&, ':root:not(.mw-motion-off) *')} {
148
+ @content;
149
+ }
150
+ }
151
+ }
152
+
153
+ // The other side of the same switch
154
+ @mixin motion-reduced {
155
+ @media (prefers-reduced-motion: reduce) {
156
+ @content;
157
+ }
158
+
159
+ @at-root #{selector.unify(&, ':root.mw-motion-off *')} {
160
+ @content;
161
+ }
162
+ }
163
+
130
164
  // A hover that *travels*. Separate from `hover`, because movement has two more
131
165
  // opt-outs than a colour change does: reduced motion, where a transform still
132
166
  // jumps to its end position in one frame however short the duration is, and the
@@ -138,7 +172,7 @@
138
172
  // and a label that lands where it belongs beats two pixels of travel.
139
173
  @mixin hover-move {
140
174
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
141
- :root:not(.mw-hover-static) &:hover:not([data-tooltip]) {
175
+ :root:not(.mw-hover-static, .mw-motion-off) &:hover:not([data-tooltip]) {
142
176
  @content;
143
177
  }
144
178
  }
@@ -200,29 +234,6 @@
200
234
  scrollbar-width: thin;
201
235
  // Firefox needs both values - a single one is invalid and drops the declaration
202
236
  scrollbar-color: #{$thumb} #{$track};
203
-
204
- &::-webkit-scrollbar {
205
- width: 8px;
206
- height: 8px;
207
- }
208
-
209
- &::-webkit-scrollbar-track {
210
- background: #{$track};
211
- border-radius: 4px;
212
- }
213
-
214
- &::-webkit-scrollbar-thumb {
215
- background: #{$thumb};
216
- border-radius: 4px;
217
-
218
- &:hover {
219
- background: var(--mw-primary-text-color);
220
- }
221
- }
222
-
223
- &::-webkit-scrollbar-corner {
224
- background: #{$track};
225
- }
226
237
  }
227
238
 
228
239
  // A hairline of light along the top edge and a shadow underneath, as if the light
@@ -3,6 +3,9 @@
3
3
 
4
4
  $mw-theme-mode: 'switchable' !default; // 'dark', 'light', or 'switchable'
5
5
 
6
+ // Opt-in, because a page only crossfades into one that carries the rule too
7
+ $mw-page-transitions: false !default;
8
+
6
9
  $mw-hero-image: url('https://images.unsplash.com/photo-1591638462868-19cfad9c49d1?q=80&w=1300&auto=format&fit=crop') !default;
7
10
 
8
11
  // A second, flatter layer over the image - see mw-parallax-pattern. Fine lines
@@ -292,6 +295,13 @@ $base-colors: (
292
295
  'section-alternate-pattern': fade('gray-color', 30%),
293
296
  'form-elements-background': $form-elements-background,
294
297
  'form-elements-color': shade('light-text-color', 18%),
298
+ // Opaque and alike in both themes; readonly keeps full ink, disabled mutes it
299
+ 'form-elements-readonly-background': string.unquote(
300
+ 'color-mix(in oklab, var(--mw-form-elements-background) 70%, var(--mw-gray-color))'
301
+ ),
302
+ 'form-elements-disabled-background': string.unquote(
303
+ 'color-mix(in oklab, var(--mw-form-elements-background) 70%, var(--mw-gray-color))'
304
+ ),
295
305
  );
296
306
 
297
307
  // Status colors. Every status offers the same four tones.
@@ -525,6 +535,8 @@ $z-index: (
525
535
  'lift': 3,
526
536
  'card': 10,
527
537
  'menu': 30,
538
+ // Over the page and its menus, under the open navigation's scrim
539
+ 'action-bar': 98,
528
540
  // The scrim sits under the bar, the panel hangs off it, the logo clears the
529
541
  // panel and the burger stays on top of all of them while it is the X.
530
542
  'header-scrim': 99,
@@ -540,6 +552,9 @@ $z-index: (
540
552
  'occasion': 1000,
541
553
  );
542
554
 
555
+ // Native, ARIA, and Angular's classes for a validator that lives in TypeScript
556
+ $invalid-states: ':user-invalid, [aria-invalid="true"], .ng-invalid.ng-touched';
557
+
543
558
  // Native select arrow, drawn as a background layer because `appearance: none`
544
559
  // removes the OS one. Shared, because any control that adds a second
545
560
  // background layer (see `.mw-prefilled`) has to restate the whole list.
@@ -107,12 +107,13 @@
107
107
 
108
108
  // The only way the chrome the browser draws itself follows the theme:
109
109
  // scrollbars, the select popup, the date picker panel, number spinners.
110
- // Under `switchable` this is also what light-dark() reads, so the pair below
111
- // does double duty and the @supports block widens it to `light dark`.
110
+ // Under `switchable` it is also what every light-dark() token reads.
112
111
  @if $mw-theme-mode == 'light' {
113
112
  color-scheme: light;
114
- } @else {
113
+ } @else if $mw-theme-mode == 'dark' {
115
114
  color-scheme: dark;
115
+ } @else {
116
+ color-scheme: light dark;
116
117
  }
117
118
  // Native controls the framework does not paint over take the brand, not OS blue
118
119
  accent-color: var(--mw-primary-color);
@@ -202,68 +203,44 @@
202
203
  }
203
204
 
204
205
  @if $mw-theme-mode == 'switchable' {
205
- .mw-theme-light {
206
- // Light theme variables for switching
207
- @each $name, $value in $light-theme-colors {
208
- --mw-#{$name}: var(--mw-light-#{$name});
206
+ // A custom property holding light-dark() is resolved where it is *used*,
207
+ // against that element's color-scheme - so one declaration on :root serves
208
+ // both themes, and a class further down switches every token by changing
209
+ // nothing but the scheme. The page also follows the OS before any script.
210
+ :root {
211
+ @each $name, $value in $_theme-colors {
212
+ --mw-#{$name}: light-dark(
213
+ var(--mw-light-#{$name}),
214
+ var(--mw-dark-#{$name})
215
+ );
209
216
  }
210
-
211
- color-scheme: light;
212
217
  }
213
218
 
214
- // The same two themes, declared once instead of twice. A custom property
215
- // holding light-dark() is resolved where it is *used*, against that
216
- // element's color-scheme - so one declaration on :root serves both, and a
217
- // class further down switches every token by changing nothing but the
218
- // scheme. The page then also follows the OS out of the box, which the
219
- // class-only route could never do: before the script had run, a machine set
220
- // to light still got the dark theme for a frame.
221
- //
222
- // The block above stays as the fallback for Chrome 119-122 and Safari
223
- // 16.4-17.4, which are inside the declared browserslist but predate
224
- // light-dark(). Drop it, and the two class rules with it, once the support
225
- // floor moves past Chrome 123 / Safari 17.5.
226
- @supports (color: light-dark(#000, #fff)) {
227
- :root,
228
- .mw-theme-light {
229
- @each $name, $value in $_theme-colors {
230
- --mw-#{$name}: light-dark(
231
- var(--mw-light-#{$name}),
232
- var(--mw-dark-#{$name})
233
- );
234
- }
235
- }
236
-
219
+ // Not a color, so it cannot ride along above - it follows the OS here and
220
+ // is overridden by whichever class the toggle puts on <html>
221
+ @media (prefers-color-scheme: light) {
237
222
  :root {
238
- color-scheme: light dark;
239
- }
240
-
241
- // Not a color, so it cannot ride along above - it follows the OS here and
242
- // is overridden by whichever class the toggle puts on the body
243
- @media (prefers-color-scheme: light) {
244
- :root {
245
- @each $name in $theme-non-colors {
246
- --mw-#{$name}: var(--mw-light-#{$name});
247
- }
223
+ @each $name in $theme-non-colors {
224
+ --mw-#{$name}: var(--mw-light-#{$name});
248
225
  }
249
226
  }
227
+ }
250
228
 
251
- .mw-theme-light {
252
- color-scheme: light;
229
+ .mw-theme-light {
230
+ color-scheme: light;
253
231
 
254
- @each $name in $theme-non-colors {
255
- --mw-#{$name}: var(--mw-light-#{$name});
256
- }
232
+ @each $name in $theme-non-colors {
233
+ --mw-#{$name}: var(--mw-light-#{$name});
257
234
  }
235
+ }
258
236
 
259
- // Only reachable once the OS says light: the explicit counterpart to the
260
- // class above, so a reader who wants dark on a light machine gets it
261
- .mw-theme-dark {
262
- color-scheme: dark;
237
+ // Only reachable once the OS says light: the explicit counterpart to the
238
+ // class above, so a reader who wants dark on a light machine gets it
239
+ .mw-theme-dark {
240
+ color-scheme: dark;
263
241
 
264
- @each $name in $theme-non-colors {
265
- --mw-#{$name}: var(--mw-dark-#{$name});
266
- }
242
+ @each $name in $theme-non-colors {
243
+ --mw-#{$name}: var(--mw-dark-#{$name});
267
244
  }
268
245
  }
269
246
  }
@@ -294,23 +271,31 @@
294
271
  // The duration tokens cover everything built on them, the blanket rule below
295
272
  // the few animations that name their own timing. 1ms and not 0: a zero-length
296
273
  // transition never fires transitionend, and a script waiting for it would hang.
274
+ @mixin _still-tokens {
275
+ --mw-duration-instant: 1ms;
276
+ --mw-duration-fast: 1ms;
277
+ --mw-duration-base: 1ms;
278
+ --mw-duration-slow: 1ms;
279
+ --mw-duration-slower: 1ms;
280
+ }
281
+
282
+ @mixin _still-everything {
283
+ animation-duration: 1ms !important;
284
+ animation-delay: 0ms !important;
285
+ animation-iteration-count: 1 !important;
286
+ transition-duration: 1ms !important;
287
+ transition-delay: 0ms !important;
288
+ }
289
+
297
290
  @media (prefers-reduced-motion: reduce) {
298
291
  :root {
299
- --mw-duration-instant: 1ms;
300
- --mw-duration-fast: 1ms;
301
- --mw-duration-base: 1ms;
302
- --mw-duration-slow: 1ms;
303
- --mw-duration-slower: 1ms;
292
+ @include _still-tokens;
304
293
  }
305
294
 
306
295
  *,
307
296
  *::before,
308
297
  *::after {
309
- animation-duration: 1ms !important;
310
- animation-delay: 0ms !important;
311
- animation-iteration-count: 1 !important;
312
- transition-duration: 1ms !important;
313
- transition-delay: 0ms !important;
298
+ @include _still-everything;
314
299
  }
315
300
 
316
301
  html {
@@ -318,6 +303,18 @@
318
303
  }
319
304
  }
320
305
 
306
+ // The same switch, thrown by the page instead of the OS
307
+ :root.mw-motion-off {
308
+ @include _still-tokens;
309
+ scroll-behavior: auto;
310
+
311
+ *,
312
+ *::before,
313
+ *::after {
314
+ @include _still-everything;
315
+ }
316
+ }
317
+
321
318
  // Without this a theme flip starts ~5900 concurrent transitions at once
322
319
  .mw-theme-switching,
323
320
  .mw-theme-switching *,
@@ -326,3 +323,49 @@
326
323
  transition: none !important;
327
324
  }
328
325
  }
326
+
327
+ @layer mw.base {
328
+ // The dark-*/light-* sources: the theme aliases read them, so both themes follow
329
+ @media (prefers-contrast: more) {
330
+ :root {
331
+ --mw-dark-border: #{fade('dark-text-color', 55%)};
332
+ --mw-light-border: #{fade('light-text-color', 55%)};
333
+ --mw-dark-border-accent: var(--mw-dark-primary-text-color);
334
+ --mw-light-border-accent: var(--mw-light-primary-text-color);
335
+ --mw-light-corner-accent: var(--mw-light-primary-text-color);
336
+ --mw-dark-text-muted-color: #{muted(0.82)};
337
+ --mw-light-text-muted-color: #{muted(0.4)};
338
+ --mw-focus-ring-width: 3px;
339
+
340
+ // 4.5:1 on a card instead of the 3:1 the default ink is tuned to
341
+ @each $name
342
+ in ('primary', 'secondary', 'success', 'warning', 'danger', 'info')
343
+ {
344
+ --mw-dark-#{$name}-text-color: #{ink-on-dark('#{$name}-color', 0.66)};
345
+ --mw-light-#{$name}-text-color: oklch(
346
+ from var(--mw-#{$name}-color) min(l, 0.45) c h
347
+ );
348
+ }
349
+ }
350
+ }
351
+
352
+ // Text on a see-through fill only - the hero's backdrop-filter is brightness
353
+ @media (prefers-reduced-transparency: reduce) {
354
+ .mw-announcement,
355
+ .mw-hero-badge,
356
+ .mw-container:has(.mw-hero) .mw-btn-outline:not(:hover) {
357
+ backdrop-filter: none !important;
358
+ }
359
+
360
+ .mw-announcement {
361
+ background: var(--mw-primary-color) !important;
362
+ }
363
+
364
+ .mw-hero-badge,
365
+ .mw-container:has(.mw-hero) .mw-btn-outline:not(:hover) {
366
+ background: oklch(
367
+ from var(--mw-hero-text-color) calc(1 - l) 0 h
368
+ ) !important;
369
+ }
370
+ }
371
+ }
@@ -1,3 +1,4 @@
1
1
  @forward 'reset';
2
2
  @forward 'base';
3
3
  @forward 'typography';
4
+ @forward 'page-transitions';
@@ -0,0 +1,29 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @if $mw-page-transitions {
4
+ @media (prefers-reduced-motion: no-preference) {
5
+ @view-transition {
6
+ navigation: auto;
7
+ }
8
+ }
9
+
10
+ @layer mw.base {
11
+ ::view-transition-old(root),
12
+ ::view-transition-new(root) {
13
+ animation-duration: var(--mw-duration-slow);
14
+ animation-timing-function: var(--mw-ease-in-out);
15
+ }
16
+
17
+ // mw-motion-off cannot reach the at-rule above, so the pages swap unfaded
18
+ :root.mw-motion-off::view-transition-group(*),
19
+ :root.mw-motion-off::view-transition-old(*),
20
+ :root.mw-motion-off::view-transition-new(*) {
21
+ animation-name: none;
22
+ }
23
+
24
+ // Its own group, so the bar stands still while the page under it fades
25
+ .mw-header {
26
+ view-transition-name: mw-header;
27
+ }
28
+ }
29
+ }
@@ -31,14 +31,6 @@
31
31
  overflow-wrap: break-word;
32
32
  -webkit-font-smoothing: antialiased;
33
33
  -moz-osx-font-smoothing: grayscale;
34
-
35
- // `dvh` and not `vh`: on iOS `100vh` is the tall viewport behind the
36
- // address bar, so the locked body stays taller than the screen and keeps a
37
- // strip of rubber-band scroll under the modal.
38
- &:has(.mw-modal-open) {
39
- overflow: hidden;
40
- height: 100dvh;
41
- }
42
34
  }
43
35
 
44
36
  // The UA rule behind the `hidden` attribute is `[hidden] { display: none }`, and
@@ -1,6 +1,27 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
3
  @layer mw.components {
4
+ // aria-expanded on the header opens it too, for markup an application renders
5
+ @mixin _open-header {
6
+ @include active {
7
+ @content;
8
+ }
9
+
10
+ &[aria-expanded='true'] {
11
+ @content;
12
+ }
13
+ }
14
+
15
+ @mixin _open-content {
16
+ @include active {
17
+ @content;
18
+ }
19
+
20
+ @at-root .mw-accordion-header[aria-expanded='true'] + #{&} {
21
+ @content;
22
+ }
23
+ }
24
+
4
25
  .mw-accordion {
5
26
  border: 1px solid var(--mw-border);
6
27
  @include surface;
@@ -73,7 +94,7 @@
73
94
  transform: rotate(0deg);
74
95
  }
75
96
 
76
- @include active() {
97
+ @include _open-header {
77
98
  background: var(--mw-primary-color);
78
99
  color: var(--mw-primary-accent-text-color);
79
100
 
@@ -113,7 +134,7 @@
113
134
  }
114
135
  }
115
136
 
116
- @include active() {
137
+ @include _open-content {
117
138
  grid-template-rows: 1fr;
118
139
  max-height: 500px;
119
140
  overflow-y: auto;
@@ -123,24 +144,8 @@
123
144
  border-top-width: 1px;
124
145
  }
125
146
 
126
- // Both spellings: the standard pair is what Firefox reads, the webkit
127
- // pseudos are for Safari below 18.2.
128
147
  scrollbar-width: thin;
129
148
  scrollbar-color: var(--mw-primary-color-hover) var(--mw-card-background);
130
-
131
- &::-webkit-scrollbar {
132
- width: 6px;
133
- }
134
-
135
- &::-webkit-scrollbar-track {
136
- background: var(--mw-card-background);
137
- border-radius: radius('sm');
138
- }
139
-
140
- &::-webkit-scrollbar-thumb {
141
- background: var(--mw-primary-color-hover);
142
- border-radius: radius('sm');
143
- }
144
149
  }
145
150
  }
146
151
  }
@@ -0,0 +1,143 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @layer mw.components {
4
+ :root {
5
+ --mw-action-bar-height: 3.75rem;
6
+ }
7
+
8
+ // Reserved while the bar is up, so focus never lands under it (WCAG 2.4.11)
9
+ :root:has(.mw-action-bar:not(.mw-action-bar-embedded)) {
10
+ --mw-action-bar-reserve: calc(
11
+ var(--mw-action-bar-height) + env(safe-area-inset-bottom, 0px)
12
+ );
13
+
14
+ scroll-padding-bottom: var(--mw-action-bar-reserve);
15
+
16
+ > body {
17
+ padding-bottom: var(--mw-action-bar-reserve);
18
+ }
19
+ }
20
+
21
+ // Chrome like the header, so it takes the header's colours in both themes
22
+ .mw-action-bar {
23
+ --mw-focus-ring-color: var(--mw-header-navbar-list-active-color);
24
+
25
+ position: fixed;
26
+ inset: auto 0 0;
27
+ z-index: z-index('action-bar');
28
+ display: grid;
29
+ grid-auto-flow: column;
30
+ grid-auto-columns: minmax(0, 1fr);
31
+ align-items: stretch;
32
+ gap: spacing('2');
33
+ min-height: calc(
34
+ var(--mw-action-bar-height) + env(safe-area-inset-bottom, 0px)
35
+ );
36
+ padding: spacing('2') max(#{spacing('3')}, env(safe-area-inset-right, 0px))
37
+ calc(#{spacing('2')} + env(safe-area-inset-bottom, 0px))
38
+ max(#{spacing('3')}, env(safe-area-inset-left, 0px));
39
+ background: var(--mw-header-background);
40
+ border-top: 1px solid var(--mw-header-border);
41
+ box-shadow: 0 -6px 18px var(--mw-shadow-far);
42
+
43
+ &-embedded {
44
+ position: absolute;
45
+ }
46
+
47
+ &-item {
48
+ display: flex;
49
+ flex-direction: column;
50
+ align-items: center;
51
+ justify-content: center;
52
+ gap: spacing('1');
53
+ min-width: 0;
54
+ min-height: 2.75rem;
55
+ padding: spacing('1') spacing('2');
56
+ border: 0;
57
+ border-radius: radius('md');
58
+ background: transparent;
59
+ color: var(--mw-header-navbar-list-color);
60
+ font: inherit;
61
+ font-size: font-size('xs');
62
+ font-weight: font-weight('medium');
63
+ line-height: 1.15;
64
+ text-align: center;
65
+ text-decoration: none;
66
+ cursor: pointer;
67
+ transition: var(--mw-transition-fast);
68
+
69
+ @include tappable;
70
+ @include focus-ring;
71
+
72
+ > i {
73
+ font-size: font-size('lg');
74
+ }
75
+
76
+ > :is(svg, img) {
77
+ flex-shrink: 0;
78
+ width: 1.35rem;
79
+ height: 1.35rem;
80
+ }
81
+
82
+ > span {
83
+ max-width: 100%;
84
+ @include truncate;
85
+ }
86
+
87
+ &:hover {
88
+ color: var(--mw-header-text-color);
89
+ background: color-mix(
90
+ in srgb,
91
+ var(--mw-header-text-color) 10%,
92
+ transparent
93
+ );
94
+ }
95
+
96
+ @include motion-safe {
97
+ &:active {
98
+ transform: scale(0.96);
99
+ transition-duration: var(--mw-duration-instant);
100
+ }
101
+ }
102
+ }
103
+
104
+ &-primary {
105
+ background: var(--mw-primary-color);
106
+ color: var(--mw-primary-accent-text-color);
107
+ box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 15%, transparent);
108
+
109
+ &:hover {
110
+ background: var(--mw-primary-color-hover);
111
+ color: var(--mw-primary-accent-text-color);
112
+ }
113
+ }
114
+ }
115
+
116
+ @include media-up('md') {
117
+ :root:has(
118
+ .mw-action-bar:not(.mw-action-bar-embedded, .mw-action-bar-always)
119
+ ) {
120
+ --mw-action-bar-reserve: 0px;
121
+ }
122
+
123
+ .mw-action-bar:not(.mw-action-bar-always, .mw-action-bar-embedded) {
124
+ display: none;
125
+ }
126
+
127
+ // Stretched across a desktop the four targets would sit a screen apart
128
+ .mw-action-bar {
129
+ justify-content: center;
130
+ grid-auto-columns: minmax(0, 10rem);
131
+ }
132
+ }
133
+
134
+ @media print {
135
+ :root:has(.mw-action-bar:not(.mw-action-bar-embedded)) {
136
+ --mw-action-bar-reserve: 0px;
137
+ }
138
+
139
+ .mw-action-bar {
140
+ display: none;
141
+ }
142
+ }
143
+ }
@@ -38,7 +38,6 @@
38
38
  margin: 0;
39
39
  line-height: 1.5;
40
40
  font-size: font-size('sm');
41
- opacity: 0.9;
42
41
 
43
42
  & + p {
44
43
  margin-top: spacing('2');
@@ -87,4 +86,17 @@
87
86
  }
88
87
  }
89
88
  }
89
+
90
+ // Undo, Retry, a link to the details - under the message, never beside it
91
+ .mw-alert-actions {
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ gap: spacing('2');
95
+ margin-top: spacing('3');
96
+
97
+ // On a status tint the brand ink can fail; the alert's own text never does
98
+ :is(.mw-btn-link, .mw-btn-ghost) {
99
+ color: inherit;
100
+ }
101
+ }
90
102
  }
@@ -272,4 +272,21 @@
272
272
  background: #{$col};
273
273
  }
274
274
  }
275
+
276
+ // The rest of a group as a count - "+4" after the faces that fit
277
+ .mw-avatar-more {
278
+ align-items: center;
279
+ justify-content: center;
280
+ background: var(--mw-surface-muted);
281
+ color: var(--mw-text-color);
282
+ font-weight: font-weight('bold');
283
+ font-variant-numeric: tabular-nums;
284
+ line-height: 1;
285
+ user-select: none;
286
+
287
+ // No photo, so neither the corner arc nor the sweep has anything to mark
288
+ &::before {
289
+ content: none;
290
+ }
291
+ }
275
292
  }
@@ -153,4 +153,16 @@
153
153
  .mw-badge-status-muted .mw-badge-dot {
154
154
  background: var(--mw-gray-color);
155
155
  }
156
+
157
+ // Forced colours drop every fill: a border keeps the badge, the dot its ink
158
+ @media (forced-colors: active) {
159
+ .mw-badge {
160
+ border: 1px solid CanvasText;
161
+ }
162
+
163
+ .mw-badge-dot {
164
+ forced-color-adjust: none;
165
+ background: CanvasText;
166
+ }
167
+ }
156
168
  }