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
@@ -0,0 +1,443 @@
1
+ @use '../abstracts' as *;
2
+ @use 'overlay-btn';
3
+
4
+ @layer mw.components {
5
+ $frame-height: min(92dvh, 56rem);
6
+ $frame-time: 5s;
7
+ // Frames per story the timing is written out for
8
+ $max-frames: 12;
9
+
10
+ // A bar cut into as many segments as the story has frames
11
+ @function segments($color, $count) {
12
+ @return repeating-linear-gradient(
13
+ to right,
14
+ $color 0 calc(100% / #{$count} - 3px),
15
+ transparent 0 calc(100% / #{$count})
16
+ );
17
+ }
18
+
19
+ .mw-stories {
20
+ display: flex;
21
+ gap: spacing('5');
22
+ overflow-x: auto;
23
+ overscroll-behavior-x: contain;
24
+ padding-block: spacing('2');
25
+ scroll-snap-type: x proximity;
26
+ scrollbar-width: none;
27
+ @include scroll-hint;
28
+
29
+ &::-webkit-scrollbar {
30
+ display: none;
31
+ }
32
+
33
+ // A finger swipes the row, a mouse wheel cannot - it gets the bar
34
+ @media (hover: hover) and (pointer: fine) {
35
+ scrollbar-width: thin;
36
+ }
37
+ }
38
+
39
+ .mw-story {
40
+ flex: none;
41
+ display: inline-flex;
42
+ flex-direction: column;
43
+ align-items: center;
44
+ gap: spacing('2');
45
+ width: 4.75rem;
46
+ padding: spacing('0');
47
+ border: none;
48
+ border-radius: radius('md');
49
+ background: none;
50
+ color: inherit;
51
+ font: inherit;
52
+ cursor: pointer;
53
+ scroll-snap-align: start;
54
+ @include tappable;
55
+ @include focus-ring;
56
+
57
+ &:active .mw-story-cover {
58
+ transform: scale(0.93);
59
+ transition-duration: var(--mw-duration-instant);
60
+ }
61
+ }
62
+
63
+ .mw-story-cover {
64
+ display: block;
65
+ width: 4.25rem;
66
+ height: 4.25rem;
67
+ padding: 3px;
68
+ border-radius: radius('full');
69
+ // The ink tones, not the fills: the ring has to carry on a dark page too
70
+ background: linear-gradient(
71
+ 45deg,
72
+ var(--mw-secondary-text-color),
73
+ var(--mw-primary-text-color)
74
+ );
75
+ transition: transform var(--mw-duration-fast) var(--mw-ease-out);
76
+
77
+ img {
78
+ display: block;
79
+ width: 100%;
80
+ height: 100%;
81
+ // The token that follows the surface - the page, or a card around the row
82
+ border: 2px solid var(--mw-scroll-hint-cover, var(--mw-page-background));
83
+ border-radius: radius('full');
84
+ object-fit: cover;
85
+ }
86
+ }
87
+
88
+ .mw-story-label {
89
+ max-width: 100%;
90
+ font-size: font-size('xs');
91
+ @include truncate;
92
+ }
93
+
94
+ // A vertical snap strip: swipe, wheel and arrow keys page through it natively
95
+ .mw-story-reel {
96
+ // Not only the UA rule, or a browser without popovers shows it in the row
97
+ display: none;
98
+ width: 100%;
99
+ height: 100%;
100
+ max-width: none;
101
+ max-height: none;
102
+ margin: 0;
103
+ padding: spacing('0');
104
+ border: none;
105
+ overflow-y: auto;
106
+ overscroll-behavior: contain;
107
+ scroll-snap-type: y mandatory;
108
+ scrollbar-width: none;
109
+ background: #000;
110
+ color: $fixed-surface-text;
111
+ --mw-focus-ring-color: #{$fixed-surface-text};
112
+ // A long press pauses, so it must not open the image menu as well
113
+ user-select: none;
114
+ -webkit-touch-callout: none;
115
+ opacity: 0;
116
+ transition:
117
+ opacity var(--mw-duration-base) var(--mw-ease-out),
118
+ overlay var(--mw-duration-base) allow-discrete,
119
+ display var(--mw-duration-base) allow-discrete;
120
+
121
+ &::-webkit-scrollbar {
122
+ display: none;
123
+ }
124
+
125
+ &:popover-open {
126
+ display: block;
127
+ opacity: 1;
128
+
129
+ @starting-style {
130
+ opacity: 0;
131
+ }
132
+ }
133
+
134
+ &::backdrop {
135
+ background: rgb(0 0 0 / 88%);
136
+ opacity: 0;
137
+ transition:
138
+ opacity var(--mw-duration-base) var(--mw-ease-out),
139
+ overlay var(--mw-duration-base) allow-discrete,
140
+ display var(--mw-duration-base) allow-discrete;
141
+ }
142
+
143
+ &:popover-open::backdrop {
144
+ opacity: 1;
145
+
146
+ @starting-style {
147
+ opacity: 0;
148
+ }
149
+ }
150
+
151
+ > img,
152
+ > video {
153
+ display: block;
154
+ width: 100%;
155
+ height: 100%;
156
+ object-fit: cover;
157
+ scroll-snap-align: start;
158
+ scroll-snap-stop: always;
159
+ }
160
+
161
+ // Wider than a phone held upright: the story keeps its phone shape
162
+ @include media-up('sm') {
163
+ width: calc(#{$frame-height} * 9 / 16);
164
+ height: $frame-height;
165
+ margin: auto;
166
+ border-radius: radius('lg');
167
+ }
168
+ }
169
+
170
+ .mw-story-reel-head {
171
+ position: sticky;
172
+ top: 0;
173
+ z-index: 2;
174
+ display: flex;
175
+ align-items: flex-start;
176
+ gap: spacing('1');
177
+ height: 5rem;
178
+ // Laid over the first frame instead of pushing it down
179
+ margin-bottom: -5rem;
180
+ padding: calc(max(#{spacing('3')}, env(safe-area-inset-top, 0px)) + 0.75rem)
181
+ spacing('3') 0 spacing('5');
182
+ background: linear-gradient(rgb(0 0 0 / 55%), transparent);
183
+ font-size: font-size('sm');
184
+ font-weight: font-weight('bold');
185
+ text-shadow: 0 1px 3px rgb(0 0 0 / 60%);
186
+
187
+ > span {
188
+ flex: 1;
189
+ min-width: 0;
190
+ @include truncate;
191
+ }
192
+
193
+ .mw-overlay-btn {
194
+ margin-top: -#{spacing('3')};
195
+ background: transparent;
196
+ }
197
+
198
+ // The progress bar: the track and the fill drawn over it
199
+ &::before,
200
+ &::after {
201
+ content: '';
202
+ position: absolute;
203
+ top: max(#{spacing('3')}, env(safe-area-inset-top, 0px));
204
+ left: spacing('3');
205
+ right: spacing('3');
206
+ height: 2px;
207
+ border-radius: 2px;
208
+ }
209
+
210
+ &::before {
211
+ background: rgb(255 255 255 / 35%);
212
+ }
213
+
214
+ &::after {
215
+ background: $fixed-surface-text;
216
+ }
217
+ }
218
+
219
+ // A checkbox, so pausing needs no script: two bars, a triangle once paused
220
+ .mw-story-reel-pause {
221
+ position: relative;
222
+
223
+ input {
224
+ position: absolute;
225
+ inset: 0;
226
+ margin: 0;
227
+ opacity: 0;
228
+ cursor: pointer;
229
+ }
230
+
231
+ &:has(:focus-visible) {
232
+ outline: var(--mw-focus-ring-width) solid var(--mw-focus-ring-color);
233
+ outline-offset: var(--mw-focus-ring-offset);
234
+ }
235
+
236
+ &::before {
237
+ content: '';
238
+ width: 10px;
239
+ height: 13px;
240
+ border-inline: 3px solid currentColor;
241
+ }
242
+
243
+ &:has(:checked)::before {
244
+ width: 0;
245
+ height: 0;
246
+ margin-left: 3px;
247
+ border-inline: 0;
248
+ border-left: 11px solid currentColor;
249
+ border-block: 7px solid transparent;
250
+ }
251
+ }
252
+
253
+ // Built by the script: left a third back, the rest on
254
+ .mw-story-reel-prev,
255
+ .mw-story-reel-next {
256
+ position: absolute;
257
+ top: 0;
258
+ bottom: 0;
259
+ z-index: 1;
260
+ display: flex;
261
+ align-items: center;
262
+ padding: spacing('0') spacing('3');
263
+ border: none;
264
+ background: none;
265
+ color: $fixed-surface-text;
266
+ cursor: pointer;
267
+ @include tappable;
268
+ @include focus-ring-inset(-4px);
269
+
270
+ svg {
271
+ width: 1.75rem;
272
+ height: 1.75rem;
273
+ fill: none;
274
+ stroke: currentColor;
275
+ stroke-width: 2;
276
+ stroke-linecap: round;
277
+ stroke-linejoin: round;
278
+ opacity: 0;
279
+ filter: drop-shadow(0 1px 2px rgb(0 0 0 / 60%));
280
+ transition: opacity var(--mw-duration-fast) var(--mw-ease-out);
281
+ }
282
+
283
+ &:focus-visible svg {
284
+ opacity: 1;
285
+ }
286
+
287
+ // A finger taps the halves blind; a mouse gets the arrows to aim at
288
+ @media (hover: hover) and (pointer: fine) {
289
+ svg {
290
+ opacity: 0.7;
291
+ }
292
+
293
+ &:hover svg {
294
+ opacity: 1;
295
+ }
296
+ }
297
+ }
298
+
299
+ .mw-story-reel-prev {
300
+ left: 0;
301
+ width: 30%;
302
+ }
303
+
304
+ .mw-story-reel-next {
305
+ right: 0;
306
+ width: 70%;
307
+ justify-content: flex-end;
308
+ }
309
+
310
+ // Longhands only in this file: the shorthand would reset the scroll timeline
311
+ @media (prefers-reduced-motion: no-preference) {
312
+ .mw-story-reel {
313
+ overflow: hidden;
314
+ scroll-snap-type: none;
315
+
316
+ > img,
317
+ > video {
318
+ position: absolute;
319
+ inset: 0;
320
+ }
321
+
322
+ > :nth-child(n + 2 of img, video) {
323
+ visibility: hidden;
324
+ animation-name: mw-story-reel-frame;
325
+ animation-duration: 1ms;
326
+ animation-timing-function: step-end;
327
+ animation-fill-mode: forwards;
328
+ }
329
+
330
+ @for $frame from 2 through $max-frames {
331
+ > :nth-child(#{$frame} of img, video) {
332
+ animation-delay: ($frame - 1) * $frame-time;
333
+ }
334
+ }
335
+
336
+ // Untimed, they would show at once on top of the first
337
+ > :nth-child(n + #{$max-frames + 1} of img, video) {
338
+ display: none;
339
+ }
340
+ }
341
+
342
+ .mw-story-reel-head::after {
343
+ clip-path: inset(0 100% 0 0);
344
+ animation-name: mw-story-reel-fill;
345
+ animation-duration: $frame-time;
346
+ animation-timing-function: linear;
347
+ animation-fill-mode: forwards;
348
+ }
349
+
350
+ // One segment per frame, and the fill takes as long as all of them
351
+ @for $frames from 2 through $max-frames {
352
+ .mw-story-reel:has(
353
+ > :nth-child(#{$frames} of img, video):nth-last-child(1 of img, video)
354
+ )
355
+ .mw-story-reel-head {
356
+ &::before {
357
+ background: segments(rgb(255 255 255 / 35%), $frames);
358
+ }
359
+
360
+ &::after {
361
+ background: segments($fixed-surface-text, $frames);
362
+ animation-duration: $frames * $frame-time;
363
+ }
364
+ }
365
+ }
366
+
367
+ // A longer story ends at the last timed frame
368
+ .mw-story-reel:has(> :nth-child(#{$max-frames + 1} of img, video))
369
+ .mw-story-reel-head {
370
+ &::before {
371
+ background: segments(rgb(255 255 255 / 35%), $max-frames);
372
+ }
373
+
374
+ &::after {
375
+ background: segments($fixed-surface-text, $max-frames);
376
+ animation-duration: $max-frames * $frame-time;
377
+ }
378
+ }
379
+
380
+ .mw-story-reel:is(:active, :has(.mw-story-reel-pause :checked)) {
381
+ > *,
382
+ .mw-story-reel-head::after {
383
+ animation-play-state: paused;
384
+ }
385
+ }
386
+ }
387
+
388
+ // No autoplay with reduced motion: the frames stay a strip to page through
389
+ @media (prefers-reduced-motion: reduce) {
390
+ .mw-story-reel-pause,
391
+ .mw-story-reel-prev,
392
+ .mw-story-reel-next {
393
+ display: none;
394
+ }
395
+
396
+ .mw-story-reel-head::after {
397
+ transform-origin: left;
398
+ transform: scaleX(0);
399
+ }
400
+
401
+ @supports (animation-timeline: scroll()) {
402
+ .mw-story-reel-head::after {
403
+ animation-name: mw-story-reel-scroll;
404
+ animation-timing-function: linear;
405
+ animation-fill-mode: both;
406
+ animation-timeline: scroll();
407
+ }
408
+ }
409
+
410
+ @supports not (animation-timeline: scroll()) {
411
+ .mw-story-reel-head {
412
+ &::before,
413
+ &::after {
414
+ display: none;
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ @keyframes mw-story-reel-frame {
421
+ to {
422
+ visibility: visible;
423
+ }
424
+ }
425
+
426
+ @keyframes mw-story-reel-fill {
427
+ to {
428
+ clip-path: inset(0);
429
+ }
430
+ }
431
+
432
+ @keyframes mw-story-reel-scroll {
433
+ to {
434
+ transform: scaleX(1);
435
+ }
436
+ }
437
+
438
+ // A popover does not lock the page scroll behind it
439
+ :root:has(.mw-story-reel:popover-open) {
440
+ overflow: hidden;
441
+ scrollbar-gutter: stable; // Or the page shifts by the hidden scrollbar
442
+ }
443
+ }
@@ -41,7 +41,10 @@
41
41
  &:hover {
42
42
  box-shadow: var(--mw-elevation-3);
43
43
  border-color: var(--mw-border-accent);
44
- --mw-corner-accent: var(--mw-corner-accent-hover);
44
+
45
+ &::after {
46
+ --mw-corner-accent: var(--mw-corner-accent-hover);
47
+ }
45
48
  }
46
49
 
47
50
  @include hover-move {
@@ -0,0 +1,29 @@
1
+ @use '../abstracts' as *;
2
+
3
+ @layer mw.utilities {
4
+ // Set by the script when the photo's shape is too far off the frame's
5
+ .mw-fit-contain {
6
+ > img:not(.mw-fit-backdrop),
7
+ > video {
8
+ object-fit: contain;
9
+ }
10
+
11
+ > .mw-fit-backdrop {
12
+ display: block;
13
+ }
14
+ }
15
+
16
+ // Made once and kept; the frame can turn back to a crop on a resize
17
+ .mw-fit-backdrop {
18
+ display: none;
19
+ position: absolute;
20
+ inset: 0;
21
+ width: 100%;
22
+ height: 100%;
23
+ object-fit: cover;
24
+ // Scaled, or the blur fades into a pale rim at the edges
25
+ transform: scale(1.2);
26
+ filter: blur(24px) brightness(0.7) saturate(1.2);
27
+ pointer-events: none;
28
+ }
29
+ }
@@ -3,6 +3,7 @@
3
3
  @forward 'corner';
4
4
  @forward 'display';
5
5
  @forward 'elevation';
6
+ @forward 'fit';
6
7
  @forward 'flex';
7
8
  @forward 'print';
8
9
  @forward 'reveal';