maverick-wave 3.10.0 → 4.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 (60) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +62 -30
  2. package/.claude/skills/maverick-wave/examples/angular-form.md +1 -1
  3. package/.claude/skills/maverick-wave/examples/angular-services.md +15 -8
  4. package/.claude/skills/maverick-wave/examples/static-landing-page.md +4 -4
  5. package/.claude/skills/maverick-wave/references/components.md +416 -27
  6. package/.claude/skills/maverick-wave/references/forms.md +8 -4
  7. package/.claude/skills/maverick-wave/references/javascript.md +32 -18
  8. package/.claude/skills/maverick-wave/references/layout.md +48 -3
  9. package/.claude/skills/maverick-wave/references/theming.md +34 -24
  10. package/CHANGELOG.md +56 -0
  11. package/index.html +25 -0
  12. package/maverick-wave.min.css +2 -2
  13. package/maverick-wave.min.js +1 -1
  14. package/package.json +2 -2
  15. package/src/js/main.js +314 -20
  16. package/src/partials/accordions-container.html +2 -2
  17. package/src/partials/button-bar-container.html +4 -2
  18. package/src/partials/buttons-container.html +35 -0
  19. package/src/partials/calendar-container.html +205 -0
  20. package/src/partials/cards-container.html +43 -0
  21. package/src/partials/documentation-container.html +3 -0
  22. package/src/partials/form-container.html +1 -1
  23. package/src/partials/get-started-container.html +2 -2
  24. package/src/partials/kanban-container.html +680 -715
  25. package/src/partials/modals-container.html +5 -5
  26. package/src/partials/pagination-container.html +50 -13
  27. package/src/partials/segmented-container.html +66 -0
  28. package/src/partials/tables-container.html +96 -24
  29. package/src/partials/tabs-container.html +16 -6
  30. package/src/partials/tags-container.html +15 -0
  31. package/src/partials/timelines-container.html +2 -2
  32. package/src/partials/utilities-container.html +81 -0
  33. package/src/scss/abstracts/_mixins.scss +34 -0
  34. package/src/scss/abstracts/_variables.scss +6 -2
  35. package/src/scss/base/_base.scss +21 -0
  36. package/src/scss/components/_accordions.scss +2 -0
  37. package/src/scss/components/_alerts.scss +5 -19
  38. package/src/scss/components/_button-bar.scss +6 -0
  39. package/src/scss/components/_buttons.scss +67 -0
  40. package/src/scss/components/_calendar.scss +290 -0
  41. package/src/scss/components/_content-slider.scss +1 -0
  42. package/src/scss/components/_index.scss +2 -0
  43. package/src/scss/components/_kanban.scss +2 -6
  44. package/src/scss/components/_modals.scss +15 -4
  45. package/src/scss/components/_pagination.scss +68 -64
  46. package/src/scss/components/_segmented.scss +85 -0
  47. package/src/scss/components/_tables.scss +16 -0
  48. package/src/scss/components/_tabs.scss +15 -0
  49. package/src/scss/components/_tags.scss +16 -0
  50. package/src/scss/components/_theme-toggle.scss +2 -0
  51. package/src/scss/components/_timelines.scss +3 -0
  52. package/src/scss/form-elements/_form.scss +13 -7
  53. package/src/scss/layout/_header.scss +3 -0
  54. package/src/scss/layout/_main.scss +5 -1
  55. package/src/scss/layout/_section.scss +1 -1
  56. package/src/scss/utilities/_accessibility.scss +14 -0
  57. package/src/scss/utilities/_flex.scss +17 -0
  58. package/src/scss/utilities/_index.scss +2 -0
  59. package/src/scss/utilities/_text.scss +17 -5
  60. package/src/scss/utilities/_touch-targets.scss +54 -0
