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.
- package/.claude/skills/mw-maverick-wave/SKILL.md +4 -2
- package/.claude/skills/mw-maverick-wave/references/components.md +212 -0
- package/.claude/skills/mw-maverick-wave/references/javascript.md +6 -1
- package/.claude/skills/mw-maverick-wave/references/theming.md +53 -51
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +4 -4
- package/README.md +2 -2
- package/index.html +65 -0
- package/maverick-wave.min.css +4 -4
- package/maverick-wave.min.js +1 -1
- package/package.json +2 -2
- package/scripts/verify.js +2 -1
- package/src/assets/images/photo-balloon.svg +35 -0
- package/src/assets/images/photo-forest-path.svg +31 -0
- package/src/assets/images/photo-lighthouse.svg +42 -0
- package/src/assets/images/photo-palm.svg +27 -0
- package/src/assets/images/photo-peak.svg +27 -0
- package/src/assets/images/photo-tower.svg +34 -0
- package/src/assets/images/photo-waterfall.svg +32 -0
- package/src/assets/images/story-aurora.svg +36 -0
- package/src/assets/images/story-beach.svg +34 -0
- package/src/assets/images/story-stars.svg +35 -0
- package/src/assets/media/story-sunrise.jpg +0 -0
- package/src/assets/media/story-sunrise.mp4 +0 -0
- package/src/js/main.js +411 -8
- package/src/partials/avatars-container.html +50 -0
- package/src/partials/calendar-container.html +36 -38
- package/src/partials/feed-container.html +506 -0
- package/src/partials/item-lists-container.html +80 -130
- package/src/partials/kanban-container.html +17 -294
- package/src/partials/mosaic-container.html +173 -0
- package/src/partials/panels-container.html +113 -162
- package/src/partials/portrait-gallery-container.html +175 -0
- package/src/partials/stories-container.html +182 -0
- package/src/partials/tables-container.html +21 -40
- package/src/scss/abstracts/_mixins.scss +2 -0
- package/src/scss/abstracts/_variables.scss +9 -0
- package/src/scss/components/_avatars.scss +52 -0
- package/src/scss/components/_cards.scss +7 -3
- package/src/scss/components/_feed.scss +203 -0
- package/src/scss/components/_hero-badge.scss +48 -0
- package/src/scss/components/_index.scss +8 -0
- package/src/scss/components/_leader-row.scss +24 -18
- package/src/scss/components/_lightbox.scss +178 -0
- package/src/scss/components/_mosaic.scss +83 -0
- package/src/scss/components/_overlay-btn.scss +47 -0
- package/src/scss/components/_portrait-gallery.scss +180 -0
- package/src/scss/components/_stories.scss +443 -0
- package/src/scss/components/_tiles.scss +4 -1
- package/src/scss/components/_tooltip.scss +193 -0
- package/src/scss/layout/_footer.scss +46 -84
- package/src/scss/layout/_grid.scss +48 -45
- package/src/scss/layout/_header-reveal.scss +1 -1
- package/src/scss/layout/_header.scss +49 -66
- package/src/scss/layout/_home.scss +5 -2
- package/src/scss/layout/_main.scss +100 -337
- package/src/scss/layout/_parallax.scss +1 -1
- package/src/scss/layout/_section.scss +24 -17
- package/src/scss/utilities/_fit.scss +29 -0
- package/src/scss/utilities/_index.scss +1 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
@use 'sass:math';
|
|
2
1
|
@use '../abstracts' as *;
|
|
3
2
|
|
|
4
3
|
@layer mw.layout {
|
|
@@ -34,182 +33,135 @@
|
|
|
34
33
|
margin: spacing('0') auto;
|
|
35
34
|
padding: spacing('0');
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
&:has(.mw-hero) {
|
|
37
|
+
position: relative;
|
|
38
|
+
width: 100%;
|
|
39
|
+
min-height: 100dvh;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
background-size: cover;
|
|
44
|
+
background-position: center;
|
|
45
|
+
background-image: var(--mw-hero-background);
|
|
46
|
+
|
|
47
|
+
// Set on the container so a hero without .mw-home gets it too
|
|
48
|
+
color: var(--mw-hero-text-color);
|
|
49
|
+
|
|
50
|
+
// backdrop-filter and not an overlay - see $hero-filter-dark. On the pseudo
|
|
51
|
+
// element, so the content in front of the image stays unfiltered.
|
|
52
|
+
&::before {
|
|
53
|
+
content: '';
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 0;
|
|
56
|
+
backdrop-filter: var(--mw-hero-image-filter);
|
|
57
|
+
z-index: z-index('lift');
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
transition: var(--mw-transition);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ...so the content has to sit above it - except the parallax layer,
|
|
63
|
+
// which is the one thing that belongs underneath
|
|
64
|
+
> *:not(.mw-parallax-media) {
|
|
39
65
|
position: relative;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
display: flex;
|
|
43
|
-
align-items: center;
|
|
44
|
-
justify-content: center;
|
|
45
|
-
background-size: cover;
|
|
46
|
-
background-position: center;
|
|
47
|
-
background-image: var(--mw-hero-background);
|
|
66
|
+
z-index: z-index('card');
|
|
67
|
+
}
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
// Add `mw-parallax` here and a `.mw-parallax-media` child and the picture
|
|
70
|
+
// becomes its own layer, free to drift or to stay pinned. It reads the
|
|
71
|
+
// same token, so the image is still configured in one place - and a
|
|
72
|
+
// second media element carrying `mw-parallax-pattern` takes the other.
|
|
73
|
+
&:has(> .mw-parallax-media) {
|
|
74
|
+
background-image: none;
|
|
51
75
|
|
|
52
|
-
|
|
53
|
-
// element, so the content in front of the image stays unfiltered.
|
|
54
|
-
&::before {
|
|
55
|
-
content: '';
|
|
56
|
-
position: absolute;
|
|
57
|
-
inset: 0;
|
|
58
|
-
backdrop-filter: var(--mw-hero-image-filter);
|
|
59
|
-
z-index: z-index('lift');
|
|
60
|
-
pointer-events: none;
|
|
61
|
-
transition: var(--mw-transition);
|
|
62
|
-
}
|
|
76
|
+
--mw-parallax-pattern-image: var(--mw-hero-pattern);
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
> *:not(.mw-parallax-media) {
|
|
67
|
-
position: relative;
|
|
68
|
-
z-index: z-index('card');
|
|
78
|
+
> .mw-parallax-media:not(.mw-parallax-pattern) {
|
|
79
|
+
background-image: var(--mw-hero-background);
|
|
69
80
|
}
|
|
81
|
+
}
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
// The same shadow the scroll cue already carries: the light theme lifts
|
|
84
|
+
// the photo to brightness(1.1) and pale ink alone stops holding its edge
|
|
85
|
+
h1,
|
|
86
|
+
h2,
|
|
87
|
+
h3,
|
|
88
|
+
p {
|
|
89
|
+
text-shadow: 0 1px 4px rgb(0 0 0 / 65%);
|
|
90
|
+
}
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
// ...except anything that paints its own surface: a card in the hero is
|
|
93
|
+
// still a card, or its text is light on a light card in the light theme
|
|
94
|
+
.mw-card,
|
|
95
|
+
.mw-panel,
|
|
96
|
+
.mw-modal,
|
|
97
|
+
.mw-tile,
|
|
98
|
+
.mw-calendar:not(.mw-calendar-plain) {
|
|
99
|
+
color: var(--mw-text-color);
|
|
84
100
|
|
|
85
|
-
// The same shadow the scroll cue already carries: the light theme lifts
|
|
86
|
-
// the photo to brightness(1.1) and pale ink alone stops holding its edge
|
|
87
101
|
h1,
|
|
88
102
|
h2,
|
|
89
103
|
h3,
|
|
90
104
|
p {
|
|
91
|
-
text-shadow:
|
|
105
|
+
text-shadow: none;
|
|
92
106
|
}
|
|
107
|
+
}
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
// ...and except the outline button, for the opposite reason: its tone is
|
|
110
|
+
// picked to read on the page background and goes quiet over a photograph.
|
|
111
|
+
// The hover is restated because this selector is exactly as specific as
|
|
112
|
+
// `.mw-btn-outline:hover:not(:disabled)` and the layer order beats it.
|
|
113
|
+
.mw-btn-outline {
|
|
114
|
+
border-color: color-mix(
|
|
115
|
+
in srgb,
|
|
116
|
+
var(--mw-hero-text-color) 45%,
|
|
117
|
+
transparent
|
|
118
|
+
);
|
|
119
|
+
background: rgb(0 0 0 / 30%);
|
|
120
|
+
backdrop-filter: blur(6px);
|
|
121
|
+
color: var(--mw-hero-text-color);
|
|
102
122
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
text-shadow: none;
|
|
108
|
-
}
|
|
123
|
+
&:hover:not(:disabled) {
|
|
124
|
+
background: var(--mw-primary-color);
|
|
125
|
+
border-color: var(--mw-primary-color);
|
|
126
|
+
color: var(--mw-primary-accent-text-color);
|
|
109
127
|
}
|
|
128
|
+
}
|
|
110
129
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
border-color: color-mix(
|
|
117
|
-
in srgb,
|
|
118
|
-
var(--mw-hero-text-color) 45%,
|
|
119
|
-
transparent
|
|
120
|
-
);
|
|
121
|
-
background: rgb(0 0 0 / 30%);
|
|
122
|
-
backdrop-filter: blur(6px);
|
|
123
|
-
color: var(--mw-hero-text-color);
|
|
130
|
+
// The bar floats over the hero, so centring against the full viewport hides
|
|
131
|
+
// a badge or a kicker underneath it. Guarded - no header, no offset.
|
|
132
|
+
body:has(.mw-header) & {
|
|
133
|
+
padding-top: calc(var(--mw-header-height) + #{spacing('7')});
|
|
134
|
+
}
|
|
124
135
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
// The cue is out of flow, so buttons under the hero text would park on top
|
|
137
|
+
// of it. Only when there is one, or every hero pays for a feature it lacks.
|
|
138
|
+
&:has(> .mw-scroll-hint) {
|
|
139
|
+
padding-bottom: calc(
|
|
140
|
+
var(--mw-scroll-hint-offset) + var(--mw-scroll-hint-reserve)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
131
143
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
144
|
+
// ...except in the corner: the end variant stands beside the content
|
|
145
|
+
&:has(> .mw-scroll-hint-end) {
|
|
146
|
+
padding-bottom: spacing('0');
|
|
147
|
+
}
|
|
137
148
|
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
// On a phone the corner is tight too, so both variants get the strip back.
|
|
150
|
+
// Last of the three: a media query adds no specificity and would lose to
|
|
151
|
+
// the end rule above. Smaller than on the desktop, or the full reserve
|
|
152
|
+
// tips the container past 100dvh and pushes the cue off screen.
|
|
153
|
+
@include media-down('md') {
|
|
140
154
|
&:has(> .mw-scroll-hint) {
|
|
155
|
+
--mw-scroll-hint-reserve: #{spacing('6')};
|
|
156
|
+
|
|
141
157
|
padding-bottom: calc(
|
|
142
158
|
var(--mw-scroll-hint-offset) + var(--mw-scroll-hint-reserve)
|
|
143
159
|
);
|
|
144
160
|
}
|
|
145
|
-
|
|
146
|
-
// ...except in the corner: the end variant stands beside the content
|
|
147
|
-
&:has(> .mw-scroll-hint-end) {
|
|
148
|
-
padding-bottom: spacing('0');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// On a phone the corner is tight too, so both variants get the strip back.
|
|
152
|
-
// Last of the three: a media query adds no specificity and would lose to
|
|
153
|
-
// the end rule above. Smaller than on the desktop, or the full reserve
|
|
154
|
-
// tips the container past 100dvh and pushes the cue off screen.
|
|
155
|
-
@include media-down('md') {
|
|
156
|
-
&:has(> .mw-scroll-hint) {
|
|
157
|
-
--mw-scroll-hint-reserve: #{spacing('6')};
|
|
158
|
-
|
|
159
|
-
padding-bottom: calc(
|
|
160
|
-
var(--mw-scroll-hint-offset) + var(--mw-scroll-hint-reserve)
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
163
|
}
|
|
167
164
|
|
|
168
|
-
// Glass rather than a fill: on a hero image a solid badge is the loudest thing
|
|
169
|
-
// on the screen and plain text sinks into the picture.
|
|
170
|
-
.mw-hero-badge {
|
|
171
|
-
display: inline-flex;
|
|
172
|
-
align-items: center;
|
|
173
|
-
justify-content: center;
|
|
174
|
-
gap: spacing('3');
|
|
175
|
-
max-width: 100%;
|
|
176
|
-
margin-bottom: spacing('6');
|
|
177
|
-
padding: spacing('2') spacing('5');
|
|
178
|
-
border: 1px solid #{fade('hero-text-color', 22%)};
|
|
179
|
-
border-radius: 999px;
|
|
180
|
-
// A vertical ramp and not a flat tint: glass is lit from above, and the
|
|
181
|
-
// gradient is what gives the pill a thickness instead of a fogged panel
|
|
182
|
-
background: linear-gradient(
|
|
183
|
-
to bottom,
|
|
184
|
-
#{fade('hero-text-color', 16%)},
|
|
185
|
-
#{fade('hero-text-color', 5%)}
|
|
186
|
-
);
|
|
187
|
-
// saturate() is what separates glass from frosted plastic: the blur alone
|
|
188
|
-
// greys out what shows through, and pushing the chroma back up is how a
|
|
189
|
-
// real lens reads. The two inset lines are the specular highlight - light
|
|
190
|
-
// catching the top edge, a fainter bounce off the bottom - and the outer
|
|
191
|
-
// shadow is what lifts the pill off the photo instead of pasting it on.
|
|
192
|
-
backdrop-filter: blur(14px) saturate(180%);
|
|
193
|
-
box-shadow:
|
|
194
|
-
inset 0 1px 0 #{fade('hero-text-color', 40%)},
|
|
195
|
-
inset 0 -1px 0 #{fade('hero-text-color', 12%)},
|
|
196
|
-
0 2px 12px rgb(0 0 0 / 18%);
|
|
197
|
-
color: fade('hero-text-color', 88%);
|
|
198
|
-
font-size: font-size('sm');
|
|
199
|
-
font-weight: font-weight('medium');
|
|
200
|
-
letter-spacing: 0.02em;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Carries the secondary colour, because the badge is a state, not a label -
|
|
204
|
-
// override `background` for a different one
|
|
205
|
-
.mw-hero-badge-dot {
|
|
206
|
-
width: 8px;
|
|
207
|
-
height: 8px;
|
|
208
|
-
flex-shrink: 0;
|
|
209
|
-
border-radius: radius('full');
|
|
210
|
-
background: var(--mw-secondary-color);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
165
|
// Belongs to the hero container, not to the flow below it, so the cue sits on
|
|
214
166
|
// the lower edge whatever the content does. Beats the `> *` rule above it.
|
|
215
167
|
.mw-container:has(.mw-hero) > .mw-scroll-hint {
|
|
@@ -281,193 +233,4 @@
|
|
|
281
233
|
flex-direction: column;
|
|
282
234
|
justify-content: center;
|
|
283
235
|
}
|
|
284
|
-
|
|
285
|
-
// A pseudo element on the trigger, so an ancestor with `overflow: hidden` cuts
|
|
286
|
-
// it off. Hidden means `display: none`: a laid-out box still counts towards the
|
|
287
|
-
// page's overflow, and a 280px bubble near the right edge added 50px of sideways
|
|
288
|
-
// scroll on a phone. Hence `allow-discrete` and the starting style.
|
|
289
|
-
$_mw-tooltip-arrow: 6px;
|
|
290
|
-
$_mw-tooltip-gap: 8px;
|
|
291
|
-
// A square turned 45deg rather than a border triangle: it reads the same from
|
|
292
|
-
// above and from below, so the second placement costs no second arrow and a
|
|
293
|
-
// flip never has to repaint anything. The diagonal carries the visible width,
|
|
294
|
-
// so the side is the visible height doubled over root two.
|
|
295
|
-
$_mw-tooltip-arrow-side: math.div($_mw-tooltip-arrow * 2, math.sqrt(2));
|
|
296
|
-
// Half a side short of where the triangle's edge sat, so the turned square is
|
|
297
|
-
// centred on the bubble's edge and only its outer half shows. The inner half
|
|
298
|
-
// lands on the bubble in the same colour, which is what makes it read as one
|
|
299
|
-
// shape rather than a diamond stuck to a box.
|
|
300
|
-
$_mw-tooltip-arrow-inset: $_mw-tooltip-gap +
|
|
301
|
-
$_mw-tooltip-arrow - math.div($_mw-tooltip-arrow-side, 2);
|
|
302
|
-
|
|
303
|
-
[data-tooltip] {
|
|
304
|
-
position: relative;
|
|
305
|
-
|
|
306
|
-
&::before {
|
|
307
|
-
content: attr(data-tooltip);
|
|
308
|
-
position: absolute;
|
|
309
|
-
bottom: 100%;
|
|
310
|
-
left: 50%;
|
|
311
|
-
transform: translateX(-50%);
|
|
312
|
-
margin-bottom: $_mw-tooltip-gap + $_mw-tooltip-arrow;
|
|
313
|
-
max-width: min(280px, 90vw);
|
|
314
|
-
width: max-content;
|
|
315
|
-
padding: spacing('1') spacing('2');
|
|
316
|
-
background: var(--mw-gray-color);
|
|
317
|
-
color: $fixed-surface-text;
|
|
318
|
-
font-size: font-size('xs');
|
|
319
|
-
border-radius: radius('xs');
|
|
320
|
-
box-shadow: var(--mw-elevation-2);
|
|
321
|
-
pointer-events: none;
|
|
322
|
-
opacity: 0;
|
|
323
|
-
display: none;
|
|
324
|
-
transition:
|
|
325
|
-
opacity var(--mw-duration-fast) var(--mw-ease-out),
|
|
326
|
-
display var(--mw-duration-fast) allow-discrete;
|
|
327
|
-
z-index: z-index('tooltip');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
&::after {
|
|
331
|
-
content: '';
|
|
332
|
-
position: absolute;
|
|
333
|
-
bottom: 100%;
|
|
334
|
-
left: 50%;
|
|
335
|
-
transform: translateX(-50%) rotate(45deg);
|
|
336
|
-
margin-bottom: $_mw-tooltip-arrow-inset;
|
|
337
|
-
width: $_mw-tooltip-arrow-side;
|
|
338
|
-
height: $_mw-tooltip-arrow-side;
|
|
339
|
-
background: var(--mw-gray-color);
|
|
340
|
-
opacity: 0;
|
|
341
|
-
display: none;
|
|
342
|
-
transition:
|
|
343
|
-
opacity var(--mw-duration-fast) var(--mw-ease-out),
|
|
344
|
-
display var(--mw-duration-fast) allow-discrete;
|
|
345
|
-
z-index: z-index('tooltip');
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// The placement API: the text is the attribute, the side is a class. The
|
|
349
|
-
// three steer different properties - block axis, inline axis - so `below`
|
|
350
|
-
// and `end` compose without a rule for the pairing.
|
|
351
|
-
//
|
|
352
|
-
// They place the bubble against the trigger rather than joining the anchored
|
|
353
|
-
// path below, and that is the point, not a shortcut. The reason to want them
|
|
354
|
-
// is a control in a fixed header, and that header carries a backdrop-filter,
|
|
355
|
-
// which makes it the containing block for anything `fixed` inside it.
|
|
356
|
-
// `block-end` is then the few pixels between the trigger and the header's own
|
|
357
|
-
// bottom edge, the bubble does not fit, and it gets pushed back over the
|
|
358
|
-
// trigger it belongs to. What this gives up is the clipping immunity the
|
|
359
|
-
// anchored path buys - a modifier inside a scroll container is cut off.
|
|
360
|
-
//
|
|
361
|
-
// Opting out has to restore the placement, not just the positioning scheme:
|
|
362
|
-
// the anchored path below is the later rule and clears `bottom`, `top`,
|
|
363
|
-
// `left` and the centring transform so `position-area` can resolve them
|
|
364
|
-
// itself. Leave that cleared and the box falls to its static position, which
|
|
365
|
-
// for a pseudo element is inside the trigger it is supposed to sit above.
|
|
366
|
-
// The three rules under this one then move the one edge each of them owns.
|
|
367
|
-
&:is(.mw-tooltip-below, .mw-tooltip-end, .mw-tooltip-start) {
|
|
368
|
-
&::before,
|
|
369
|
-
&::after {
|
|
370
|
-
position: absolute;
|
|
371
|
-
position-area: none;
|
|
372
|
-
bottom: 100%;
|
|
373
|
-
top: auto;
|
|
374
|
-
left: 50%;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
&::before {
|
|
378
|
-
transform: translateX(-50%);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
&::after {
|
|
382
|
-
transform: translateX(-50%) rotate(45deg);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
&.mw-tooltip-below {
|
|
387
|
-
&::before {
|
|
388
|
-
top: 100%;
|
|
389
|
-
bottom: auto;
|
|
390
|
-
margin-top: $_mw-tooltip-gap + $_mw-tooltip-arrow;
|
|
391
|
-
margin-bottom: 0;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
&::after {
|
|
395
|
-
top: 100%;
|
|
396
|
-
bottom: auto;
|
|
397
|
-
margin-top: $_mw-tooltip-arrow-inset;
|
|
398
|
-
margin-bottom: 0;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Only the bubble moves - the arrow stays centred on the trigger it points at.
|
|
403
|
-
&.mw-tooltip-end::before {
|
|
404
|
-
left: auto;
|
|
405
|
-
right: 0;
|
|
406
|
-
transform: none;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
&.mw-tooltip-start::before {
|
|
410
|
-
left: 0;
|
|
411
|
-
right: auto;
|
|
412
|
-
transform: none;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
&:hover::before,
|
|
416
|
-
&:hover::after,
|
|
417
|
-
&:focus-visible::before,
|
|
418
|
-
&:focus-visible::after {
|
|
419
|
-
opacity: 1;
|
|
420
|
-
display: block;
|
|
421
|
-
|
|
422
|
-
@starting-style {
|
|
423
|
-
opacity: 0;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// A press usually scales the trigger, and a transform makes it a containing
|
|
428
|
-
// block for the fixed-position bubble above - which would drop the label
|
|
429
|
-
// into the element being pressed. So it steps aside for the click instead;
|
|
430
|
-
// by then it has been read anyway. After the hover rule, same weight.
|
|
431
|
-
&:active::before,
|
|
432
|
-
&:active::after {
|
|
433
|
-
opacity: 0;
|
|
434
|
-
display: none;
|
|
435
|
-
// Not transitioned: `allow-discrete` holds `display: block` until the fade
|
|
436
|
-
// ends, and the bubble would spend those 180ms visibly sitting in the
|
|
437
|
-
// element. Going back out on release keeps the fade - that one is wanted.
|
|
438
|
-
transition: none;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// The clipping caveat above, lifted where the browser can do it: `fixed` takes
|
|
442
|
-
// the bubble out of the trigger's containing block, anchor positioning keeps
|
|
443
|
-
// it attached. No `anchor-name` needed - a pseudo element is implicitly
|
|
444
|
-
// anchored to the element it comes from.
|
|
445
|
-
//
|
|
446
|
-
// Still no `position-try-fallbacks: flip-block`, and no longer because of the
|
|
447
|
-
// arrow - the turned square survives a flip. Bubble and arrow are two
|
|
448
|
-
// separately anchored boxes, so each would decide on its own whether it fits,
|
|
449
|
-
// and the tiny one nearly always does: the label flips, the arrow stays.
|
|
450
|
-
@supports (position-area: block-start) {
|
|
451
|
-
&::before,
|
|
452
|
-
&::after {
|
|
453
|
-
position: fixed;
|
|
454
|
-
// The absolute placement these replace - left as-is they fight the
|
|
455
|
-
// position-area, which resolves the inset properties itself.
|
|
456
|
-
bottom: auto;
|
|
457
|
-
left: auto;
|
|
458
|
-
top: auto;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
&::before {
|
|
462
|
-
position-area: block-start;
|
|
463
|
-
transform: none;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// The turn stays - it is the arrow's shape, not its placement.
|
|
467
|
-
&::after {
|
|
468
|
-
position-area: block-start;
|
|
469
|
-
transform: rotate(45deg);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
236
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
position: relative;
|
|
25
25
|
display: flex;
|
|
26
26
|
align-items: center;
|
|
27
|
-
min-height:
|
|
27
|
+
min-height: 60dvh;
|
|
28
28
|
// `clip` and never `hidden`: hidden makes this a scroll container, and the
|
|
29
29
|
// view() timeline below would then resolve against this box instead of the
|
|
30
30
|
// viewport - the layer keeps its overhang and never moves
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
@use '../abstracts' as *;
|
|
2
2
|
|
|
3
3
|
@layer mw.layout {
|
|
4
|
+
// Every multiplier below is a count of `--mw-section-head-step` (_base.scss),
|
|
5
|
+
// which is where the rhythm is set and where changing it belongs.
|
|
6
|
+
//
|
|
4
7
|
// The head of a section: a rule, a title, and the line that says what the
|
|
5
8
|
// section is. All three left-aligned, and that is the whole point of the
|
|
6
9
|
// component. Centred running text moves its own left edge with every line
|
|
@@ -49,12 +52,12 @@
|
|
|
49
52
|
text-wrap: balance;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
// Two limits, and the narrower one wins:
|
|
55
|
+
// Two limits, and the narrower one wins: 70 characters is where a line stops
|
|
53
56
|
// being comfortable, four fifths is what keeps the paragraph from filling the
|
|
54
57
|
// container edge to edge on a wide screen. Below md only the character count
|
|
55
58
|
// is left - four fifths of a phone is a column nothing fits in.
|
|
56
59
|
.mw-section-head-intro {
|
|
57
|
-
max-width:
|
|
60
|
+
max-width: 70ch;
|
|
58
61
|
margin: calc(var(--mw-section-head-step) * 0.56) spacing('0') spacing('0');
|
|
59
62
|
font-size: font-size('md');
|
|
60
63
|
line-height: 1.6;
|
|
@@ -86,13 +89,13 @@
|
|
|
86
89
|
content: counter(mw-section-head, decimal-leading-zero);
|
|
87
90
|
position: absolute;
|
|
88
91
|
top: var(--mw-section-head-pad);
|
|
89
|
-
|
|
92
|
+
// The tracking sits after the last digit too, so it comes back off the
|
|
93
|
+
// right - in em, or it stops matching the moment the size changes
|
|
94
|
+
right: -0.2em;
|
|
90
95
|
color: var(--mw-text-muted-color);
|
|
91
96
|
font-size: font-size('sm');
|
|
92
97
|
font-weight: font-weight('medium');
|
|
93
98
|
line-height: 1;
|
|
94
|
-
// The tracking sits after the last digit too, so it has to come back off
|
|
95
|
-
// the right or the number stands a space short of the edge it aligns to.
|
|
96
99
|
letter-spacing: 0.2em;
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -237,7 +240,7 @@
|
|
|
237
240
|
// between two demos - the heading leans toward what it names, not toward
|
|
238
241
|
// what came before it.
|
|
239
242
|
.mw-section-subtitle {
|
|
240
|
-
margin-top: calc(var(--mw-section-head-step) * 1.
|
|
243
|
+
margin-top: calc(var(--mw-section-head-step) * 1.5);
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
|
|
@@ -249,22 +252,26 @@
|
|
|
249
252
|
// padding pushes exactly that much of it past the bottom of the screen -
|
|
250
253
|
// and with it the scroll cue sitting on its lower edge. A hero is the full
|
|
251
254
|
// screen by definition and brings its own breathing room.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
padding-block: spacing('0');
|
|
255
|
-
}
|
|
255
|
+
&:has(.mw-hero) {
|
|
256
|
+
padding-block: spacing('0');
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
.mw-section-alternate {
|
|
260
261
|
position: relative;
|
|
261
|
-
--s: 5px;
|
|
262
|
-
--c1: var(--mw-page-background);
|
|
263
|
-
--c2: var(--mw-section-alternate-pattern);
|
|
264
|
-
--g: var(--c2) -5% 5%, var(--c1) 0 45%;
|
|
265
262
|
background:
|
|
266
|
-
linear-gradient(
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
linear-gradient(
|
|
264
|
+
0deg,
|
|
265
|
+
transparent 10%,
|
|
266
|
+
var(--mw-page-background) 0 40%,
|
|
267
|
+
transparent 0
|
|
268
|
+
),
|
|
269
|
+
repeating-linear-gradient(
|
|
270
|
+
-45deg,
|
|
271
|
+
var(--mw-section-alternate-pattern) -5% 5%,
|
|
272
|
+
var(--mw-page-background) 0 45%
|
|
273
|
+
)
|
|
274
|
+
var(--mw-page-background);
|
|
275
|
+
background-size: 5px 5px;
|
|
269
276
|
}
|
|
270
277
|
}
|
|
@@ -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
|
+
}
|