maverick-wave 4.9.4 → 4.11.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/maverick-wave/SKILL.md +112 -12
- package/.claude/skills/maverick-wave/references/components.md +152 -4
- package/.claude/skills/maverick-wave/references/forms.md +26 -0
- package/.claude/skills/maverick-wave/references/javascript.md +34 -27
- package/.claude/skills/maverick-wave/references/layout.md +38 -3
- package/.claude/skills/maverick-wave/references/theming.md +33 -7
- package/CHANGELOG.md +6 -0
- package/README.md +79 -4
- package/index.html +16 -0
- package/maverick-wave.min.css +3 -3
- package/maverick-wave.min.js +1 -1
- package/package.json +2 -2
- package/src/js/main.js +71 -1
- package/src/partials/accordions-container.html +5 -0
- package/src/partials/alerts-container.html +5 -0
- package/src/partials/avatars-container.html +5 -0
- package/src/partials/badges-container.html +114 -0
- package/src/partials/blog-posts-container.html +5 -0
- package/src/partials/breadcrumbs-container.html +1 -1
- package/src/partials/button-bar-container.html +5 -0
- package/src/partials/buttons-container.html +84 -0
- package/src/partials/calendar-container.html +1 -1
- package/src/partials/cards-container.html +5 -0
- package/src/partials/code-container.html +6 -0
- package/src/partials/colors-container.html +1 -1
- package/src/partials/coming-soon-container.html +4 -0
- package/src/partials/divider-container.html +4 -0
- package/src/partials/dropdown-container.html +119 -0
- package/src/partials/empty-state-container.html +5 -0
- package/src/partials/form-container.html +5 -0
- package/src/partials/form-elements-container.html +5 -0
- package/src/partials/form-field-container.html +5 -0
- package/src/partials/gallery-container.html +5 -0
- package/src/partials/get-started-container.html +16 -4
- package/src/partials/header-container.html +6 -2
- package/src/partials/header-utilities-container.html +8 -2
- package/src/partials/html-lists-container.html +5 -0
- package/src/partials/info-container.html +4 -0
- package/src/partials/input-group-container.html +5 -0
- package/src/partials/item-lists-container.html +5 -0
- package/src/partials/kanban-container.html +1 -1
- package/src/partials/kbd-container.html +63 -0
- package/src/partials/meta-info-container.html +5 -0
- package/src/partials/pagination-container.html +1 -1
- package/src/partials/panels-container.html +4 -0
- package/src/partials/progress-container.html +4 -0
- package/src/partials/ratings-container.html +1 -1
- package/src/partials/skeleton-container.html +5 -0
- package/src/partials/spinners-container.html +5 -1
- package/src/partials/stepper-container.html +4 -0
- package/src/partials/tables-container.html +7 -0
- package/src/partials/tabs-container.html +35 -13
- package/src/partials/tags-container.html +5 -0
- package/src/partials/tiles-container.html +1 -1
- package/src/partials/typography-container.html +5 -0
- package/src/partials/utilities-container.html +153 -0
- package/src/scss/abstracts/_mixins.scss +176 -0
- package/src/scss/abstracts/_variables.scss +193 -5
- package/src/scss/base/_base.scss +103 -2
- package/src/scss/base/_reset.scss +43 -1
- package/src/scss/base/_typography.scss +47 -0
- package/src/scss/components/_accordions.scss +11 -1
- package/src/scss/components/_alerts.scss +3 -3
- package/src/scss/components/_avatars.scss +27 -6
- package/src/scss/components/_badge.scss +157 -0
- package/src/scss/components/_blog-post.scss +10 -7
- package/src/scss/components/_breadcrumbs.scss +11 -2
- package/src/scss/components/_button-bar.scss +9 -2
- package/src/scss/components/_buttons.scss +133 -17
- package/src/scss/components/_calendar.scss +13 -2
- package/src/scss/components/_cards.scss +12 -5
- package/src/scss/components/_code.scss +15 -0
- package/src/scss/components/_coming-soon.scss +5 -2
- package/src/scss/components/_content-slider.scss +1 -1
- package/src/scss/components/_dropdown.scss +219 -0
- package/src/scss/components/_gallery.scss +17 -8
- package/src/scss/components/_index.scss +3 -0
- package/src/scss/components/_info.scss +14 -8
- package/src/scss/components/_kanban.scss +40 -6
- package/src/scss/components/_kbd.scss +43 -0
- package/src/scss/components/_lists.scss +26 -23
- package/src/scss/components/_modals.scss +71 -6
- package/src/scss/components/_pagination.scss +4 -3
- package/src/scss/components/_panels.scss +13 -14
- package/src/scss/components/_progress.scss +1 -1
- package/src/scss/components/_segmented.scss +8 -4
- package/src/scss/components/_stepper.scss +3 -3
- package/src/scss/components/_tables.scss +5 -8
- package/src/scss/components/_tabs.scss +22 -3
- package/src/scss/components/_tags.scss +1 -2
- package/src/scss/components/_techstack-bucket.scss +8 -5
- package/src/scss/components/_theme-toggle.scss +16 -8
- package/src/scss/components/_tiles.scss +14 -4
- package/src/scss/components/_timelines.scss +9 -6
- package/src/scss/components/_toasts.scss +1 -1
- package/src/scss/form-elements/_checkbox.scss +2 -1
- package/src/scss/form-elements/_form.scss +2 -2
- package/src/scss/form-elements/_input-group.scss +30 -1
- package/src/scss/form-elements/_input.scss +10 -10
- package/src/scss/form-elements/_login.scss +1 -1
- package/src/scss/form-elements/_radio.scss +4 -3
- package/src/scss/form-elements/_select.scss +9 -10
- package/src/scss/form-elements/_slider.scss +11 -11
- package/src/scss/form-elements/_textarea.scss +5 -8
- package/src/scss/form-elements/_toggle.scss +6 -5
- package/src/scss/layout/_footer.scss +14 -7
- package/src/scss/layout/_header.scss +18 -17
- package/src/scss/layout/_home.scss +1 -1
- package/src/scss/layout/_main.scss +6 -6
- package/src/scss/layout/_section.scss +7 -4
- package/src/scss/utilities/_accessibility.scss +38 -0
- package/src/scss/utilities/_aspect.scss +27 -0
- package/src/scss/utilities/_display.scss +52 -0
- package/src/scss/utilities/_elevation.scss +16 -0
- package/src/scss/utilities/_index.scss +2 -0
- package/src/scss/utilities/_text.scss +63 -0
- package/src/scss/utilities/_touch-targets.scss +63 -0
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
<h3 class="mw-section-subtitle">Utilities</h3>
|
|
2
|
+
<p class="mw-text-muted mw-text-measure mw-mb-6">
|
|
3
|
+
The classes that do exactly one thing: spacing, flex, display, text,
|
|
4
|
+
elevation, aspect ratio. Worth checking before writing a rule of your own -
|
|
5
|
+
most one-off styles in an application turn out to be a utility that already
|
|
6
|
+
exists.
|
|
7
|
+
</p>
|
|
2
8
|
|
|
3
9
|
<div class="mw-grid-3-lg">
|
|
4
10
|
<div>
|
|
@@ -231,3 +237,150 @@
|
|
|
231
237
|
</span>
|
|
232
238
|
</div>
|
|
233
239
|
</div>
|
|
240
|
+
|
|
241
|
+
<h3 class="mw-mb-4 mw-mt-6">Text Overflow & Wrapping</h3>
|
|
242
|
+
<p class="mw-text-muted mw-text-measure">
|
|
243
|
+
The two ways a long string is cut short, and they are not interchangeable:
|
|
244
|
+
<code>mw-text-truncate</code> is one line ending in an ellipsis,
|
|
245
|
+
<code>mw-text-clamp-2</code> through <code>-5</code> is that many lines ending
|
|
246
|
+
in one. Truncation carries <code>min-width: 0</code> with it, which is the
|
|
247
|
+
reason it "does not work" nine times out of ten inside a flex row.
|
|
248
|
+
</p>
|
|
249
|
+
<div class="mw-grid-2 mw-gap-6">
|
|
250
|
+
<div class="mw-card mw-card-simple">
|
|
251
|
+
<h5 class="mw-text-medium mw-mb-2"><code>mw-text-truncate</code></h5>
|
|
252
|
+
<p class="mw-text-truncate">
|
|
253
|
+
One line only: this sentence is far longer than the box it has been given
|
|
254
|
+
and ends in an ellipsis rather than wrapping.
|
|
255
|
+
</p>
|
|
256
|
+
<h5 class="mw-text-medium mw-mb-2 mw-mt-5">
|
|
257
|
+
<code>mw-text-clamp-2</code>
|
|
258
|
+
</h5>
|
|
259
|
+
<p class="mw-text-clamp-2">
|
|
260
|
+
Two lines and then an ellipsis. This paragraph runs on for long enough
|
|
261
|
+
that the third line is cut off, which is what a card preview or a list
|
|
262
|
+
subtitle wants when the copy is not yours to control.
|
|
263
|
+
</p>
|
|
264
|
+
<h5 class="mw-text-medium mw-mb-2 mw-mt-5">
|
|
265
|
+
<code>mw-text-break</code>
|
|
266
|
+
</h5>
|
|
267
|
+
<p class="mw-text-break">
|
|
268
|
+
https://example.com/a/very/long/url/that/has/no/spaces/in/it/at/all/and/would/otherwise/push/the/page/sideways
|
|
269
|
+
</p>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="mw-card mw-card-simple">
|
|
272
|
+
<h5 class="mw-text-medium mw-mb-2">
|
|
273
|
+
<code>mw-text-balance</code> and <code>mw-text-pretty</code>
|
|
274
|
+
</h5>
|
|
275
|
+
<p class="mw-text-muted mw-text-sm">
|
|
276
|
+
Headings get <code>balance</code> by default - it evens the lines out so
|
|
277
|
+
no headline leaves one word alone on the last one. Paragraphs get
|
|
278
|
+
<code>pretty</code>, which only prevents the orphan. Both are on already;
|
|
279
|
+
the classes are for anything the framework does not style itself.
|
|
280
|
+
</p>
|
|
281
|
+
<h5 class="mw-text-medium mw-mb-2 mw-mt-5">
|
|
282
|
+
<code>mw-text-eyebrow</code>
|
|
283
|
+
</h5>
|
|
284
|
+
<p class="mw-text-eyebrow mw-mb-1">Release notes</p>
|
|
285
|
+
<h4 class="mw-mb-0">The kicker above a heading</h4>
|
|
286
|
+
<h5 class="mw-text-medium mw-mb-2 mw-mt-5">
|
|
287
|
+
<code>mw-text-measure</code>
|
|
288
|
+
</h5>
|
|
289
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">
|
|
290
|
+
Caps a column at 68 characters. Past roughly 75 the eye loses the start of
|
|
291
|
+
the next line on the way back, which is why a full-width paragraph on a
|
|
292
|
+
desktop is harder to read than a narrow one.
|
|
293
|
+
</p>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<h3 class="mw-mb-4 mw-mt-6">Elevation</h3>
|
|
298
|
+
<p class="mw-text-muted mw-text-measure">
|
|
299
|
+
The same five-step ramp every component uses, as classes. Each level is two
|
|
300
|
+
shadows: a tight contact layer that gives the box weight, and a wide ambient
|
|
301
|
+
one that says how high it floats. One layer alone reads as a blur behind the
|
|
302
|
+
box - the pair reads as light. <code>mw-elevation-0</code> is explicitly flat,
|
|
303
|
+
for taking something off the page it normally floats above.
|
|
304
|
+
</p>
|
|
305
|
+
<div class="mw-grid-3 mw-gap-6">
|
|
306
|
+
<div class="mw-card mw-card-simple mw-elevation-1">
|
|
307
|
+
<h5 class="mw-text-medium mw-mb-1">1 · resting</h5>
|
|
308
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">
|
|
309
|
+
Inputs, tags, small controls.
|
|
310
|
+
</p>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="mw-card mw-card-simple mw-elevation-2">
|
|
313
|
+
<h5 class="mw-text-medium mw-mb-1">2 · raised</h5>
|
|
314
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">Cards and panels at rest.</p>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="mw-card mw-card-simple mw-elevation-3">
|
|
317
|
+
<h5 class="mw-text-medium mw-mb-1">3 · floating</h5>
|
|
318
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">A card under the pointer.</p>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="mw-card mw-card-simple mw-elevation-4">
|
|
321
|
+
<h5 class="mw-text-medium mw-mb-1">4 · overlay</h5>
|
|
322
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">Dropdowns, popovers, drawer.</p>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="mw-card mw-card-simple mw-elevation-5">
|
|
325
|
+
<h5 class="mw-text-medium mw-mb-1">5 · modal</h5>
|
|
326
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">The dialog over everything.</p>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="mw-card mw-card-simple mw-elevation-0">
|
|
329
|
+
<h5 class="mw-text-medium mw-mb-1">0 · flat</h5>
|
|
330
|
+
<p class="mw-text-muted mw-text-sm mw-mb-0">Explicitly on the page.</p>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<h3 class="mw-mb-4 mw-mt-6">Responsive Display</h3>
|
|
335
|
+
<p class="mw-text-muted mw-text-measure">
|
|
336
|
+
Mobile-first and min-width, like everything else here.
|
|
337
|
+
<code>mw-d-md-flex</code> means "flex from the md breakpoint up", and the
|
|
338
|
+
unprefixed class beside it is what applies below that - the pair
|
|
339
|
+
<code>mw-d-none mw-d-md-flex</code> is hidden on a phone and a row from a
|
|
340
|
+
tablet on. Variants exist for <code>sm</code>, <code>md</code>,
|
|
341
|
+
<code>lg</code> and <code>xl</code>.
|
|
342
|
+
</p>
|
|
343
|
+
<div class="mw-card mw-card-simple">
|
|
344
|
+
<div class="mw-row-split mw-row-split-center">
|
|
345
|
+
<code>mw-hide-mobile</code>
|
|
346
|
+
<span class="mw-hide-mobile mw-badge mw-badge-success">visible now</span>
|
|
347
|
+
<span class="mw-hide-desktop mw-badge mw-badge-muted">hidden below md</span>
|
|
348
|
+
</div>
|
|
349
|
+
<hr class="mw-divider" />
|
|
350
|
+
<div class="mw-row-split mw-row-split-center">
|
|
351
|
+
<code>mw-hide-desktop</code>
|
|
352
|
+
<span class="mw-hide-desktop mw-badge mw-badge-success">visible now</span>
|
|
353
|
+
<span class="mw-hide-mobile mw-badge mw-badge-muted"
|
|
354
|
+
>hidden from md up</span
|
|
355
|
+
>
|
|
356
|
+
</div>
|
|
357
|
+
<p class="mw-text-muted mw-text-sm mw-mt-4 mw-mb-0">
|
|
358
|
+
Resize the window past 768px to see the two swap.
|
|
359
|
+
</p>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<h3 class="mw-mb-4 mw-mt-6">Aspect Ratio</h3>
|
|
363
|
+
<p class="mw-text-muted mw-text-measure">
|
|
364
|
+
Reserves the box before the image inside it has loaded, which is the whole
|
|
365
|
+
point: without it the page reflows when the picture arrives and everything
|
|
366
|
+
below jumps. The child fills the box and crops rather than stretching.
|
|
367
|
+
<code>mw-aspect-square</code>, <code>-video</code>, <code>-wide</code>,
|
|
368
|
+
<code>-portrait</code>, <code>-photo</code>.
|
|
369
|
+
</p>
|
|
370
|
+
<div class="mw-grid-3 mw-gap-4">
|
|
371
|
+
<div
|
|
372
|
+
class="mw-aspect-square mw-card mw-card-simple mw-d-flex mw-items-center mw-justify-center"
|
|
373
|
+
>
|
|
374
|
+
<code>square</code>
|
|
375
|
+
</div>
|
|
376
|
+
<div
|
|
377
|
+
class="mw-aspect-video mw-card mw-card-simple mw-d-flex mw-items-center mw-justify-center"
|
|
378
|
+
>
|
|
379
|
+
<code>video</code>
|
|
380
|
+
</div>
|
|
381
|
+
<div
|
|
382
|
+
class="mw-aspect-photo mw-card mw-card-simple mw-d-flex mw-items-center mw-justify-center"
|
|
383
|
+
>
|
|
384
|
+
<code>photo</code>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
@@ -77,3 +77,179 @@
|
|
|
77
77
|
0.9rem 100%;
|
|
78
78
|
background-attachment: local, local, scroll, scroll;
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Focus
|
|
83
|
+
//
|
|
84
|
+
// The ring was written out by hand in nine places before this, in two different
|
|
85
|
+
// shapes, and a form field had a third. One mixin per shape instead, so the
|
|
86
|
+
// whole framework moves when the ring is retuned.
|
|
87
|
+
//
|
|
88
|
+
// `:focus-visible` and never `:focus`: a mouse click on a button should not
|
|
89
|
+
// leave it ringed. The browser decides what counts - a keyboard tab does, a
|
|
90
|
+
// pointer press on a button does not, a pointer press into a text field does.
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
@mixin focus-ring(
|
|
93
|
+
$offset: var(--mw-focus-ring-offset),
|
|
94
|
+
$color: var(--mw-focus-ring-color)
|
|
95
|
+
) {
|
|
96
|
+
&:focus-visible {
|
|
97
|
+
outline: var(--mw-focus-ring-width) solid #{$color};
|
|
98
|
+
outline-offset: #{$offset};
|
|
99
|
+
@content;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Same ring, drawn inside the element. For anything clipped by its own frame -
|
|
104
|
+
// a segment inside a segmented control, a cell inside a table - where a ring on
|
|
105
|
+
// the outside would be cut off by the parent.
|
|
106
|
+
@mixin focus-ring-inset($inset: -2px) {
|
|
107
|
+
&:focus-visible {
|
|
108
|
+
outline: var(--mw-focus-ring-width) solid var(--mw-focus-ring-color);
|
|
109
|
+
outline-offset: $inset;
|
|
110
|
+
@content;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The form-field shape: recolour the border the field already has and lay a soft
|
|
115
|
+
// halo around it, rather than adding a second hard line outside it.
|
|
116
|
+
//
|
|
117
|
+
// Two different tones, and the split is the framework's own fill-or-ink rule.
|
|
118
|
+
// The border is drawn *on* the field, which is light in both themes, so it takes
|
|
119
|
+
// the fill tone. The halo spreads *outside* it, onto the page or the card, so it
|
|
120
|
+
// takes the ink tone - the theme-aware one. Using the fill tone for both is what
|
|
121
|
+
// made the dark theme's halo a dark blue haze on a dark blue page.
|
|
122
|
+
//
|
|
123
|
+
// `outline: none` is only safe because the halo *is* the indicator - except in
|
|
124
|
+
// forced-colors mode, where box-shadow is dropped and the field would be left
|
|
125
|
+
// with nothing. The guard hands the outline back there.
|
|
126
|
+
@mixin field-focus(
|
|
127
|
+
$trigger: 'focus',
|
|
128
|
+
$border: 'primary-color',
|
|
129
|
+
$halo: 'primary-text-color'
|
|
130
|
+
) {
|
|
131
|
+
&:#{$trigger} {
|
|
132
|
+
outline: none;
|
|
133
|
+
border-color: var(--mw-#{$border});
|
|
134
|
+
@include focus-halo($halo);
|
|
135
|
+
@content;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (forced-colors: active) {
|
|
139
|
+
&:#{$trigger} {
|
|
140
|
+
outline: var(--mw-focus-ring-width) solid;
|
|
141
|
+
outline-offset: var(--mw-focus-ring-offset);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The halo on its own, for the controls that draw their own box - a checkbox, a
|
|
147
|
+
// radio, a switch track, a calendar day - and only want the ring around it.
|
|
148
|
+
@mixin focus-halo($color: 'primary-text-color') {
|
|
149
|
+
box-shadow: 0 0 0 var(--mw-focus-halo-size)
|
|
150
|
+
color-mix(
|
|
151
|
+
in srgb,
|
|
152
|
+
var(--mw-#{$color}) var(--mw-focus-halo-opacity),
|
|
153
|
+
transparent
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Pointer
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
// Hover effects that *move* something - a card lifting, a tile growing - only
|
|
162
|
+
// on a device that can actually hover. On touch, `:hover` latches after a tap
|
|
163
|
+
// and stays on until you tap somewhere else, so a lifted card stays lifted and
|
|
164
|
+
// a grid of them ends up with one member permanently out of line. Colour
|
|
165
|
+
// changes are fine unguarded; movement is not.
|
|
166
|
+
@mixin hover {
|
|
167
|
+
@media (hover: hover) and (pointer: fine) {
|
|
168
|
+
&:hover {
|
|
169
|
+
@content;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Everything a control needs to feel native under a thumb: no 300ms wait for a
|
|
175
|
+
// double-tap that is never coming, and no grey box flashing over it while the
|
|
176
|
+
// press is held - the component draws its own :active state instead.
|
|
177
|
+
@mixin tappable {
|
|
178
|
+
touch-action: manipulation;
|
|
179
|
+
-webkit-tap-highlight-color: transparent;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Scrollbar
|
|
184
|
+
//
|
|
185
|
+
// Was written per list variant, which is why three of them had a thumb the same
|
|
186
|
+
// colour as their track. One shape, used by every scroll container and by the
|
|
187
|
+
// page itself.
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
@mixin scrollbar($thumb: var(--mw-text-muted-color), $track: transparent) {
|
|
190
|
+
scrollbar-width: thin;
|
|
191
|
+
// Firefox takes both in one declaration - a single value is invalid and the
|
|
192
|
+
// whole thing is dropped, which is how a thumb ends up invisible.
|
|
193
|
+
scrollbar-color: #{$thumb} #{$track};
|
|
194
|
+
|
|
195
|
+
&::-webkit-scrollbar {
|
|
196
|
+
width: 8px;
|
|
197
|
+
height: 8px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&::-webkit-scrollbar-track {
|
|
201
|
+
background: #{$track};
|
|
202
|
+
border-radius: 4px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
&::-webkit-scrollbar-thumb {
|
|
206
|
+
background: #{$thumb};
|
|
207
|
+
border-radius: 4px;
|
|
208
|
+
|
|
209
|
+
&:hover {
|
|
210
|
+
background: var(--mw-primary-text-color);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
&::-webkit-scrollbar-corner {
|
|
215
|
+
background: #{$track};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Everything that fills its own surface with a colour - every variant except
|
|
220
|
+
// outline, link and plain.
|
|
221
|
+
//
|
|
222
|
+
// Two things, and both are what separates a button from a coloured rectangle: a
|
|
223
|
+
// hairline of light along the top edge, as if there were a light source above
|
|
224
|
+
// the page, and a shadow underneath that says the button sits on the surface
|
|
225
|
+
// rather than in it. The pressed state inverts both - the highlight goes and an
|
|
226
|
+
// inner shadow takes its place, which is the whole of what "pushed in" looks
|
|
227
|
+
// like.
|
|
228
|
+
//
|
|
229
|
+
// White and black rather than tokens on purpose: this is lighting, not palette.
|
|
230
|
+
// It has to behave the same on a primary, a danger and whatever a project
|
|
231
|
+
// overrides them with.
|
|
232
|
+
@mixin btn-solid {
|
|
233
|
+
box-shadow:
|
|
234
|
+
inset 0 1px 0 color-mix(in srgb, #fff 15%, transparent),
|
|
235
|
+
var(--mw-elevation-1);
|
|
236
|
+
|
|
237
|
+
// A pixel up, and the shadow steps up with it. The two together are what read
|
|
238
|
+
// as the button coming toward the pointer - the shadow alone says it, but not
|
|
239
|
+
// as clearly, and the lift is small enough that a row of buttons does not
|
|
240
|
+
// twitch. Guarded, so it never latches after a tap.
|
|
241
|
+
@include hover {
|
|
242
|
+
box-shadow:
|
|
243
|
+
inset 0 1px 0 color-mix(in srgb, #fff 20%, transparent),
|
|
244
|
+
var(--mw-elevation-2);
|
|
245
|
+
transform: translateY(-1px);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
&:active:not(:disabled) {
|
|
249
|
+
box-shadow: inset 0 2px 5px color-mix(in srgb, #000 25%, transparent);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
&:disabled {
|
|
253
|
+
box-shadow: none;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -61,6 +61,13 @@ $warning-color: #d1bb21 !default;
|
|
|
61
61
|
$danger-color: #d41708 !default;
|
|
62
62
|
$info-color: #0780ca !default;
|
|
63
63
|
|
|
64
|
+
// Ink on the handful of surfaces that stay a fixed grey in both themes and
|
|
65
|
+
// therefore cannot take --mw-accent-text-color: the tooltip, `mw-btn-mini`,
|
|
66
|
+
// `mw-info-mini` and the gallery arrows. Compile-time on purpose - these are
|
|
67
|
+
// not meant to be retuned per project, they just should not be four copies of
|
|
68
|
+
// the same literal in four unrelated files.
|
|
69
|
+
$fixed-surface-text: #efefef !default;
|
|
70
|
+
|
|
64
71
|
// Uniform hover tone for every solid brand/status surface. Darkening, in both
|
|
65
72
|
// themes: the label on a solid surface is $accent-text-color, and lightening
|
|
66
73
|
// a solid surface is what breaks it.
|
|
@@ -114,7 +121,139 @@ $footer-surface-light: 0.95 !default;
|
|
|
114
121
|
// Lower it for a darker bar. The ink on the bar is near-white, so going far up
|
|
115
122
|
// means overriding --mw-header-text-color and
|
|
116
123
|
// --mw-header-navbar-list-active-color with it.
|
|
117
|
-
$header-surface:
|
|
124
|
+
$header-surface: 14% !default;
|
|
125
|
+
|
|
126
|
+
// How far the active navbar link is lifted off the primary colour. Lower is a
|
|
127
|
+
// stronger, more saturated blue and closer to the brand tone; higher is paler
|
|
128
|
+
// and carries more contrast against the bar.
|
|
129
|
+
//
|
|
130
|
+
// 25% lands on #435fd5 at 3.67:1 over the bar - past the 3:1 floor a large or
|
|
131
|
+
// bold label needs, under the 4.5:1 an AA body-size label wants. That is a
|
|
132
|
+
// deliberate trade for a nav item that is already marked by an underline and a
|
|
133
|
+
// weight change as well as by colour; raise it toward 35% for the safe end.
|
|
134
|
+
$header-active-tint: 25% !default;
|
|
135
|
+
|
|
136
|
+
// The shadow pair every elevation is built from - see elevation() below.
|
|
137
|
+
//
|
|
138
|
+
// $shadow-cast-dark is how far a shadow in the dark theme sits below the page
|
|
139
|
+
// colour. A black shadow on a near-black page is invisible, which is why the
|
|
140
|
+
// framework used to carry a single light glow instead; the glow survives as the
|
|
141
|
+
// far layer, and this is the near one that puts weight back underneath it.
|
|
142
|
+
$shadow-cast-dark: 0.3 !default;
|
|
143
|
+
// Near (contact) and far (ambient) opacity per theme. The dark theme's far layer
|
|
144
|
+
// is the light rim that reads as elevation on a dark page, so it stays low - a
|
|
145
|
+
// bright glow carries much further than a dark shadow at the same alpha.
|
|
146
|
+
$shadow-near-dark: 55% !default;
|
|
147
|
+
$shadow-far-dark: 13% !default;
|
|
148
|
+
$shadow-near-light: 14% !default;
|
|
149
|
+
$shadow-far-light: 10% !default;
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Control sizes
|
|
153
|
+
//
|
|
154
|
+
// One height per step, shared by everything that can end up in a row next to
|
|
155
|
+
// something else: input, select, textarea, button.
|
|
156
|
+
//
|
|
157
|
+
// Before this each control worked its height out from its own font size, its own
|
|
158
|
+
// line-height and its own padding, and no two of them agreed - an input, a
|
|
159
|
+
// select and a button side by side measured 32.2, 34.4 and 37.2 pixels. That is
|
|
160
|
+
// the kind of flaw you see without being able to name it: the row just looks
|
|
161
|
+
// slightly wrong.
|
|
162
|
+
//
|
|
163
|
+
// A minimum, not a fixed height, so a control whose content needs more room
|
|
164
|
+
// still gets it. The step is what makes the trio line up; the font scale below
|
|
165
|
+
// is what makes them look like one family.
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// 32 / 36 / 40px - an even 4px step. The middle one used to be 2.35rem, which
|
|
168
|
+
// made the ramp 32 / 37.6 / 41.6: a 5.6px step to the base size and a 4px one
|
|
169
|
+
// past it, and a base field a shade taller than it needed to be.
|
|
170
|
+
//
|
|
171
|
+
// The padding is not what sets these. A field's height comes from the minimum
|
|
172
|
+
// below, so trimming `padding-block` on its own changes where the text sits
|
|
173
|
+
// inside the box and not how tall the box is.
|
|
174
|
+
$control-height-sm: 2rem !default;
|
|
175
|
+
$control-height: 2.25rem !default;
|
|
176
|
+
$control-height-lg: 2.5rem !default;
|
|
177
|
+
|
|
178
|
+
// The font each step is set in. All three fields share it - the select used to
|
|
179
|
+
// sit a whole step below the input beside it, which is why the two never looked
|
|
180
|
+
// like a pair. Buttons stay one step above: a button carries a label, a field
|
|
181
|
+
// carries data the user typed, and the label may be the louder of the two.
|
|
182
|
+
$control-font-sm: 0.8rem !default;
|
|
183
|
+
$control-font: 0.9rem !default;
|
|
184
|
+
$control-font-lg: 1rem !default;
|
|
185
|
+
|
|
186
|
+
// Stated rather than inherited. `line-height: normal` is whatever the font
|
|
187
|
+
// vendor decided, so an input and a select with the same padding still came out
|
|
188
|
+
// different heights - and it changes the moment a project swaps the font stack.
|
|
189
|
+
$control-line-height: 1.35 !default;
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Motion
|
|
193
|
+
//
|
|
194
|
+
// Four durations and three curves, and every transition in the framework is
|
|
195
|
+
// built from them. The point is not the numbers - it is that a hover, a drawer
|
|
196
|
+
// and a modal that all say "0.3s ease" feel like three different products,
|
|
197
|
+
// because 300ms is twice too slow for a colour swap and half too fast for a
|
|
198
|
+
// panel crossing the screen.
|
|
199
|
+
//
|
|
200
|
+
// The curves: 'out' decelerates and is what almost everything wants, because
|
|
201
|
+
// something arriving should land rather than stop. 'in-out' is symmetric, for
|
|
202
|
+
// anything that moves from A to B and back. 'spring' overshoots a few percent,
|
|
203
|
+
// which is what makes a press or a pop read as physical instead of animated.
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
$duration-instant: 110ms !default;
|
|
206
|
+
$duration-fast: 180ms !default;
|
|
207
|
+
$duration-base: 300ms !default;
|
|
208
|
+
$duration-slow: 520ms !default;
|
|
209
|
+
$duration-slower: 900ms !default;
|
|
210
|
+
|
|
211
|
+
// The curves.
|
|
212
|
+
//
|
|
213
|
+
// 'out' was cubic-bezier(0.22, 1, 0.36, 1) for one revision, and it was wrong:
|
|
214
|
+
// that curve is so front-loaded it covers about seventy percent of the distance
|
|
215
|
+
// in the first quarter of the time. On a colour swap that reads as snappy. On
|
|
216
|
+
// anything that actually travels - an image scaling inside a card, a panel
|
|
217
|
+
// sliding - it reads as a jump followed by a crawl, which is the opposite of
|
|
218
|
+
// smooth. This one decelerates evenly and is the right default for both.
|
|
219
|
+
$ease-out: cubic-bezier(0.33, 1, 0.68, 1) !default;
|
|
220
|
+
$ease-in-out: cubic-bezier(0.65, 0, 0.35, 1) !default;
|
|
221
|
+
// A few percent of overshoot, not a bounce - the earlier 1.56 read as a wobble
|
|
222
|
+
$ease-spring: cubic-bezier(0.34, 1.3, 0.64, 1) !default;
|
|
223
|
+
|
|
224
|
+
// Anything that moves a large area rather than recolouring one - an image
|
|
225
|
+
// zooming inside a card, a tile growing. Distance and duration go together: the
|
|
226
|
+
// same 300ms that feels right on a 40px button feels snatched on a 300px photo,
|
|
227
|
+
// because the eye reads speed as distance over time, not as time.
|
|
228
|
+
$duration-zoom: 650ms !default;
|
|
229
|
+
|
|
230
|
+
// What --mw-transition actually animates.
|
|
231
|
+
//
|
|
232
|
+
// It used to be `all`, which is a footgun in two directions: it animates
|
|
233
|
+
// properties nobody meant to animate - a width that changes at a breakpoint
|
|
234
|
+
// crawls to its new value - and it forces the browser to check every property
|
|
235
|
+
// on every change. The list below covers every paint-only property a hover,
|
|
236
|
+
// focus or active state touches, and nothing that moves layout. Anything that
|
|
237
|
+
// really does want to animate a size says so itself.
|
|
238
|
+
$transition-properties:
|
|
239
|
+
color, background-color, background-image, border-color, box-shadow, opacity,
|
|
240
|
+
transform, outline-color, outline-offset, filter, backdrop-filter, fill,
|
|
241
|
+
stroke, text-decoration-color !default;
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Focus ring
|
|
245
|
+
//
|
|
246
|
+
// One ring for the whole framework, on :focus-visible only - a mouse click on a
|
|
247
|
+
// button should not leave it outlined. 2px is the floor at which a ring stays
|
|
248
|
+
// visible against a busy surface, and the offset is what keeps it from reading
|
|
249
|
+
// as a border on the control itself.
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
$focus-ring-width: 2px !default;
|
|
252
|
+
$focus-ring-offset: 2px !default;
|
|
253
|
+
// The soft halo a form field gets instead of a hard ring - a field already has a
|
|
254
|
+
// border to recolour, so a second hard line around it reads as a mistake.
|
|
255
|
+
$focus-halo-size: 3px !default;
|
|
256
|
+
$focus-halo-opacity: 28% !default;
|
|
118
257
|
|
|
119
258
|
// ---------------------------------------------------------------------------
|
|
120
259
|
// Derivation helpers
|
|
@@ -160,6 +299,15 @@ $header-surface: 16% !default;
|
|
|
160
299
|
);
|
|
161
300
|
}
|
|
162
301
|
|
|
302
|
+
// Translucent variant of an arbitrary color expression. fade() is the same thing
|
|
303
|
+
// for a token name; this one takes whatever another helper handed back, which is
|
|
304
|
+
// what the shadow tones below need.
|
|
305
|
+
@function alpha($color, $amount) {
|
|
306
|
+
@return string.unquote(
|
|
307
|
+
'color-mix(in srgb, #{$color} #{$amount}, transparent)'
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
163
311
|
// Darkened straight toward black in sRGB - the ramp a color tool prints as a
|
|
164
312
|
// color's shades, where $amount is how much of the color survives. shade()
|
|
165
313
|
// cannot stand in for it: mixing in hsl scales saturation along with lightness
|
|
@@ -209,6 +357,24 @@ $header-surface: 16% !default;
|
|
|
209
357
|
);
|
|
210
358
|
}
|
|
211
359
|
|
|
360
|
+
// Two-layer elevation.
|
|
361
|
+
//
|
|
362
|
+
// The near layer is the contact shadow - short offset, tight blur - that gives
|
|
363
|
+
// an element weight and pins it to the surface underneath. The far layer is the
|
|
364
|
+
// ambient spread that says how high it floats. One layer on its own reads as a
|
|
365
|
+
// blur behind the box; the pair reads as light, which is the whole difference
|
|
366
|
+
// between a framework that looks drawn and one that looks rendered.
|
|
367
|
+
//
|
|
368
|
+
// Both tones are theme-prefixed tokens, so the ramp is declared once per theme
|
|
369
|
+
// and the same five level names work in either one - see the note on derived
|
|
370
|
+
// tokens above for why they cannot be one shared set on :root.
|
|
371
|
+
@function elevation($theme, $y-near, $blur-near, $y-far, $blur-far) {
|
|
372
|
+
@return string.unquote(
|
|
373
|
+
'0 #{$y-near} #{$blur-near} var(--mw-#{$theme}-shadow-near),' +
|
|
374
|
+
' 0 #{$y-far} #{$blur-far} var(--mw-#{$theme}-shadow-far)'
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
212
378
|
// Base colors that don't change between themes.
|
|
213
379
|
// Emitted as --mw-<key>.
|
|
214
380
|
//
|
|
@@ -311,6 +477,20 @@ $dark-theme-colors: (
|
|
|
311
477
|
'footer-background': surface('dark-page-background', $footer-surface-dark),
|
|
312
478
|
'border': surface('dark-page-background', 1.35),
|
|
313
479
|
'shadow': fade('dark-text-color', 30%),
|
|
480
|
+
// The two tones every elevation level is mixed from, and the five-step ramp
|
|
481
|
+
// itself. A component names a level - `var(--mw-elevation-2)` - instead of
|
|
482
|
+
// inventing another `0 5px 9px`; before this the framework carried twelve
|
|
483
|
+
// different shadow values for what were really four heights.
|
|
484
|
+
'shadow-near': alpha(
|
|
485
|
+
surface('dark-page-background', $shadow-cast-dark),
|
|
486
|
+
$shadow-near-dark
|
|
487
|
+
),
|
|
488
|
+
'shadow-far': fade('dark-text-color', $shadow-far-dark),
|
|
489
|
+
'elevation-1': elevation('dark', 1px, 2px, 1px, 4px),
|
|
490
|
+
'elevation-2': elevation('dark', 2px, 4px, 5px, 12px),
|
|
491
|
+
'elevation-3': elevation('dark', 3px, 6px, 10px, 24px),
|
|
492
|
+
'elevation-4': elevation('dark', 6px, 12px, 18px, 40px),
|
|
493
|
+
'elevation-5': elevation('dark', 10px, 20px, 30px, 64px),
|
|
314
494
|
'text-color': $dark-text-color,
|
|
315
495
|
'text-muted-color': muted(0.7),
|
|
316
496
|
// Every color as ink on this page. Derived, not hardcoded - a project that
|
|
@@ -335,6 +515,17 @@ $light-theme-colors: (
|
|
|
335
515
|
'footer-background': surface('light-page-background', $footer-surface-light),
|
|
336
516
|
'border': surface('light-page-background', 0.8),
|
|
337
517
|
'shadow': fade('light-text-color', 30%),
|
|
518
|
+
// The two tones every elevation level is mixed from, and the five-step ramp
|
|
519
|
+
// itself. A component names a level - `var(--mw-elevation-2)` - instead of
|
|
520
|
+
// inventing another `0 5px 9px`; before this the framework carried twelve
|
|
521
|
+
// different shadow values for what were really four heights.
|
|
522
|
+
'shadow-near': fade('light-text-color', $shadow-near-light),
|
|
523
|
+
'shadow-far': fade('light-text-color', $shadow-far-light),
|
|
524
|
+
'elevation-1': elevation('light', 1px, 2px, 1px, 4px),
|
|
525
|
+
'elevation-2': elevation('light', 2px, 4px, 5px, 12px),
|
|
526
|
+
'elevation-3': elevation('light', 3px, 6px, 10px, 24px),
|
|
527
|
+
'elevation-4': elevation('light', 6px, 12px, 18px, 40px),
|
|
528
|
+
'elevation-5': elevation('light', 10px, 20px, 30px, 64px),
|
|
338
529
|
'text-color': $light-text-color,
|
|
339
530
|
'text-muted-color': muted(0.5),
|
|
340
531
|
// Same idea, other direction: a color too light to be read on paper is
|
|
@@ -357,7 +548,7 @@ $header-colors: (
|
|
|
357
548
|
'background': deepen('primary-color', $header-surface),
|
|
358
549
|
'text-color': tint('primary-color', 92%),
|
|
359
550
|
'navbar-list-color': fade('header-text-color', 70%),
|
|
360
|
-
'navbar-list-active-color': tint('primary-color',
|
|
551
|
+
'navbar-list-active-color': tint('primary-color', $header-active-tint),
|
|
361
552
|
// The burger sits on the primary button surface, and on the secondary one
|
|
362
553
|
// while the drawer is open, so it takes the label ink of each - not the
|
|
363
554
|
// header text color, which is picked for the dark bar behind it.
|
|
@@ -466,9 +657,6 @@ $radius: (
|
|
|
466
657
|
'full': 50%,
|
|
467
658
|
);
|
|
468
659
|
|
|
469
|
-
// Transitions
|
|
470
|
-
$transition-default: all 0.3s ease;
|
|
471
|
-
|
|
472
660
|
// Z-index
|
|
473
661
|
$z-index: (
|
|
474
662
|
'lift': 3,
|