maverick-wave 4.28.0 → 5.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 (138) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/mw-maverick-wave/SKILL.md +39 -24
  3. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +16 -4
  4. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +13 -4
  5. package/.claude/skills/mw-maverick-wave/references/components.md +42 -5
  6. package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
  7. package/.claude/skills/mw-maverick-wave/references/javascript.md +17 -4
  8. package/.claude/skills/mw-maverick-wave/references/layout.md +53 -19
  9. package/.claude/skills/mw-maverick-wave/references/theming.md +38 -1
  10. package/.impeccable/config.local.json +5 -0
  11. package/.impeccable/hook.cache.json +1 -0
  12. package/CHANGELOG.md +11 -0
  13. package/CLAUDE.md +5 -5
  14. package/README.md +48 -4
  15. package/index.html +19 -38
  16. package/maverick-wave.min.css +14 -12
  17. package/maverick-wave.min.js +1 -1
  18. package/package.json +2 -2
  19. package/scripts/verify.js +27 -2
  20. package/src/js/main.js +223 -83
  21. package/src/partials/accordions-container.html +6 -10
  22. package/src/partials/alerts-container.html +6 -6
  23. package/src/partials/announcement-container.html +5 -5
  24. package/src/partials/avatars-container.html +85 -17
  25. package/src/partials/badges-container.html +6 -6
  26. package/src/partials/blog-posts-container.html +11 -12
  27. package/src/partials/buttons-container.html +3 -3
  28. package/src/partials/cards-container.html +26 -9
  29. package/src/partials/code-container.html +5 -5
  30. package/src/partials/content-slider-container.html +5 -4
  31. package/src/partials/divider-container.html +7 -7
  32. package/src/partials/dropdown-container.html +2 -2
  33. package/src/partials/empty-state-container.html +5 -5
  34. package/src/partials/footer-container.html +21 -18
  35. package/src/partials/form-elements-container.html +14 -14
  36. package/src/partials/gallery-container.html +16 -8
  37. package/src/partials/get-started-container.html +4 -8
  38. package/src/partials/header-container.html +18 -7
  39. package/src/partials/home-container.html +10 -20
  40. package/src/partials/input-group-container.html +11 -2
  41. package/src/partials/kbd-container.html +1 -1
  42. package/src/partials/login-container.html +5 -1
  43. package/src/partials/media-container.html +2 -2
  44. package/src/partials/modals-container.html +22 -32
  45. package/src/partials/pricing-container.html +9 -9
  46. package/src/partials/skeleton-container.html +6 -6
  47. package/src/partials/stepper-container.html +2 -2
  48. package/src/partials/tables-container.html +12 -12
  49. package/src/partials/tabs-container.html +17 -27
  50. package/src/partials/tags-container.html +23 -17
  51. package/src/partials/techstack-bucket-container.html +13 -0
  52. package/src/partials/tiles-container.html +26 -9
  53. package/src/partials/typography-container.html +7 -6
  54. package/src/partials/utilities-container.html +35 -15
  55. package/src/scss/_layers.scss +10 -0
  56. package/src/scss/abstracts/_index.scss +6 -0
  57. package/src/scss/abstracts/_mixins.scss +61 -153
  58. package/src/scss/abstracts/_variables.scss +123 -319
  59. package/src/scss/base/_base.scss +170 -190
  60. package/src/scss/base/_reset.scss +106 -104
  61. package/src/scss/base/_typography.scss +153 -151
  62. package/src/scss/components/_accordions.scss +97 -97
  63. package/src/scss/components/_alerts.scss +71 -70
  64. package/src/scss/components/_announcement.scss +71 -70
  65. package/src/scss/components/_avatars.scss +130 -142
  66. package/src/scss/components/_badge.scss +131 -132
  67. package/src/scss/components/_blog-post.scss +223 -220
  68. package/src/scss/components/_breadcrumbs.scss +93 -92
  69. package/src/scss/components/_button-bar.scss +102 -110
  70. package/src/scss/components/_buttons.scss +319 -324
  71. package/src/scss/components/_calendar.scss +251 -252
  72. package/src/scss/components/_cards.scss +359 -386
  73. package/src/scss/components/_code.scss +190 -194
  74. package/src/scss/components/_coming-soon.scss +77 -75
  75. package/src/scss/components/_content-slider.scss +90 -89
  76. package/src/scss/components/_divider.scss +55 -53
  77. package/src/scss/components/_dropdown.scss +179 -185
  78. package/src/scss/components/_empty-state.scss +57 -57
  79. package/src/scss/components/_flag.scss +294 -278
  80. package/src/scss/components/_gallery.scss +131 -133
  81. package/src/scss/components/_info.scss +151 -167
  82. package/src/scss/components/_kanban.scss +332 -341
  83. package/src/scss/components/_kbd.scss +40 -38
  84. package/src/scss/components/_lang-switch.scss +89 -97
  85. package/src/scss/components/_links.scss +21 -28
  86. package/src/scss/components/_lists.scss +510 -514
  87. package/src/scss/components/_localhost-indicator.scss +26 -24
  88. package/src/scss/components/_media.scss +22 -20
  89. package/src/scss/components/_meta-info.scss +35 -33
  90. package/src/scss/components/_modals.scss +277 -186
  91. package/src/scss/components/_pagination.scss +81 -87
  92. package/src/scss/components/_panels.scss +99 -97
  93. package/src/scss/components/_pricing.scss +183 -241
  94. package/src/scss/components/_progress.scss +112 -89
  95. package/src/scss/components/_prose.scss +35 -33
  96. package/src/scss/components/_ratings.scss +40 -38
  97. package/src/scss/components/_segmented.scss +82 -84
  98. package/src/scss/components/_skeleton.scss +66 -72
  99. package/src/scss/components/_spinners.scss +155 -159
  100. package/src/scss/components/_stepper.scss +116 -116
  101. package/src/scss/components/_tables.scss +155 -148
  102. package/src/scss/components/_tabs.scss +232 -141
  103. package/src/scss/components/_tags.scss +109 -107
  104. package/src/scss/components/_techstack-bucket.scss +123 -122
  105. package/src/scss/components/_testimonial.scss +72 -70
  106. package/src/scss/components/_theme-toggle.scss +53 -51
  107. package/src/scss/components/_tiles.scss +174 -174
  108. package/src/scss/components/_timelines.scss +282 -280
  109. package/src/scss/components/_toasts.scss +66 -72
  110. package/src/scss/form-elements/_checkbox.scss +117 -132
  111. package/src/scss/form-elements/_form.scss +149 -115
  112. package/src/scss/form-elements/_input-group.scss +99 -103
  113. package/src/scss/form-elements/_input.scss +74 -74
  114. package/src/scss/form-elements/_login.scss +48 -48
  115. package/src/scss/form-elements/_prefilled.scss +53 -51
  116. package/src/scss/form-elements/_radio.scss +95 -107
  117. package/src/scss/form-elements/_select.scss +42 -42
  118. package/src/scss/form-elements/_slider.scss +117 -132
  119. package/src/scss/form-elements/_textarea.scss +68 -54
  120. package/src/scss/form-elements/_toggle.scss +127 -140
  121. package/src/scss/layout/_footer.scss +172 -171
  122. package/src/scss/layout/_grid.scss +268 -302
  123. package/src/scss/layout/_header.scss +426 -427
  124. package/src/scss/layout/_home.scss +28 -27
  125. package/src/scss/layout/_main.scss +275 -238
  126. package/src/scss/layout/_page-header.scss +38 -36
  127. package/src/scss/layout/_section.scss +150 -124
  128. package/src/scss/main.scss +1 -1
  129. package/src/scss/utilities/_accessibility.scss +46 -45
  130. package/src/scss/utilities/_aspect.scss +22 -20
  131. package/src/scss/utilities/_corner.scss +23 -5
  132. package/src/scss/utilities/_display.scss +76 -70
  133. package/src/scss/utilities/_elevation.scss +13 -11
  134. package/src/scss/utilities/_flex.scss +83 -85
  135. package/src/scss/utilities/_reveal.scss +40 -63
  136. package/src/scss/utilities/_spacing.scss +63 -64
  137. package/src/scss/utilities/_text.scss +139 -141
  138. package/src/scss/utilities/_touch-targets.scss +130 -147
