maverick-wave 4.27.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 (140) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/mw-maverick-wave/SKILL.md +42 -25
  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 +90 -5
  6. package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
  7. package/.claude/skills/mw-maverick-wave/references/javascript.md +18 -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 +17 -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 +266 -75
  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/header-utilities-container.html +192 -5
  40. package/src/partials/home-container.html +10 -20
  41. package/src/partials/input-group-container.html +11 -2
  42. package/src/partials/kbd-container.html +1 -1
  43. package/src/partials/login-container.html +5 -1
  44. package/src/partials/media-container.html +2 -2
  45. package/src/partials/modals-container.html +22 -32
  46. package/src/partials/pricing-container.html +9 -9
  47. package/src/partials/skeleton-container.html +6 -6
  48. package/src/partials/stepper-container.html +2 -2
  49. package/src/partials/tables-container.html +12 -12
  50. package/src/partials/tabs-container.html +17 -27
  51. package/src/partials/tags-container.html +23 -17
  52. package/src/partials/techstack-bucket-container.html +13 -0
  53. package/src/partials/tiles-container.html +26 -9
  54. package/src/partials/typography-container.html +7 -6
  55. package/src/partials/utilities-container.html +35 -15
  56. package/src/scss/_layers.scss +10 -0
  57. package/src/scss/abstracts/_index.scss +6 -0
  58. package/src/scss/abstracts/_mixins.scss +61 -153
  59. package/src/scss/abstracts/_variables.scss +123 -319
  60. package/src/scss/base/_base.scss +170 -190
  61. package/src/scss/base/_reset.scss +106 -104
  62. package/src/scss/base/_typography.scss +153 -151
  63. package/src/scss/components/_accordions.scss +97 -97
  64. package/src/scss/components/_alerts.scss +71 -70
  65. package/src/scss/components/_announcement.scss +71 -70
  66. package/src/scss/components/_avatars.scss +130 -142
  67. package/src/scss/components/_badge.scss +131 -132
  68. package/src/scss/components/_blog-post.scss +223 -220
  69. package/src/scss/components/_breadcrumbs.scss +93 -92
  70. package/src/scss/components/_button-bar.scss +102 -110
  71. package/src/scss/components/_buttons.scss +319 -324
  72. package/src/scss/components/_calendar.scss +251 -252
  73. package/src/scss/components/_cards.scss +359 -386
  74. package/src/scss/components/_code.scss +190 -194
  75. package/src/scss/components/_coming-soon.scss +77 -75
  76. package/src/scss/components/_content-slider.scss +90 -89
  77. package/src/scss/components/_divider.scss +55 -53
  78. package/src/scss/components/_dropdown.scss +179 -185
  79. package/src/scss/components/_empty-state.scss +57 -57
  80. package/src/scss/components/_flag.scss +325 -0
  81. package/src/scss/components/_gallery.scss +131 -133
  82. package/src/scss/components/_index.scss +2 -0
  83. package/src/scss/components/_info.scss +151 -167
  84. package/src/scss/components/_kanban.scss +332 -341
  85. package/src/scss/components/_kbd.scss +40 -38
  86. package/src/scss/components/_lang-switch.scss +108 -0
  87. package/src/scss/components/_links.scss +21 -28
  88. package/src/scss/components/_lists.scss +510 -514
  89. package/src/scss/components/_localhost-indicator.scss +26 -24
  90. package/src/scss/components/_media.scss +22 -20
  91. package/src/scss/components/_meta-info.scss +35 -33
  92. package/src/scss/components/_modals.scss +277 -186
  93. package/src/scss/components/_pagination.scss +81 -87
  94. package/src/scss/components/_panels.scss +99 -97
  95. package/src/scss/components/_pricing.scss +183 -241
  96. package/src/scss/components/_progress.scss +112 -89
  97. package/src/scss/components/_prose.scss +35 -33
  98. package/src/scss/components/_ratings.scss +40 -38
  99. package/src/scss/components/_segmented.scss +82 -84
  100. package/src/scss/components/_skeleton.scss +66 -72
  101. package/src/scss/components/_spinners.scss +155 -159
  102. package/src/scss/components/_stepper.scss +116 -116
  103. package/src/scss/components/_tables.scss +155 -148
  104. package/src/scss/components/_tabs.scss +232 -141
  105. package/src/scss/components/_tags.scss +109 -107
  106. package/src/scss/components/_techstack-bucket.scss +123 -122
  107. package/src/scss/components/_testimonial.scss +72 -70
  108. package/src/scss/components/_theme-toggle.scss +53 -51
  109. package/src/scss/components/_tiles.scss +174 -174
  110. package/src/scss/components/_timelines.scss +282 -280
  111. package/src/scss/components/_toasts.scss +66 -72
  112. package/src/scss/form-elements/_checkbox.scss +117 -132
  113. package/src/scss/form-elements/_form.scss +149 -115
  114. package/src/scss/form-elements/_input-group.scss +99 -103
  115. package/src/scss/form-elements/_input.scss +74 -74
  116. package/src/scss/form-elements/_login.scss +48 -48
  117. package/src/scss/form-elements/_prefilled.scss +53 -51
  118. package/src/scss/form-elements/_radio.scss +95 -107
  119. package/src/scss/form-elements/_select.scss +42 -42
  120. package/src/scss/form-elements/_slider.scss +117 -132
  121. package/src/scss/form-elements/_textarea.scss +68 -54
  122. package/src/scss/form-elements/_toggle.scss +127 -140
  123. package/src/scss/layout/_footer.scss +172 -171
  124. package/src/scss/layout/_grid.scss +268 -302
  125. package/src/scss/layout/_header.scss +426 -427
  126. package/src/scss/layout/_home.scss +28 -27
  127. package/src/scss/layout/_main.scss +275 -238
  128. package/src/scss/layout/_page-header.scss +38 -36
  129. package/src/scss/layout/_section.scss +150 -124
  130. package/src/scss/main.scss +1 -1
  131. package/src/scss/utilities/_accessibility.scss +46 -45
  132. package/src/scss/utilities/_aspect.scss +22 -20
  133. package/src/scss/utilities/_corner.scss +23 -5
  134. package/src/scss/utilities/_display.scss +76 -70
  135. package/src/scss/utilities/_elevation.scss +13 -11
  136. package/src/scss/utilities/_flex.scss +83 -85
  137. package/src/scss/utilities/_reveal.scss +40 -63
  138. package/src/scss/utilities/_spacing.scss +63 -64
  139. package/src/scss/utilities/_text.scss +139 -141
  140. 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
  }