maverick-wave 5.28.0 → 5.30.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 (52) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +4 -2
  2. package/.claude/skills/mw-maverick-wave/references/components.md +217 -4
  3. package/.claude/skills/mw-maverick-wave/references/javascript.md +6 -1
  4. package/.claude/skills/mw-maverick-wave/references/theming.md +55 -51
  5. package/CHANGELOG.md +16 -0
  6. package/CLAUDE.md +4 -4
  7. package/README.md +2 -2
  8. package/index.html +65 -0
  9. package/maverick-wave.min.css +2 -2
  10. package/maverick-wave.min.js +1 -1
  11. package/package.json +2 -2
  12. package/scripts/verify.js +2 -1
  13. package/src/assets/images/photo-balloon.svg +35 -0
  14. package/src/assets/images/photo-forest-path.svg +31 -0
  15. package/src/assets/images/photo-lighthouse.svg +42 -0
  16. package/src/assets/images/photo-palm.svg +27 -0
  17. package/src/assets/images/photo-peak.svg +27 -0
  18. package/src/assets/images/photo-tower.svg +34 -0
  19. package/src/assets/images/photo-waterfall.svg +32 -0
  20. package/src/assets/images/story-aurora.svg +36 -0
  21. package/src/assets/images/story-beach.svg +34 -0
  22. package/src/assets/images/story-stars.svg +35 -0
  23. package/src/assets/media/story-sunrise.jpg +0 -0
  24. package/src/assets/media/story-sunrise.mp4 +0 -0
  25. package/src/js/main.js +403 -0
  26. package/src/partials/avatars-container.html +50 -0
  27. package/src/partials/calendar-container.html +36 -38
  28. package/src/partials/feed-container.html +506 -0
  29. package/src/partials/item-lists-container.html +80 -130
  30. package/src/partials/kanban-container.html +17 -294
  31. package/src/partials/mosaic-container.html +173 -0
  32. package/src/partials/panels-container.html +113 -162
  33. package/src/partials/portrait-gallery-container.html +175 -0
  34. package/src/partials/stories-container.html +182 -0
  35. package/src/partials/tables-container.html +21 -40
  36. package/src/scss/abstracts/_mixins.scss +2 -0
  37. package/src/scss/abstracts/_variables.scss +3 -0
  38. package/src/scss/components/_avatars.scss +85 -1
  39. package/src/scss/components/_cards.scss +7 -3
  40. package/src/scss/components/_dropdown.scss +24 -1
  41. package/src/scss/components/_feed.scss +203 -0
  42. package/src/scss/components/_index.scss +6 -0
  43. package/src/scss/components/_lang-switch.scss +4 -8
  44. package/src/scss/components/_lightbox.scss +178 -0
  45. package/src/scss/components/_modals.scss +19 -5
  46. package/src/scss/components/_mosaic.scss +83 -0
  47. package/src/scss/components/_overlay-btn.scss +47 -0
  48. package/src/scss/components/_portrait-gallery.scss +180 -0
  49. package/src/scss/components/_stories.scss +443 -0
  50. package/src/scss/components/_tiles.scss +4 -1
  51. package/src/scss/utilities/_fit.scss +29 -0
  52. package/src/scss/utilities/_index.scss +1 -0
@@ -47,14 +47,10 @@
47
47
  }
48
48
 
49
49
  // A language list is short and its labels are one word - the 200px default