@@ -1,83 +1,70 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
+ // Same shape as a calendar: a header with an arrow on each side of the title,
4
+ // content below. Both are "one page of something, with a way to the next one",
5
+ // so they get the same frame - only the body differs.
3
6
  .mw-pagination {
4
- border: 1px solid var(--mw-border);
5
- border-radius: radius('lg');
7
+ padding: spacing('5');
6
8
  background: var(--mw-card-background);
7
- overflow: hidden;
8
-
9
- &-header {
10
- display: flex;
11
- align-items: center;
12
- justify-content: space-between;
13
- padding: spacing('3');
14
- background: var(--mw-primary-color);
15
- color: var(--mw-accent-text-color);
16
- }
17
-
18
- &-nav {
19
- background: none;
20
- color: var(--mw-accent-text-color);
21
- cursor: pointer;
22
- padding: spacing('2') spacing('4');
23
- margin: spacing('0') spacing('5');
24
- border-radius: radius('sm');
25
- border: 1px solid var(--mw-accent-text-color);
26
- display: flex;
27
- align-items: center;
28
- justify-content: center;
29
- transition: background-color 0.2s ease;
30
- font-size: font-size('lg');
31
-
32
- &:hover {
33
- background: var(--mw-primary-color-hover);
34
- }
9
+ border: 1px solid var(--mw-border);
10
+ border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
11
+ box-shadow: 0 5px 10px var(--mw-shadow);
12
+ }
35
13
 
36
- &:disabled {
37
- opacity: 0.5;
38
- cursor: not-allowed;
39
- }
40
- }
14
+ // The calendar gets its separation from the weekday row - here there is
15
+ // nothing between the title and the body, so it needs a rule of its own
16
+ .mw-pagination-header {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: spacing('3');
20
+ margin-bottom: spacing('5');
21
+ padding-bottom: spacing('4');
22
+ border-bottom: 1px solid var(--mw-border);
23
+ }
41
24
 
42
- &-title {
43
- flex: 1;
44
- text-align: center;
45
- font-weight: font-weight('medium');
46
- font-size: font-size('lg');
47
- margin: 0;
48
- }
25
+ // Takes the space between the two arrows so the label stays optically centred
26
+ // no matter how long it is
27
+ .mw-pagination-title {
28
+ flex: 1;
29
+ min-width: 0;
30
+ margin: spacing('0');
31
+ text-align: center;
32
+ font-size: font-size('md');
33
+ font-weight: font-weight('medium');
34
+ color: var(--mw-text-color);
35
+ white-space: nowrap;
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ }
49
39
 
50
- &-content {
51
- padding: spacing('4');
52
- min-height: 200px;
53
- transition: all 0.3s ease;
40
+ // Sits on .mw-btn - this only squares it off around the chevron
41
+ .mw-pagination-nav {
42
+ padding: spacing('2') spacing('4');
43
+ line-height: 1;
44
+ flex-shrink: 0;
45
+ }
54
46
 
55
- &.mw-pagination-loading {
56
- opacity: 0.6;
57
- pointer-events: none;
58
- }
59
- }
47
+ .mw-pagination-content {
48
+ min-height: 200px;
49
+ transition: var(--mw-transition);
50
+ }
60
51
 
61
- @include media-down('sm') {
62
- &-nav {
63
- font-size: font-size('md');
64
- }
65
- &-title {
66
- font-size: font-size('md');
67
- }
68
- }
52
+ // Dims the body while the next page is on its way
53
+ .mw-pagination-content.mw-pagination-loading {
54
+ opacity: 0.6;
55
+ pointer-events: none;
69
56
  }
70
57
 
71
- // Optional slide animation
58
+ // Optional directional transitions for the swap
72
59
  .mw-pagination-slide-left {
73
- animation: slideLeft 0.3s ease;
60
+ animation: mw-pagination-slide-left 0.3s ease;
74
61
  }
75
62
 
76
63
  .mw-pagination-slide-right {
77
- animation: slideRight 0.3s ease;
64
+ animation: mw-pagination-slide-right 0.3s ease;
78
65
  }
79
66
 
