maverick-wave 5.27.0 → 5.29.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/mw-maverick-wave/SKILL.md +4 -2
  2. package/.claude/skills/mw-maverick-wave/references/components.md +212 -0
  3. package/.claude/skills/mw-maverick-wave/references/javascript.md +6 -1
  4. package/.claude/skills/mw-maverick-wave/references/theming.md +53 -51
  5. package/CHANGELOG.md +17 -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 +4 -4
  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 +411 -8
  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 +9 -0
  38. package/src/scss/components/_avatars.scss +52 -0
  39. package/src/scss/components/_cards.scss +7 -3
  40. package/src/scss/components/_feed.scss +203 -0
  41. package/src/scss/components/_hero-badge.scss +48 -0
  42. package/src/scss/components/_index.scss +8 -0
  43. package/src/scss/components/_leader-row.scss +24 -18
  44. package/src/scss/components/_lightbox.scss +178 -0
  45. package/src/scss/components/_mosaic.scss +83 -0
  46. package/src/scss/components/_overlay-btn.scss +47 -0
  47. package/src/scss/components/_portrait-gallery.scss +180 -0
  48. package/src/scss/components/_stories.scss +443 -0
  49. package/src/scss/components/_tiles.scss +4 -1
  50. package/src/scss/components/_tooltip.scss +193 -0
  51. package/src/scss/layout/_footer.scss +46 -84
  52. package/src/scss/layout/_grid.scss +48 -45
  53. package/src/scss/layout/_header-reveal.scss +1 -1
  54. package/src/scss/layout/_header.scss +49 -66
  55. package/src/scss/layout/_home.scss +5 -2
  56. package/src/scss/layout/_main.scss +100 -337
  57. package/src/scss/layout/_parallax.scss +1 -1
  58. package/src/scss/layout/_section.scss +24 -17
  59. package/src/scss/utilities/_fit.scss +29 -0
  60. 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,193 @@