50
- // menu leaves half of it empty. The one place it must not be narrowed is under
51
- // a thumb, where the dropdown widens every menu on purpose.
52
- .mw-lang-switch .mw-dropdown-menu {
53
- min-width: 170px;
54
-
55
- @include touch-context('sm') {
56
- min-width: max(100%, 240px);
57
- }
50
+ // menu leaves half of it empty. Through the dropdown's property, so under a
51
+ // thumb the dropdown's own widening still wins.
52
+ .mw-lang-switch {
53
+ --mw-dropdown-min-width: 170px;
58
54
  }
59
55
 
60
56
  .mw-lang-switch-name {
@@ -0,0 +1,178 @@
1
+ @use '../abstracts' as *;
2
+ @use 'overlay-btn';
3
+
4
+ @layer mw.components {
5
+ dialog.mw-lightbox {
6
+ position: fixed;
7
+ inset: 0;
8
+ width: auto;
9
+ height: auto;
10
+ max-width: none;
11
+ max-height: none;
12
+ margin: 0;
13
+ padding: 0;
14
+ border: none;
15
+ background: $fixed-stage;
16
+ color: $fixed-surface-text;
17
+ overflow: hidden;
18
+ overscroll-behavior: contain;
19
+ // The brand ink disappears on the stage
20
+ --mw-focus-ring-color: #{$fixed-surface-text};
21
+ opacity: 0;
22
+ transition:
23
+ opacity var(--mw-duration-base) var(--mw-ease-out),
24
+ overlay var(--mw-duration-base) allow-discrete,
25
+ display var(--mw-duration-base) allow-discrete;
26
+
27
+ &[open] {
28
+ opacity: 1;
29
+
30
+ @starting-style {
31
+ opacity: 0;
32
+ }
33
+ }
34
+
35
+ &::backdrop {
36
+ background: transparent;
37
+ }
38
+ }
39
+
40
+ // A <dialog> does not lock the page scroll behind it
41
+ :root:has(dialog.mw-lightbox[open]) {
42
+ overflow: hidden;
43
+ scrollbar-gutter: stable; // Or the page shifts by the hidden scrollbar
44
+ }
45
+
46
+ .mw-lightbox-track {
47
+ display: flex;
48
+ height: 100%;
49
+ overflow-x: auto;
50
+ overflow-y: hidden;
51
+ overscroll-behavior: contain;
52
+ scroll-snap-type: x mandatory;
53
+ scrollbar-width: none;
54
+
55
+ &::-webkit-scrollbar {
56
+ display: none;
57
+ }
58
+
59
+ @media (prefers-reduced-motion: no-preference) {
60
+ scroll-behavior: smooth;
61
+ }
62
+ }
63
+
64
+ .mw-lightbox-slide {
65
+ flex: 0 0 100%;
66
+ min-width: 0;
67
+ display: flex;
68
+ flex-direction: column;
69
+ align-items: center;
70
+ justify-content: center;
71
+ gap: spacing('4');
72
+ margin: spacing('0');
73
+ padding: 4.25rem spacing('3')
74
+ calc(#{spacing('6')} + env(safe-area-inset-bottom, 0px));
75
+ scroll-snap-align: center;
76
+ scroll-snap-stop: always;
77
+
78
+ img {
79
+ display: block;
80
+ flex: 0 1 auto;
81
+ min-height: 0;
82
+ max-width: 100%;
83
+ max-height: 100%;
84
+ object-fit: contain;
85
+ border-radius: radius('xs');
86
+ user-select: none;
87
+ }
88
+
89
+ figcaption {
90
+ flex: none;
91
+ max-width: 60ch;
92
+ max-height: 30%;
93
+ overflow-y: auto;
94
+ padding-inline: spacing('3');
95
+ color: rgb(239 239 239 / 88%);
96
+ font-size: font-size('sm');
97
+ line-height: 1.5;
98
+ text-align: center;
99
+
100
+ > * {
101
+ margin: spacing('0');
102
+ }
103
+
104
+ > * + * {
105
+ margin-top: spacing('2');
106
+ }
107
+
108
+ time,
109
+ small {
110
+ display: block;
111
+ color: rgb(239 239 239 / 60%);
112
+ font-size: font-size('xs');
113
+ }
114
+ }
115
+ }
116
+
117
+ // On a phone the arrows sit in the bar, off the photo
118
+ .mw-lightbox-bar {
119
+ position: absolute;
120
+ inset: 0 0 auto;
121
+ z-index: z-index('lift');
122
+ display: flex;
123
+ align-items: center;
124
+ gap: spacing('1');
125
+ padding: max(#{spacing('3')}, env(safe-area-inset-top, 0px)) spacing('3')
126
+ spacing('5');
127
+ background: linear-gradient(rgb(0 0 0 / 55%), transparent);
128
+
129
+ .mw-lightbox-close {
130
+ margin-left: auto;
131
+ }
132
+ }
133
+
134
+ .mw-lightbox-count {
135
+ min-width: 4.5em;
136
+ font-size: font-size('sm');
137
+ font-variant-numeric: tabular-nums;
138
+ text-align: center;
139
+ }
140
+
141
+ @include media-up('md') {
142
+ .mw-lightbox-slide {
143
+ padding-inline: 5.5rem;
144
+ }
145
+
146
+ .mw-lightbox-bar {
147
+ padding-left: spacing('6');
148
+ }
149
+
150
+ .mw-lightbox-count {
151
+ order: -1;
152
+ text-align: left;
153
+ }
154
+
155
+ // Fixed works because the bar has no transform or filter to trap them
156
+ .mw-lightbox-prev,
157
+ .mw-lightbox-next {
158
+ position: fixed;
159
+ top: 50%;
160
+ translate: 0 -50%;
161
+ width: 3.25rem;
162
+ height: 3.25rem;
163
+ background: rgb(255 255 255 / 10%);
164
+
165
+ &:hover {
166
+ background: rgb(255 255 255 / 20%);
167
+ }
168
+ }
169
+
170
+ .mw-lightbox-prev {
171
+ left: spacing('6');
172
+ }
173
+
174
+ .mw-lightbox-next {
175
+ right: spacing('6');
176
+ }
177
+ }
178
+ }
@@ -42,9 +42,10 @@
42
42
  background: var(--mw-card-background);
43
43
  @include surface(0);
44
44
  box-shadow: var(--mw-elevation-5);
45
+ --mw-modal-max-height: 85dvh;
45
46
  max-width: 520px;
46
47
  width: 100%;
47
- max-height: 85dvh;
48
+ max-height: var(--mw-modal-max-height);
48
49
  display: flex;
49
50
  flex-direction: column;
50
51
  z-index: z-index('modal');
@@ -61,15 +62,15 @@
61
62
  // two read as the same modal rendered twice.
62
63
  &.mw-modal-sm {
63
64
  max-width: 370px;
64
- max-height: 80dvh;
65
+ --mw-modal-max-height: 80dvh;
65
66
  }
66
67
  &.mw-modal-lg {
67
68
  max-width: 720px;
68
- max-height: 90dvh;
69
+ --mw-modal-max-height: 90dvh;
69
70
  }
70
71
  &.mw-modal-xl {
71
72
  max-width: 960px;
72
- max-height: 92dvh;
73
+ --mw-modal-max-height: 92dvh;
73
74
  }
74
75
  }
75
76
 
@@ -85,7 +86,7 @@
85
86
  .mw-modal.mw-modal-lg,
86
87
  .mw-modal.mw-modal-xl {
87
88
  max-width: 100%;
88
- max-height: 92dvh;
89
+ --mw-modal-max-height: 92dvh;
89
90
  border-radius: radius('xl') radius('xl') radius('none') radius('none');
90
91
  padding-bottom: env(safe-area-inset-bottom, 0px);
91
92
  // Straight up from the bottom edge - a sheet does not scale in
@@ -173,6 +174,13 @@
173
174
  display var(--mw-duration-base) allow-discrete;
174
175
 
175
176
  &[open] {
177
+ // Not the flex column of the div version: iOS WebKit squeezes a scrolling
178
+ // flex item inside a top-layer dialog to a sliver, so the header shows and
179
+ // the body is gone. Desktop browsers and Playwright's WebKit do not show it.
180
+ // The 1fr row takes what max-height leaves, so the body scrolls in it.
181
+ display: grid;
182
+ grid-template-rows: auto minmax(0, 1fr) auto;
183
+ grid-template-columns: minmax(0, 1fr);
176
184
  opacity: 1;
177
185
  transform: scale(1) translateY(0);
178
186
 
@@ -184,6 +192,12 @@
184
192
  }
185
193
  }
186
194
 
195
+ // Without it an absolute child - screen reader text, a tooltip - anchors to
196
+ // the dialog, escapes the body's scroll and makes the whole dialog scroll
197
+ .mw-modal-body {
198
+ position: relative;
199
+ }
200
+
187
201
  &::backdrop {
188
202
  background: var(--mw-overlay-background);
189
203
  backdrop-filter: blur(3px);
@@ -0,0 +1,83 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @layer mw.components {
4
+ .mw-mosaic {
5
+ display: grid;
6
+ // Two columns on a phone, four at most, in whatever box it sits
7
+ grid-template-columns: repeat(auto-fill, minmax(max(120px, 22%), 1fr));
8
+ // Equal rows sized by the square tiles' aspect-ratio
9
+ grid-auto-rows: 1fr;
10
+ grid-auto-flow: dense;
11
+ // Dense packing reorders the tiles; Tab follows what the eye sees
12
+ reading-flow: grid-rows;
13
+ gap: spacing('4');
14
+ }
15
+
16
+ .mw-mosaic-gap-sm {
17
+ gap: spacing('1');
18
+ }
19
+
20
+ .mw-mosaic-gap-lg {
21
+ gap: spacing('5');
22
+ }
23
+
24
+ .mw-mosaic-item {
25
+ position: relative;
26
+ margin: spacing('0');
27
+ // Only sizes the rows; stretched both ways, or the ratio sizes the tile
28
+ aspect-ratio: 1;
29
+ place-self: stretch;
30
+ overflow: hidden;
31
+ border-radius: radius('sm');
32
+ background: var(--mw-card-background);
33
+ transition: transform var(--mw-duration-fast) var(--mw-ease-out);
34
+
35
+ > a {
36
+ position: absolute;
37
+ inset: 0;
38
+ display: block;
39
+ @include tappable;
40
+ @include focus-ring-inset(-3px);
41
+ }
42
+
43
+ img {
44
+ display: block;
45
+ width: 100%;
46
+ height: 100%;
47
+ object-fit: cover;
48
+ transition: transform var(--mw-duration-zoom) var(--mw-ease-out);
49
+ }
50
+
51
+ > figcaption {
52
+ display: none;
53
+ }
54
+
55
+ @include hover-move {
56
+ img {
57
+ transform: scale(1.05);
58
+ }
59
+ }
60
+
61
+ &:has(> a:active) {
62
+ transform: scale(0.97);
63
+ transition-duration: var(--mw-duration-instant);
64
+ }
65
+ }
66
+
67
+ // Picked by the script from the photo's shape unless one is set by hand
68
+ .mw-mosaic-wide {
69
+ grid-column: span 2;
70
+ aspect-ratio: 2;
71
+ }
72
+
73
+ .mw-mosaic-tall {
74
+ grid-row: span 2;
75
+ aspect-ratio: 1 / 2;
76
+ }
77
+
78
+ .mw-mosaic-big {
79
+ grid-column: span 2;
80
+ grid-row: span 2;
81
+ aspect-ratio: 1;
82
+ }
83
+ }
@@ -0,0 +1,47 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @layer mw.components {
4
+ .mw-overlay-btn {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ flex: none;
9
+ width: 2.75rem;
10
+ height: 2.75rem;
11
+ padding: spacing('0');
12
+ border: none;
13
+ border-radius: radius('full');
14
+ background: rgb(0 0 0 / 38%);
15
+ color: $fixed-surface-text;
16
+ font-size: font-size('md');
17
+ line-height: 1;
18
+ cursor: pointer;
19
+ transition: var(--mw-transition-fast);
20
+ @include tappable;
21
+ @include focus-ring;
22
+
23
+ svg {
24
+ width: 1.375rem;
25
+ height: 1.375rem;
26
+ fill: none;
27
+ stroke: currentColor;
28
+ stroke-width: 2;
29
+ stroke-linecap: round;
30
+ stroke-linejoin: round;
31
+ }
32
+
33
+ &:hover {
34
+ background: rgb(0 0 0 / 62%);
35
+ }
36
+
37
+ &:active {
38
+ transform: scale(0.9);
39
+ transition-duration: var(--mw-duration-instant);
40
+ }
41
+
42
+ &:disabled {
43
+ opacity: 0;
44
+ pointer-events: none;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,180 @@
1
+ @use '../abstracts' as *;
2
+ @use 'overlay-btn';
3
+
4
+ @layer mw.components {
5
+ $two-up: 560px;
6
+ $three-up: 880px;
7
+ $gaps: (
8
+ '': spacing('6'),
9
+ '-gap-sm': spacing('3'),
10
+ '-gap-lg': spacing('7'),
11
+ );
12
+
13
+ // On a landscape phone the width follows from this cap
14
+ $max-height: 78svh;
15
+
16
+ .mw-portrait-gallery {
17
+ position: relative;
18
+ // Inline-size containment would collapse the box in a flex or grid slot
19
+ width: 100%;
20
+ container: mw-portrait-gallery / inline-size;
21
+ }
22
+
23
+ .mw-portrait-gallery-track {
24
+ display: flex;
25
+ overflow-x: auto;
26
+ overscroll-behavior-x: contain;
27
+ scroll-snap-type: x mandatory;
28
+ border-radius: radius('lg');
29
+ scrollbar-width: none;
30
+ @include focus-ring;
31
+
32
+ &::-webkit-scrollbar {
33
+ display: none;
34
+ }
35
+
36
+ @media (prefers-reduced-motion: no-preference) {
37
+ scroll-behavior: smooth;
38
+ }
39
+ }
40
+
41
+ .mw-portrait-gallery-slide {
42
+ position: relative;
43
+ flex: none;
44
+ // The next slide peeks in at the edge
45
+ width: min(86cqi, $max-height * 3 / 4);
46
+ aspect-ratio: 3 / 4;
47
+ margin: spacing('0');
48
+ overflow: hidden;
49
+ border-radius: radius('lg');
50
+ background: $fixed-stage;
51
+ scroll-snap-align: start;
52
+
53
+ > img,
54
+ > video {
55
+ position: absolute;
56
+ inset: 0;
57
+ width: 100%;
58
+ height: 100%;
59
+ object-fit: cover;
60
+ display: block;
61
+ }
62
+
63
+ > figcaption {
64
+ position: absolute;
65
+ inset: auto 0 0;
66
+ padding: spacing('10') spacing('5') spacing('5');
67
+ background: linear-gradient(to top, rgb(0 0 0 / 72%), transparent);
68
+ color: $fixed-surface-text;
69
+ font-size: font-size('sm');
70
+ line-height: 1.45;
71
+ text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
72
+
73
+ strong {
74
+ display: block;
75
+ font-size: font-size('md');
76
+ }
77
+ }
78
+ }
79
+
80
+ .mw-portrait-gallery-tall .mw-portrait-gallery-slide {
81
+ width: min(86cqi, $max-height * 9 / 16);
82
+ aspect-ratio: 9 / 16;
83
+ }
84
+
85
+ .mw-portrait-gallery-dots {
86
+ display: flex;
87
+ justify-content: center;
88
+ gap: spacing('4');
89
+ margin-top: spacing('5');
90
+ }
91
+
92
+ .mw-portrait-gallery-dot {
93
+ width: 7px;
94
+ height: 7px;
95
+ padding: spacing('0');
96
+ border: none;
97
+ border-radius: radius('full');
98
+ background: var(--mw-gray-color);
99
+ cursor: pointer;
100
+ transition: var(--mw-transition-fast);
101
+ @include tappable;
102
+ @include hit-area(6px);
103
+ @include focus-ring;
104
+
105
+ &.mw-active {
106
+ background: var(--mw-primary-text-color);
107
+ transform: scale(1.45);
108
+ }
109
+ }
110
+
111
+ @each $suffix, $gap in $gaps {
112
+ .mw-portrait-gallery#{$suffix} .mw-portrait-gallery-track {
113
+ gap: $gap;
114
+ }
115
+ }
116
+
117
+ @container mw-portrait-gallery (width >= #{$two-up}) {
118
+ .mw-portrait-gallery-dots {
119
+ display: none;
120
+ }
121
+ }
122
+
123
+ // The widths subtract the gaps, so each gap restates them - every two-up
124
+ // rule before every three-up one, or a modifier's two-up rule would win
125
+ @each $up, $share, $gaps-in-row in ($two-up 2.25 1, $three-up 3 2) {
126
+ @container mw-portrait-gallery (width >= #{$up}) {
127
+ @each $suffix, $gap in $gaps {
128
+ $room: calc(100cqi - $gaps-in-row * $gap);
129
+
130
+ .mw-portrait-gallery#{$suffix} .mw-portrait-gallery-slide {
131
+ width: min($room / $share, $max-height * 3 / 4);
132
+ }
133
+
134
+ .mw-portrait-gallery#{$suffix}.mw-portrait-gallery-tall
135
+ .mw-portrait-gallery-slide {
136
+ width: min($room / $share, $max-height * 9 / 16);
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // A finger swipes, so the arrows are for a mouse only
143
+ .mw-portrait-gallery-prev,
144
+ .mw-portrait-gallery-next {
145
+ display: none;
146
+ position: absolute;
147
+ top: 50%;
148
+ translate: 0 -50%;
149
+ z-index: z-index('lift');
150
+
151
+ @media (hover: hover) and (pointer: fine) {
152
+ display: inline-flex;
153
+ }
154
+ }
155
+
156
+ .mw-portrait-gallery-prev {
157
+ left: spacing('4');
158
+ }
159
+
160
+ .mw-portrait-gallery-next {
161
+ right: spacing('4');
162
+ }
163
+
164
+ // Centred on the photos, not on photos plus the dot row
165
+ .mw-portrait-gallery:has(.mw-portrait-gallery-dots) {
166
+ .mw-portrait-gallery-prev,
167
+ .mw-portrait-gallery-next {
168
+ translate: 0 calc(-50% - #{spacing('5')});
169
+ }
170
+ }
171
+
172
+ @container mw-portrait-gallery (width >= #{$two-up}) {
173
+ .mw-portrait-gallery:has(.mw-portrait-gallery-dots) {
174
+ .mw-portrait-gallery-prev,
175
+ .mw-portrait-gallery-next {
176
+ translate: 0 -50%;
177
+ }
178
+ }
179
+ }
180
+ }