@@ -1,29 +1,31 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- // Localhost indicator - a pulsating thin line at the very top of the header
4
- .mw-localhost-indicator-pulse {
5
- position: absolute;
6
- top: 2px;
7
- left: spacing('0');
8
- width: 100%;
9
- height: 4px;
10
- background: var(--mw-warning-color);
11
- z-index: z-index('modal')-10;
12
- animation: pulse-indicator 4s infinite;
13
- }
14
-
15
- // Keyframes for the pulsing indicator
16
- @keyframes pulse-indicator {
17
- 0% {
18
- opacity: 0.5;
19
- box-shadow: 0 0 3px var(--mw-warning-color);
3
+ @layer mw.components {
4
+ // Localhost indicator - a pulsating thin line at the very top of the header
5
+ .mw-localhost-indicator-pulse {
6
+ position: absolute;
7
+ top: 2px;
8
+ left: spacing('0');
9
+ width: 100%;
10
+ height: 4px;
11
+ background: var(--mw-warning-color);
12
+ z-index: z-index('modal')-10;
13
+ animation: pulse-indicator 4s infinite;
20
14
  }
21
- 25% {
22
- opacity: 0.9;
23
- box-shadow: 0 6px 13px var(--mw-warning-color);
24
- }
25
- 100% {
26
- opacity: 0.5;
27
- box-shadow: 0 0 3px var(--mw-warning-color);
15
+
16
+ // Keyframes for the pulsing indicator
17
+ @keyframes pulse-indicator {
18
+ 0% {
19
+ opacity: 0.5;
20
+ box-shadow: 0 0 3px var(--mw-warning-color);
21
+ }
22
+ 25% {
23
+ opacity: 0.9;
24
+ box-shadow: 0 6px 13px var(--mw-warning-color);
25
+ }
26
+ 100% {
27
+ opacity: 0.5;
28
+ box-shadow: 0 0 3px var(--mw-warning-color);
29
+ }
28
30
  }
29
31
  }
@@ -1,26 +1,28 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- // Self-hosted audio and video. The framework ships no player - the native
4
- // controls are the player, and `accent-color` on :root already brands them.
5
- // This only makes the element sit right: full width, block, and a video gets
6
- // the surface silhouette so it reads as part of the family.
7
- .mw-media {
8
- audio,
9
- video {
10
- display: block;
11
- width: 100%;
12
- }
3
+ @layer mw.components {
4
+ // Self-hosted audio and video. The framework ships no player - the native
5
+ // controls are the player, and `accent-color` on :root already brands them.
6
+ // This only makes the element sit right: full width, block, and a video gets
7
+ // the surface silhouette so it reads as part of the family.
8
+ .mw-media {
9
+ audio,
10
+ video {
11
+ display: block;
12
+ width: 100%;
13
+ }
13
14
 
14
- video {
15
- height: auto;
16
- border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
17
- box-shadow: var(--mw-elevation-2);
15
+ video {
16
+ height: auto;
17
+ border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
18
+ box-shadow: var(--mw-elevation-2);
19
+ }
18
20
  }
19
- }
20
21
 
21
- .mw-media-caption {
22
- margin-top: spacing('2');
23
- font-size: font-size('sm');
24
- color: var(--mw-text-muted-color);
25
- text-align: center;
22
+ .mw-media-caption {
23
+ margin-top: spacing('2');
24
+ font-size: font-size('sm');
25
+ color: var(--mw-text-muted-color);
26
+ text-align: center;
27
+ }
26
28
  }
@@ -1,44 +1,46 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- .mw-meta {
4
- &-header {
5
- padding: spacing('3');
6
- border-bottom: 1px solid var(--mw-border);
7
- margin-bottom: spacing('5');
8
- display: flex;
9
- flex-wrap: wrap;
10
- align-items: center;
11
- justify-content: flex-end;
12
- row-gap: spacing('1');
3
+ @layer mw.components {
4
+ .mw-meta {
5
+ &-header {
6
+ padding: spacing('3');
7
+ border-bottom: 1px solid var(--mw-border);
8
+ margin-bottom: spacing('5');
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ align-items: center;
12
+ justify-content: flex-end;
13
+ row-gap: spacing('1');
13
14
 
14
- @include media-down('sm') {
15
- justify-content: flex-start;
16
- column-gap: spacing('6');
17
- row-gap: spacing('2');
15
+ @include media-down('sm') {
16
+ justify-content: flex-start;
17
+ column-gap: spacing('6');
18
+ row-gap: spacing('2');
19
+ }
18
20
  }
19
- }
20
21
 
21
- &-item {
22
- display: inline-flex;
23
- align-items: center;
24
- row-gap: spacing('1');
25
- gap: spacing('2');
26
- &:not(:first-child) {
27
- margin-left: spacing('9');
28
-
29
- @include media-down('sm') {
22
+ &-item {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ row-gap: spacing('1');
26
+ gap: spacing('2');
27
+ &:not(:first-child) {
30
28
  margin-left: spacing('0');
29
+
30
+ @include media-up('sm') {
31
+ margin-left: spacing('9');
32
+ }
31
33
  }
32
- }
33
- font-size: font-size('md');
34
+ font-size: font-size('md');
34
35
 
35
- i {
36
- font-size: font-size('lg');
37
- color: var(--mw-primary-text-color);
38
- flex-shrink: 0;
39
- }
40
- span {
41
- color: var(--mw-text-color);
36
+ i {
37
+ font-size: font-size('lg');
38
+ color: var(--mw-primary-text-color);
39
+ flex-shrink: 0;
40
+ }
41
+ span {
42
+ color: var(--mw-text-color);
43
+ }
42
44
  }
43
45
  }
44
46
  }
@@ -1,217 +1,308 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- // Modal overlay - hidden by default
4
- .mw-modal-overlay {
5
- position: fixed;
6
- top: 0;
7
- left: 0;
8
- right: 0;
9
- bottom: 0;
10
- background: var(--mw-overlay-background);
11
- backdrop-filter: blur(3px);
12
- z-index: z-index('modal')-10;
13
- display: none;
14
- align-items: center;
15
- justify-content: center;
16
- padding: spacing('7');
17
- opacity: 0;
18
- transition: opacity var(--mw-duration-base) var(--mw-ease-out);
19
-
20
- // Show modal when class is added
21
- &.mw-modal-open {
22
- display: flex;
23
- opacity: 1;
24
- }
25
- }
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
+ transition: opacity var(--mw-duration-base) var(--mw-ease-out);
26
20
 
27
- // Modal container
28
- .mw-modal {
29
- background: var(--mw-card-background);
30
- @include surface(0);
31
- box-shadow: var(--mw-elevation-5);
32
- max-width: 520px;
33
- width: 100%;
34
- max-height: 85dvh;
35
- display: flex;
36
- flex-direction: column;
37
- z-index: z-index('modal');
38
- transform: scale(0.96) translateY(12px);
39
- transition: transform var(--mw-duration-base) var(--mw-ease-out);
40
-
41
- .mw-modal-open & {
42
- transform: scale(1) translateY(0);
21
+ // Show modal when class is added
22
+ &.mw-modal-open {
23
+ display: flex;
24
+ opacity: 1;
25
+ }
43
26
  }
44
27
 
45
- // The four widths grow by a roughly even step (150 / 200 / 240px) so each
46
- // size is recognisably a size. Before, default and lg sat 130px apart - the
47
- // smallest gap of the four, right in the middle of the scale, which made the
48
- // two read as the same modal rendered twice.
49
- &.mw-modal-sm {
50
- max-width: 370px;
51
- max-height: 80dvh;
52
- }
53
- &.mw-modal-lg {
54
- max-width: 720px;
55
- max-height: 90dvh;
28
+ // Modal container
29
+ .mw-modal {
30
+ background: var(--mw-card-background);
31
+ @include surface(0);
32
+ box-shadow: var(--mw-elevation-5);
33
+ max-width: 520px;
34
+ width: 100%;
35
+ max-height: 85dvh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ z-index: z-index('modal');
39
+ transform: scale(0.96) translateY(12px);
40
+ transition: transform var(--mw-duration-base) var(--mw-ease-out);
41
+
42
+ .mw-modal-open & {
43
+ transform: scale(1) translateY(0);
44
+ }
45
+
46
+ // The four widths grow by a roughly even step (150 / 200 / 240px) so each
47
+ // size is recognisably a size. Before, default and lg sat 130px apart - the
48
+ // smallest gap of the four, right in the middle of the scale, which made the
49
+ // two read as the same modal rendered twice.
50
+ &.mw-modal-sm {
51
+ max-width: 370px;
52
+ max-height: 80dvh;
53
+ }
54
+ &.mw-modal-lg {
55
+ max-width: 720px;
56
+ max-height: 90dvh;
57
+ }
58
+ &.mw-modal-xl {
59
+ max-width: 960px;
60
+ max-height: 92dvh;
61
+ }
56
62
  }
57
- &.mw-modal-xl {
58
- max-width: 960px;
59
- max-height: 92dvh;
63
+
64
+ // Phone: the dialog becomes a sheet
65
+ @include media-down('sm') {
66
+ .mw-modal-overlay {
67
+ padding: spacing('0');
68
+ align-items: flex-end;
69
+ }
70
+
71
+ .mw-modal,
72
+ .mw-modal.mw-modal-sm,
73
+ .mw-modal.mw-modal-lg,
74
+ .mw-modal.mw-modal-xl {
75
+ max-width: 100%;
76
+ max-height: 92dvh;
77
+ border-radius: radius('xl') radius('xl') radius('none') radius('none');
78
+ padding-bottom: env(safe-area-inset-bottom, 0px);
79
+ // Straight up from the bottom edge - a sheet does not scale in
80
+ transform: translateY(100%);
81
+ transition: transform var(--mw-duration-slow) var(--mw-ease-out);
82
+ }
83
+
84
+ .mw-modal-open .mw-modal {
85
+ transform: translateY(0);
86
+ }
87
+
88
+ // The grab handle. It does nothing - there is no drag-to-dismiss behind it -
89
+ // but it is the mark that says "this came up from the bottom and goes back
90
+ // down", and a sheet without one reads as a page that got stuck.
91
+ .mw-modal-header::before {
92
+ content: '';
93
+ position: absolute;
94
+ top: spacing('2');
95
+ left: 50%;
96
+ transform: translateX(-50%);
97
+ width: 36px;
98
+ height: 4px;
99
+ border-radius: radius('lg');
100
+ background: var(--mw-text-muted-color);
101
+ opacity: 0.45;
102
+ }
103
+
104
+ .mw-modal-header {
105
+ position: relative;
106
+ padding: spacing('6') spacing('4') spacing('3');
107
+ }
108
+
109
+ .mw-modal-footer {
110
+ padding: spacing('4');
111
+ }
112
+
113
+ .mw-modal-body {
114
+ padding: spacing('4');
115
+ }
116
+
117
+ // On a phone the actions are the reason the sheet is open. Full width, in
118
+ // reading order, thumb-sized - not two 80px buttons huddled in a corner.
119
+ .mw-modal-footer .mw-btn {
120
+ flex: 1 1 auto;
121
+ min-width: 0;
122
+ min-height: 2.75rem;
123
+ }
60
124
  }
61
- }
62
125
 
63
- // Phone: the dialog becomes a sheet
64
- @include media-down('sm') {
65
- .mw-modal-overlay {
126
+ // The same modal as a <dialog>, which brings the focus trap, Escape, the inert
127
+ // background and the scroll lock a div cannot have. Every rule above is
128
+ // class-based and applies unchanged; what follows is only what the top layer
129
+ // changes. `closedby="any"` dismisses on a backdrop click, and where that is
130
+ // not understood the framework script handles the click itself.
131
+ dialog.mw-modal {
132
+ // The UA draws its own box - a border, padding and a white background - and
133
+ // `.mw-modal` above already has all three.
66
134
  padding: spacing('0');
67
- align-items: flex-end;
68
- }
135
+ border: none;
136
+ color: inherit;
137
+ // In the top layer there is nothing left to sit above
138
+ z-index: auto;
139
+ // The UA centres a dialog with auto margins; these keep that while giving
140
+ // the sheet below something to override.
141
+ margin: auto;
69
142
 
70
- .mw-modal,
71
- .mw-modal.mw-modal-sm,
72
- .mw-modal.mw-modal-lg,
73
- .mw-modal.mw-modal-xl {
74
- max-width: 100%;
75
- max-height: 92dvh;
76
- border-radius: radius('xl') radius('xl') radius('none') radius('none');
77
- padding-bottom: env(safe-area-inset-bottom, 0px);
78
- // Straight up from the bottom edge - a sheet does not scale in
79
- transform: translateY(100%);
80
- transition: transform var(--mw-duration-slow) var(--mw-ease-out);
81
- }
143
+ // `.mw-modal` sets `display: flex`, which beats the UA's rule for a closed
144
+ // dialog and would leave it on screen. Discrete, so it can be transitioned.
145
+ &:not([open]) {
146
+ display: none;
147
+ }
82
148
 
83
- .mw-modal-open .mw-modal {
84
- transform: translateY(0);
85
- }
149
+ opacity: 0;
150
+ transform: scale(0.96) translateY(12px);
151
+ transition:
152
+ opacity var(--mw-duration-base) var(--mw-ease-out),
153
+ transform var(--mw-duration-base) var(--mw-ease-out),
154
+ overlay var(--mw-duration-base) allow-discrete,
155
+ display var(--mw-duration-base) allow-discrete;
86
156
 
87
- // The grab handle. It does nothing - there is no drag-to-dismiss behind it -
88
- // but it is the mark that says "this came up from the bottom and goes back
89
- // down", and a sheet without one reads as a page that got stuck.
90
- .mw-modal-header::before {
91
- content: '';
92
- position: absolute;
93
- top: spacing('2');
94
- left: 50%;
95
- transform: translateX(-50%);
96
- width: 36px;
97
- height: 4px;
98
- border-radius: radius('lg');
99
- background: var(--mw-text-muted-color);
100
- opacity: 0.45;
101
- }
157
+ &[open] {
158
+ opacity: 1;
159
+ transform: scale(1) translateY(0);
102
160
 
103
- .mw-modal-header {
104
- position: relative;
105
- padding: spacing('6') spacing('4') spacing('3');
106
- }
161
+ // Without this the dialog is already at its end state on the first frame
162
+ // and only the backdrop animates.
163
+ @starting-style {
164
+ opacity: 0;
165
+ transform: scale(0.96) translateY(12px);
166
+ }
167
+ }
107
168
 
108
- .mw-modal-footer {
109
- padding: spacing('4');
110
- }
169
+ &::backdrop {
170
+ background: var(--mw-overlay-background);
171
+ backdrop-filter: blur(3px);
172
+ opacity: 0;
173
+ transition:
174
+ opacity var(--mw-duration-base) var(--mw-ease-out),
175
+ overlay var(--mw-duration-base) allow-discrete,
176
+ display var(--mw-duration-base) allow-discrete;
177
+ }
111
178
 
112
- .mw-modal-body {
113
- padding: spacing('4');
179
+ &[open]::backdrop {
180
+ opacity: 1;
181
+
182
+ @starting-style {
183
+ opacity: 0;
184
+ }
185
+ }
186
+
187
+ // Phone: the same bottom sheet the div version turns into. The width, the
188
+ // height cap, the corners and the safe-area padding all come from the
189
+ // `.mw-modal` rule above, which is class-based and applies here too - only
190
+ // what a top-layer box needs on top of it is repeated.
191
+ @include media-down('sm') {
192
+ // The UA centres a dialog; a sheet sits on the bottom edge.
193
+ margin: auto auto 0;
194
+ width: 100%;
195
+ // Straight up from the bottom edge - a sheet does not scale in, and it
196
+ // travels the height of itself rather than twelve pixels, so it gets the
197
+ // slow duration the div sheet above uses for the same move.
198
+ transform: translateY(100%);
199
+ transition:
200
+ opacity var(--mw-duration-slow) var(--mw-ease-out),
201
+ transform var(--mw-duration-slow) var(--mw-ease-out),
202
+ overlay var(--mw-duration-slow) allow-discrete,
203
+ display var(--mw-duration-slow) allow-discrete;
204
+
205
+ &[open] {
206
+ transform: translateY(0);
207
+
208
+ @starting-style {
209
+ transform: translateY(100%);
210
+ }
211
+ }
212
+ }
114
213
  }
115
214
 
116
- // On a phone the actions are the reason the sheet is open. Full width, in
117
- // reading order, thumb-sized - not two 80px buttons huddled in a corner.
118
- .mw-modal-footer .mw-btn {
119
- flex: 1 1 auto;
120
- min-width: 0;
121
- min-height: 2.75rem;
215
+ // Modal sections
216
+ .mw-modal-header {
217
+ padding: spacing('3') spacing('5');
218
+ border-bottom: 1px solid var(--mw-border-accent);
219
+ display: flex;
220
+ justify-content: space-between;
221
+ align-items: center;
222
+
223
+ h1,
224
+ h2,
225
+ h3,
226
+ h4,
227
+ h5 {
228
+ margin: spacing('0');
229
+ line-height: 1.2;
230
+ }
122
231
  }
123
- }
124
232
 
125
- // Modal sections
126
- .mw-modal-header {
127
- padding: spacing('3') spacing('5');
128
- border-bottom: 1px solid var(--mw-border-accent);
129
- display: flex;
130
- justify-content: space-between;
131
- align-items: center;
132
-
133
- h1,
134
- h2,
135
- h3,
136
- h4,
137
- h5 {
233
+ // Explicit hook for the heading
234
+ .mw-modal-title {
138
235
  margin: spacing('0');
139
236
  line-height: 1.2;
140
237
  }
141
- }
142
238
 
143
- // Explicit hook for the heading
144
- .mw-modal-title {
145
- margin: spacing('0');
146
- line-height: 1.2;
147
- }
239
+ .mw-modal-body {
240
+ padding: spacing('4') spacing('5');
241
+ overflow-y: auto;
242
+ flex: 1;
243
+ overscroll-behavior: contain;
244
+ @include scrollbar;
245
+ }
148
246
 
149
- .mw-modal-body {
150
- padding: spacing('4') spacing('5');
151
- overflow-y: auto;
152
- flex: 1;
153
- overscroll-behavior: contain;
154
- @include scrollbar;
155
- }
247
+ .mw-modal-footer {
248
+ padding: spacing('3') spacing('5');
249
+ border-top: 1px solid var(--mw-border-accent);
250
+ display: flex;
251
+ // Three actions do not fit one line inside a phone-width dialog, and a row
252
+ // that cannot wrap pushes the last one out through the border
253
+ flex-wrap: wrap;
254
+ gap: spacing('2');
255
+ justify-content: flex-end;
256
+ }
156
257
 
157
- .mw-modal-footer {
158
- padding: spacing('3') spacing('5');
159
- border-top: 1px solid var(--mw-border-accent);
160
- display: flex;
161
- // Three actions do not fit one line inside a phone-width dialog, and a row
162
- // that cannot wrap pushes the last one out through the border
163
- flex-wrap: wrap;
164
- gap: spacing('2');
165
- justify-content: flex-end;
166
- }
258
+ // Close button
259
+ .mw-modal-close {
260
+ background: none;
261
+ font-size: font-size('md');
262
+ cursor: pointer;
263
+ padding: spacing('1') spacing('3');
264
+ border-radius: radius('sm');
265
+ border: 1px solid var(--mw-border);
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ color: var(--mw-text-color);
270
+ transition: var(--mw-transition-fast);
271
+ @include focus-ring;
272
+ @include tappable;
167
273
 
168
- // Close button
169
- .mw-modal-close {
170
- background: none;
171
- font-size: font-size('md');
172
- cursor: pointer;
173
- padding: spacing('1') spacing('3');
174
- border-radius: radius('sm');
175
- border: 1px solid var(--mw-border);
176
- display: flex;
177
- align-items: center;
178
- justify-content: center;
179
- color: var(--mw-text-color);
180
- transition: var(--mw-transition-fast);
181
- @include focus-ring;
182
- @include tappable;
183
-
184
- &:hover {
185
- background: var(--mw-primary-color-hover);
186
- color: var(--mw-primary-accent-text-color);
187
- }
274
+ &:hover {
275
+ background: var(--mw-primary-color-hover);
276
+ color: var(--mw-primary-accent-text-color);
277
+ }
188
278
 
189
- &:active {
190
- transform: scale(0.92);
191
- transition-duration: var(--mw-duration-instant);
192
- }
279
+ &:active {
280
+ transform: scale(0.92);
281
+ transition-duration: var(--mw-duration-instant);
282
+ }
193
283
 
194
- // The one control on the sheet a thumb has to find without looking
195
- @include media-down('sm') {
196
- min-width: 2.75rem;
197
- min-height: 2.75rem;
284
+ // The one control on the sheet a thumb has to find without looking
285
+ @include media-down('sm') {
286
+ min-width: 2.75rem;
287
+ min-height: 2.75rem;
288
+ }
198
289
  }
199
- }
200
290
 
201
- // Backdrop for click-outside-to-close.
202
- // The cursor says it is clickable, so it should also be reachable by keyboard -
203
- // which means it wants to be a <button>. These four lines clear the browser
204
- // defaults that come with one; on a <div> they change nothing.
205
- .mw-modal-backdrop {
206
- position: absolute;
207
- top: 0;
208
- left: 0;
209
- right: 0;
210
- bottom: 0;
211
- z-index: z-index('modal')-5;
212
- cursor: pointer;
213
- appearance: none;
214
- padding: 0;
215
- background: none;
216
- border: 0;
291
+ // Backdrop for click-outside-to-close.
292
+ // The cursor says it is clickable, so it should also be reachable by keyboard -
293
+ // which means it wants to be a <button>. These four lines clear the browser
294
+ // defaults that come with one; on a <div> they change nothing.
295
+ .mw-modal-backdrop {
296
+ position: absolute;
297
+ top: 0;
298
+ left: 0;
299
+ right: 0;
300
+ bottom: 0;
301
+ z-index: z-index('modal')-5;
302
+ cursor: pointer;
303
+ appearance: none;
304
+ padding: 0;
305
+ background: none;
306
+ border: 0;
307
+ }
217
308
  }