80
- @keyframes slideLeft {
67
+ @keyframes mw-pagination-slide-left {
81
68
  from {
82
69
  transform: translateX(20px);
83
70
  opacity: 0;
@@ -88,7 +75,7 @@
88
75
  }
89
76
  }
90
77
 
91
- @keyframes slideRight {
78
+ @keyframes mw-pagination-slide-right {
92
79
  from {
93
80
  transform: translateX(-20px);
94
81
  opacity: 0;
@@ -98,3 +85,20 @@
98
85
  opacity: 1;
99
86
  }
100
87
  }
88
+
89
+ @include media-down('sm') {
90
+ .mw-pagination {
91
+ padding: spacing('4') spacing('3');
92
+ }
93
+
94
+ .mw-pagination-title {
95
+ font-size: font-size('base');
96
+ }
97
+ }
98
+
99
+ @media (prefers-reduced-motion: reduce) {
100
+ .mw-pagination-slide-left,
101
+ .mw-pagination-slide-right {
102
+ animation: none;
103
+ }
104
+ }
@@ -0,0 +1,85 @@
1
+ @use '../abstracts' as *;
2
+
3
+ // Two or three positions of *one* switch - income/expense on a booking form,
4
+ // offer/quote on a document form.
5
+ //
6
+ // Deliberately not a `.mw-button-bar`: that one stacks below 576px and gives
7
+ // every button 96% width, which is right for a row of independent actions and
8
+ // wrong here. Stacked, two positions of a switch look like two buttons you
9
+ // could press both of. This one stays horizontal at every width.
10
+ .mw-segmented {
11
+ display: inline-flex;
12
+ width: 100%;
13
+ gap: spacing('1');
14
+ padding: spacing('1');
15
+ background: var(--mw-surface-muted);
16
+ border: 1px solid var(--mw-border);
17
+ border-radius: radius('sm');
18
+ }
19
+
20
+ // Equal shares, so the switch reads as one control and not as a row of labels
21
+ .mw-segmented-item {
22
+ flex: 1 1 0;
23
+ min-width: 0;
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: spacing('2');
28
+ padding: spacing('2') spacing('3');
29
+ border: 1px solid transparent;
30
+ border-radius: radius('xs');
31
+ background: transparent;
32
+ color: var(--mw-text-muted-color);
33
+ font-family: inherit;
34
+ font-size: font-size('sm');
35
+ font-weight: font-weight('medium');
36
+ line-height: 1.2;
37
+ text-align: center;
38
+ white-space: nowrap;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ cursor: pointer;
42
+ transition: var(--mw-transition);
43
+
44
+ &:hover:not(:disabled):not(.mw-active):not(.active) {
45
+ color: var(--mw-text-color);
46
+ }
47
+
48
+ // Inset, because an outline outside the item would be clipped by the frame
49
+ &:focus-visible {
50
+ outline: 2px solid var(--mw-primary-color);
51
+ outline-offset: -2px;
52
+ }
53
+
54
+ &:disabled {
55
+ cursor: not-allowed;
56
+ opacity: 0.6;
57
+ }
58
+
59
+ &.mw-active,
60
+ &.active {
61
+ background: var(--mw-primary-color);
62
+ border-color: var(--mw-primary-color);
63
+ color: var(--mw-accent-text-color);
64
+ }
65
+ }
66
+
67
+ // Secondary tone for a switch that is not the primary decision on the form
68
+ .mw-segmented-secondary .mw-segmented-item {
69
+ &.mw-active,
70
+ &.active {
71
+ background: var(--mw-secondary-color);
72
+ border-color: var(--mw-secondary-color);
73
+ }
74
+ }
75
+
76
+ // Sits inline next to a label instead of filling the row. Equal shares are the
77
+ // point of the full-width control; here they would clip the longest label to
78
+ // the width of the shortest, so the items follow their own content instead.
79
+ .mw-segmented-auto {
80
+ width: auto;
81
+
82
+ .mw-segmented-item {
83
+ flex: 0 1 auto;
84
+ }
85
+ }
@@ -56,6 +56,22 @@
56
56
  }
57
57
  }
58
58
 
59
+ // Scroll hint for a table that is known to be wider than its frame.
60
+ //
61
+ // Opt-in, unlike the one on the tab bar, and by a different mechanism. A table
62
+ // paints its own opaque surface, so the gradient trick used on `.mw-tabs-nav`
63
+ // would sit behind the rows and never show. A mask sits in front of the
64
+ // content and works - but masks have no `local` attachment, so it cannot
65
+ // disappear once the last column is reached. Hence the extra class: only put
66
+ // it on a table that really does overflow.
67
+ .mw-table-responsive-hint {
68
+ mask-image: linear-gradient(
69
+ to right,
70
+ #000 calc(100% - 2.5rem),
71
+ transparent 100%
72
+ );
73
+ }
74
+
59
75
  // Card view for mobile - clean and simple approach