1
+ @use 'sass:math';
2
+ @use '../abstracts' as *;
3
+
4
+ @layer mw.components {
5
+ // A pseudo element on the trigger, so an ancestor with `overflow: hidden` cuts
6
+ // it off. Hidden means `display: none`: a laid-out box still counts towards the
7
+ // page's overflow, and a 280px bubble near the right edge added 50px of sideways
8
+ // scroll on a phone. Hence `allow-discrete` and the starting style.
9
+ $_mw-tooltip-arrow: 6px;
10
+ $_mw-tooltip-gap: 8px;
11
+ // A square turned 45deg rather than a border triangle: it reads the same from
12
+ // above and from below, so the second placement costs no second arrow and a
13
+ // flip never has to repaint anything. The diagonal carries the visible width,
14
+ // so the side is the visible height doubled over root two.
15
+ $_mw-tooltip-arrow-side: math.div($_mw-tooltip-arrow * 2, math.sqrt(2));
16
+ // Half a side short of where the triangle's edge sat, so the turned square is
17
+ // centred on the bubble's edge and only its outer half shows. The inner half
18
+ // lands on the bubble in the same colour, which is what makes it read as one
19
+ // shape rather than a diamond stuck to a box.
20
+ $_mw-tooltip-arrow-inset: $_mw-tooltip-gap +
21
+ $_mw-tooltip-arrow - math.div($_mw-tooltip-arrow-side, 2);
22
+
23
+ [data-tooltip] {
24
+ position: relative;
25
+
26
+ &::before {
27
+ content: attr(data-tooltip);
28
+ position: absolute;
29
+ bottom: 100%;
30
+ left: 50%;
31
+ transform: translateX(-50%);
32
+ margin-bottom: $_mw-tooltip-gap + $_mw-tooltip-arrow;
33
+ max-width: min(280px, 90vw);
34
+ width: max-content;
35
+ padding: spacing('1') spacing('2');
36
+ background: var(--mw-gray-color);
37
+ color: $fixed-surface-text;
38
+ font-size: font-size('xs');
39
+ border-radius: radius('xs');
40
+ box-shadow: var(--mw-elevation-2);
41
+ pointer-events: none;
42
+ opacity: 0;
43
+ display: none;
44
+ transition:
45
+ opacity var(--mw-duration-fast) var(--mw-ease-out),
46
+ display var(--mw-duration-fast) allow-discrete;
47
+ z-index: z-index('tooltip');
48
+ }
49
+
50
+ &::after {
51
+ content: '';
52
+ position: absolute;
53
+ bottom: 100%;
54
+ left: 50%;
55
+ transform: translateX(-50%) rotate(45deg);
56
+ margin-bottom: $_mw-tooltip-arrow-inset;
57
+ width: $_mw-tooltip-arrow-side;
58
+ height: $_mw-tooltip-arrow-side;
59
+ background: var(--mw-gray-color);
60
+ opacity: 0;
61
+ display: none;
62
+ transition:
63
+ opacity var(--mw-duration-fast) var(--mw-ease-out),
64
+ display var(--mw-duration-fast) allow-discrete;
65
+ z-index: z-index('tooltip');
66
+ }
67
+
68
+ // The placement API: the text is the attribute, the side is a class. The
69
+ // three steer different properties - block axis, inline axis - so `below`
70
+ // and `end` compose without a rule for the pairing.
71
+ //
72
+ // They place the bubble against the trigger rather than joining the anchored
73
+ // path below, and that is the point, not a shortcut. The reason to want them
74
+ // is a control in a fixed header, and that header carries a backdrop-filter,
75
+ // which makes it the containing block for anything `fixed` inside it.
76
+ // `block-end` is then the few pixels between the trigger and the header's own
77
+ // bottom edge, the bubble does not fit, and it gets pushed back over the
78
+ // trigger it belongs to. What this gives up is the clipping immunity the
79
+ // anchored path buys - a modifier inside a scroll container is cut off.
80
+ //
81
+ // Opting out has to restore the placement, not just the positioning scheme:
82
+ // the anchored path below is the later rule and clears `bottom`, `top`,
83
+ // `left` and the centring transform so `position-area` can resolve them
84
+ // itself. Leave that cleared and the box falls to its static position, which
85
+ // for a pseudo element is inside the trigger it is supposed to sit above.
86
+ // The three rules under this one then move the one edge each of them owns.
87
+ &:is(.mw-tooltip-below, .mw-tooltip-end, .mw-tooltip-start) {
88
+ &::before,
89
+ &::after {
90
+ position: absolute;
91
+ position-area: none;
92
+ bottom: 100%;
93
+ top: auto;
94
+ left: 50%;
95
+ }
96
+
97
+ &::before {
98
+ transform: translateX(-50%);
99
+ }
100
+
101
+ &::after {
102
+ transform: translateX(-50%) rotate(45deg);
103
+ }
104
+ }
105
+
106
+ &.mw-tooltip-below {
107
+ &::before {
108
+ top: 100%;
109
+ bottom: auto;
110
+ margin-top: $_mw-tooltip-gap + $_mw-tooltip-arrow;
111
+ margin-bottom: 0;
112
+ }
113
+
114
+ &::after {
115
+ top: 100%;
116
+ bottom: auto;
117
+ margin-top: $_mw-tooltip-arrow-inset;
118
+ margin-bottom: 0;
119
+ }
120
+ }
121
+
122
+ // Only the bubble moves - the arrow stays centred on the trigger it points at.
123
+ &.mw-tooltip-end::before {
124
+ left: auto;
125
+ right: 0;
126
+ transform: none;
127
+ }
128
+
129
+ &.mw-tooltip-start::before {
130
+ left: 0;
131
+ right: auto;
132
+ transform: none;
133
+ }
134
+
135
+ &:hover::before,
136
+ &:hover::after,
137
+ &:focus-visible::before,
138
+ &:focus-visible::after {
139
+ opacity: 1;
140
+ display: block;
141
+
142
+ @starting-style {
143
+ opacity: 0;
144
+ }
145
+ }
146
+
147
+ // A press usually scales the trigger, and a transform makes it a containing
148
+ // block for the fixed-position bubble above - which would drop the label
149
+ // into the element being pressed. So it steps aside for the click instead;
150
+ // by then it has been read anyway. After the hover rule, same weight.
151
+ &:active::before,
152
+ &:active::after {
153
+ opacity: 0;
154
+ display: none;
155
+ // Not transitioned: `allow-discrete` holds `display: block` until the fade
156
+ // ends, and the bubble would spend those 180ms visibly sitting in the
157
+ // element. Going back out on release keeps the fade - that one is wanted.
158
+ transition: none;
159
+ }
160
+
161
+ // The clipping caveat above, lifted where the browser can do it: `fixed` takes
162
+ // the bubble out of the trigger's containing block, anchor positioning keeps
163
+ // it attached. No `anchor-name` needed - a pseudo element is implicitly
164
+ // anchored to the element it comes from.
165
+ //
166
+ // Still no `position-try-fallbacks: flip-block`, and no longer because of the
167
+ // arrow - the turned square survives a flip. Bubble and arrow are two
168
+ // separately anchored boxes, so each would decide on its own whether it fits,
169
+ // and the tiny one nearly always does: the label flips, the arrow stays.
170
+ @supports (position-area: block-start) {
171
+ &::before,
172
+ &::after {
173
+ position: fixed;
174
+ // The absolute placement these replace - left as-is they fight the
175
+ // position-area, which resolves the inset properties itself.
176
+ bottom: auto;
177
+ left: auto;
178
+ top: auto;
179
+ }
180
+
181
+ &::before {
182
+ position-area: block-start;
183
+ transform: none;
184
+ }
185
+
186
+ // The turn stays - it is the arrow's shape, not its placement.
187
+ &::after {
188
+ position-area: block-start;
189
+ transform: rotate(45deg);
190
+ }
191
+ }
192
+ }
193
+ }