60
76
  .mw-table-cards {
61
77
  @include media-down('md') {
@@ -11,6 +11,11 @@
11
11
  margin-bottom: spacing('3');
12
12
  overflow-x: auto;
13
13
 
14
+ // Seven tabs, three of them visible, and nothing saying so - whoever is
15
+ // looking for the seventh never finds the page. The hint appears only on
16
+ // the side that still has tabs behind it.
17
+ @include scroll-hint;
18
+
14
19
  @include media-down('sm') {
15
20
  flex-wrap: nowrap;
16
21
  -webkit-overflow-scrolling: touch;
@@ -33,6 +38,7 @@
33
38
  color: var(--mw-primary-color);
34
39
  }
35
40
 
41
+ &.mw-active,
36
42
  &.active {
37
43
  color: var(--mw-primary-color);
38
44
  border-bottom-color: var(--mw-primary-color);
@@ -47,6 +53,7 @@
47
53
  &-panel {
48
54
  display: none;
49
55
 
56
+ &.mw-active,
50
57
  &.active {
51
58
  display: block;
52
59
  animation: fadeIn 1.1s ease;
@@ -70,6 +77,8 @@
70
77
  margin-right: spacing('3');
71
78
  min-width: 160px;
72
79
  max-width: 200px;
80
+ // Stacked, so nothing scrolls sideways and the hint would be a lie
81
+ background-image: none;
73
82
 
74
83
  @include media-down('sm') {
75
84
  flex-direction: row;
@@ -82,6 +91,9 @@
82
91
  min-width: 100%;
83
92
  overflow-x: auto;
84
93
  display: flex;
94
+
95
+ // Horizontal again down here, so it comes back
96
+ @include scroll-hint;
85
97
  }
86
98
  }
87
99
 
@@ -89,6 +101,7 @@
89
101
  border-bottom: none;
90
102
  border-right: 2px solid transparent;
91
103
 
104
+ &.mw-active,
92
105
  &.active {
93
106
  border-right-color: var(--mw-primary-color);
94
107
  }
@@ -98,6 +111,7 @@
98
111
  border-bottom: 2px solid transparent;
99
112
  flex-shrink: 0;
100
113
 
114
+ &.mw-active,
101
115
  &.active {
102
116
  border-right-color: transparent;
103
117
  border-bottom-color: var(--mw-primary-color);
@@ -129,6 +143,7 @@
129
143
  border: 1px solid var(--mw-border-accent);
130
144
  background: var(--mw-card-background);
131
145
 
146
+ &.mw-active,
132
147
  &.active {
133
148
  background: var(--mw-primary-color);
134
149
  color: var(--mw-accent-text-color);
@@ -97,6 +97,22 @@ $_mw-tag-colors: (
97
97
  }
98
98
  }
99
99
 
100
+ // Neutral variant for plain identifiers - a customer number, a document type,
101
+ // a price on a category. `muted` means "steps back", this one means "no
102
+ // judgement attached": on an invoice card "XRechnung" is a property, while
103
+ // "no e-invoice" is a shortcoming, and the two must not look alike.
104
+ .mw-tags-neutral .mw-tags-item,
105
+ .mw-tag-neutral {
106
+ background-color: var(--mw-surface-muted);
107
+ border-color: var(--mw-border);
108
+ color: var(--mw-text-color);
109
+
110
+ &:hover {
111
+ background-color: var(--mw-surface-muted);
112
+ border-color: var(--mw-border-accent);
113
+ }
114
+ }
115
+
100
116
  // Size variants
101
117
  // a little more air between the items instead of inside them
102
118
  .mw-tags-lg {
@@ -33,10 +33,12 @@
33
33
  font-size: font-size('md');
34
34
  }
35
35
 
36
+ &.mw-active &-slider,
36
37
  &.active &-slider {
37
38
  left: 35px;
38
39
  }
39
40
 
41
+ &.mw-active &-icon,
40
42
  &.active &-icon {
41
43
  left: 2px;
42
44
  }
@@ -179,6 +179,7 @@ $_simple-date-width: 110px;
179
179
  }
180
180
 
181
181
  // Active step
182
+ &-step.mw-active &-date-container,
182
183
  &-step.active &-date-container {
183
184
  background: var(--mw-primary-color);
184
185
  border-color: var(--mw-primary-color);
@@ -209,6 +210,7 @@ $_simple-date-width: 110px;
209
210
  left: -22px;
210
211
  }
211
212
 
213
+ &-step.mw-active &-date-container,
212
214
  &-step.active &-date-container {
213
215
  transform: scale(1.05);
214
216
  }
@@ -306,6 +308,7 @@ $_simple-date-width: 110px;
306
308
  }
307
309
 
308
310
  // Active step
311
+ &-step.mw-active &-date,
309
312
  &-step.active &-date {
310
313
  background: var(--mw-primary-color);
311
314
  color: var(--mw-accent-text-color);
@@ -46,13 +46,9 @@
46
46
  position: relative;
47
47
  text-align: right;
48
48
 
49
- // hint text above the buttons
50
- &-hint {
51
- width: 100%;
52
- font-size: font-size('sm');
53
- color: var(--mw-text-muted-color);
54
- margin-bottom: spacing('0');
55
- }
49
+ // The note above the buttons is `.mw-actions-note` (see _buttons.scss), so
50
+ // it looks the same in a form, a modal, a panel and a card. Only its
51
+ // alignment is the form's business, because a form row can be re-aligned.
56
52
 
57
53
  // full-width stacked
58
54
  &-full-width {
@@ -65,11 +61,21 @@
65
61
  &-center {
66
62
  justify-content: center;
67
63
  text-align: center;
64
+
65
+ .mw-actions-note,
66
+ .mw-form-actions-hint {
67
+ text-align: center;
68
+ }
68
69
  }
69
70
 
70
71
  &-left {
71
72
  justify-content: flex-start;
72
73
  text-align: left;
74
+
75
+ .mw-actions-note,
76
+ .mw-form-actions-hint {
77
+ text-align: left;
78
+ }
73
79
  }
74
80
  }
75
81
 
@@ -243,6 +243,7 @@
243
243
  }
244
244
 
245
245
  // Last, so the current page keeps its look while being hovered
246
+ &.mw-active,
246
247
  &.active {
247
248
  color: var(--mw-header-navbar-list-active-color);
248
249
  background: var(--mw-primary-background);
@@ -358,11 +359,13 @@
358
359
  }
359
360
 
360
361
  &:hover,
362
+ &.mw-active,
361
363
  &.active {
362
364
  color: var(--mw-header-navbar-list-active-color);
363
365
  }
364
366
 
365
367
  &:hover::after,
368
+ &.mw-active::after,
366
369
  &.active::after {
367
370
  transform: scaleX(1) !important;
368
371
  opacity: 1 !important;
@@ -9,10 +9,14 @@
9
9
  }
10
10
 
11
11
  .mw-container {
12
- width: min(1200px, 89%);
12
+ width: var(--mw-container-width);
13
13
  margin: spacing('0') auto;
14
14
  padding: spacing('0');
15
15
 
16
+ @include media-down('sm') {
17
+ width: var(--mw-container-width-sm);
18
+ }
19
+
16
20
  // add a hero part for a full size background picture
17
21
  @supports (selector(:has(.mw-hero))) {
18
22
  // if it has *any* descendant .mw-hero
@@ -106,7 +106,7 @@
106
106
 
107
107
  // Additional spacing for the whole section
108
108
  .mw-section {
109
- padding: spacing('7') spacing('0');
109
+ padding: var(--mw-section-padding-block) spacing('0');
110
110
  position: relative;
111
111
  }
112
112
 
@@ -0,0 +1,14 @@
1
+ @use '../abstracts' as *;
2
+
3
+ // Visible to screen readers only
4
+ .mw-sr-only {
5
+ position: absolute;
6
+ width: 1px;
7
+ height: 1px;
8
+ padding: 0;
9
+ margin: -1px;
10
+ overflow: hidden;
11
+ clip-path: inset(50%);
12
+ white-space: nowrap;
13
+ border: 0;
14
+ }
@@ -95,3 +95,20 @@
95
95
  align-self: stretch;
96
96
  }
97
97
  }
98
+
99
+ // The most common row in any app: what it is on the left, the value or the
100
+ // action on the right, and on a narrow screen the right half drops below.
101
+ // The `flex-wrap` is the part everyone forgets.
102
+ .mw-row-split {
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ align-items: baseline;
106
+ justify-content: space-between;
107
+ gap: spacing('2') spacing('5');
108
+ }
109
+
110
+ // Same row for content of unequal height - an icon beside text, a button
111
+ // beside a heading. Baseline alignment would hang those off the text.
112
+ .mw-row-split-center {
113
+ align-items: center;
114
+ }
@@ -1,4 +1,6 @@
1
+ @forward 'accessibility';
1
2
  @forward 'display';
2
3
  @forward 'flex';
3
4
  @forward 'spacing';
4
5
  @forward 'text';
6
+ @forward 'touch-targets';
@@ -14,12 +14,24 @@
14
14
  text-align: right;
15
15
  }
16
16
 
17
- // For money and other figures in tables: right-aligned, fixed-width digits
18
- // so decimal places line up across rows, and never wrapped mid-number.
19
- // Locale-agnostic - works for 1,204.50 and 1.204,50 alike, because the
20
- // alignment comes from the right edge, not from the separator.
21
- // `lining-nums` guards against fonts that default to old-style figures.
17
+ // Fixed-width digits, nothing else. For anything where figures have to line
18
+ // up vertically or must not jitter while they count: a clock, a counter, a
19
+ // key figure, an ID. `lining-nums` guards against fonts that default to
20
+ // old-style figures.
21
+ //
22
+ // Deliberately without alignment: bundling `text-align: right` in here meant
23
+ // that whoever only wanted the digits could not use the class and wrote the
24
+ // declaration out by hand instead.
22
25
  &-numeric {
26
+ font-variant-numeric: tabular-nums lining-nums;
27
+ font-feature-settings: 'tnum', 'lnum';
28
+ }
29
+
30
+ // For money and other figures in tables: the digits from above, plus
31
+ // right-aligned and never wrapped mid-number. Locale-agnostic - it works for
32
+ // 1,204.50 and 1.204,50 alike, because the alignment comes from the right
33
+ // edge, not from the separator.
34
+ &-currency {
23
35
  text-align: right;
24
36
  font-variant-numeric: tabular-nums lining-nums;
25
37
  font-feature-settings: 'tnum', 'lnum';
@@ -0,0 +1,54 @@
1
+ @use '../abstracts' as *;
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Touch targets
5
+ //
6
+ // WCAG 2.5.8 puts the floor at 24x24 CSS px, and that is a floor, not a goal.
7
+ // The compact sizes are right on a pointing device and too small on a thumb,
8
+ // so they only grow where the input actually is coarse.
9
+ //
10
+ // `pointer: coarse` *or* narrow, not just narrow: a tablet in landscape is
11
+ // wide and still operated with a finger. Loaded last, so these win over the
12
+ // component defaults without needing extra specificity.
13
+ // ---------------------------------------------------------------------------
14
+ @media (pointer: coarse), (max-width: #{breakpoint('md')}) {
15
+ .mw-btn-sm {
16
+ min-height: 2.5rem;
17
+ }
18
+
19
+ .mw-input-sm,
20
+ .mw-select-sm,
21
+ .mw-textarea-sm {
22
+ min-height: 2.5rem;
23
+ }
24
+
25
+ // A tab strip is the one navigation a phone has no fallback for
26
+ .mw-tabs-nav-item {
27
+ display: flex;
28
+ align-items: center;
29
+ min-height: 2.75rem;
30
+ }
31
+
32
+ .mw-segmented-item {
33
+ min-height: 2.5rem;
34
+ }
35
+
36
+ .mw-calendar-day {
37
+ min-height: 44px;
38
+ }
39
+
40
+ // The 18px silhouette stays: mini buttons sit in tag rows and table cells,
41
+ // where growing the circle would shift the layout around them. Only the hit
42
+ // area grows, to 28px - clear of the 24px floor. The extra margin keeps two
43
+ // neighbouring hit areas from overlapping.
44
+ .mw-btn-mini {
45
+ position: relative;
46
+ margin-left: spacing('4');
47
+
48
+ &::after {
49
+ content: '';
50
+ position: absolute;
51
+ inset: -5px;
52
+ }
53
+ }
54
+ }