maverick-wave 5.8.0 → 5.10.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 (43) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +22 -12
  2. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +10 -1
  3. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +4 -3
  4. package/.claude/skills/mw-maverick-wave/references/components.md +23 -9
  5. package/.claude/skills/mw-maverick-wave/references/javascript.md +1 -1
  6. package/.claude/skills/mw-maverick-wave/references/layout.md +8 -3
  7. package/.claude/skills/mw-maverick-wave/references/theming.md +48 -25
  8. package/CHANGELOG.md +13 -0
  9. package/README.md +41 -24
  10. package/index.html +325 -20
  11. package/maverick-wave.min.css +61 -5
  12. package/maverick-wave.min.js +1 -1
  13. package/package.json +2 -2
  14. package/scripts/verify.js +7 -1
  15. package/src/js/main.js +24 -5
  16. package/src/partials/palette-container.html +105 -65
  17. package/src/partials/preview-container.html +136 -0
  18. package/src/partials/utilities-container.html +38 -0
  19. package/src/scss/abstracts/_functions.scss +31 -3
  20. package/src/scss/abstracts/_mixins.scss +62 -12
  21. package/src/scss/abstracts/_variables.scss +47 -21
  22. package/src/scss/base/_base.scss +97 -8
  23. package/src/scss/components/_badge.scss +1 -1
  24. package/src/scss/components/_buttons.scss +1 -1
  25. package/src/scss/components/_cards.scss +23 -14
  26. package/src/scss/components/_dropdown.scss +58 -1
  27. package/src/scss/components/_kanban.scss +10 -9
  28. package/src/scss/components/_lang-switch.scss +1 -1
  29. package/src/scss/components/_localhost-indicator.scss +1 -1
  30. package/src/scss/components/_modals.scss +13 -1
  31. package/src/scss/components/_pricing.scss +1 -1
  32. package/src/scss/components/_progress.scss +9 -5
  33. package/src/scss/components/_skeleton.scss +1 -1
  34. package/src/scss/components/_spinners.scss +5 -5
  35. package/src/scss/components/_stepper.scss +4 -1
  36. package/src/scss/components/_tabs.scss +28 -8
  37. package/src/scss/components/_tiles.scss +41 -16
  38. package/src/scss/components/_timelines.scss +12 -5
  39. package/src/scss/layout/_main.scss +1 -1
  40. package/src/scss/layout/_parallax.scss +10 -5
  41. package/src/scss/utilities/_elevation.scss +23 -0
  42. package/src/scss/utilities/_touch-targets.scss +1 -1
  43. package/src/scss/utilities/_variants.scss +74 -7
@@ -44,14 +44,14 @@ Load the one you need - do not read them all up front.
44
44
  ```html
45
45
  <link
46
46
  rel="stylesheet"
47
- href="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.css"
47
+ href="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.css"
48
48
  />
49
49
  <link
50
50
  rel="stylesheet"
51
51
  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
52
52
  />
53
53
  ...
54
- <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.js"></script>
54
+ <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.js"></script>
55
55
  ```
56
56
 
57
57
  Pin the version. The JS file is optional and only for server-rendered/static pages -
@@ -151,9 +151,9 @@ keys at all (negative gap is invalid CSS and is not generated).
151
151
  `5xl` 3, `6xl` 4.3 rem.
152
152
 
153
153
  **Breakpoints**: `xs` 375, `sm` 576, `md` 768, `lg` 992, `xl` 1200, `2xl` 1400 px.
154
- Column grids and most components are mobile-first (`min-width`); the ranges do
155
- not overlap, `media-down` stops 0.02px short of its breakpoint. Blocks that only
156
- adjust something on a phone stay `max-width`.
154
+ Column grids and most components are mobile-first. The mixins emit range syntax
155
+ (`media-up` is `width >= bp`, `media-down` is `width < bp`), so the two never
156
+ overlap at the breakpoint itself and neither has to stop a fraction short of it.
157
157
 
158
158
  **Overriding.** Everything the framework emits sits in
159
159
  `@layer mw.reset, mw.base, mw.forms, mw.components, mw.layout, mw.utilities`.
@@ -187,16 +187,23 @@ Two shadows per level - a tight contact layer plus a wide ambient one:
187
187
  `0` is explicitly flat. Never write a `box-shadow` by hand - the twelve one-off
188
188
  values that used to exist are exactly what this replaced.
189
189
 
190
+ **Glow** (`mw-glow`, `mw-glow-{primary,secondary,info,success,warning,danger}`).
191
+ The same two-layer idea without the offset, so the surface reads as giving off
192
+ the light rather than casting a shadow. For the one element on a screen that has
193
+ to be seen first, and for nothing else.
194
+
190
195
  **Motion** `--mw-duration-instant|fast|base|slow|slower` = 110/180/300/520/900ms
191
196
  plus `--mw-duration-zoom` (650ms, for a large surface actually travelling),
192
197
  `--mw-ease-out` (things arriving - the default), `--mw-ease-in-out` (A to B and
193
198
  back), `--mw-ease-spring` (a pop). Two ready-made transitions:
194
199
  `var(--mw-transition)` for hover and focus states, `var(--mw-transition-fast)`
195
200
  for anything that should feel instant under the pointer. Both list their
196
- properties explicitly rather than saying `all`.
201
+ properties explicitly rather than saying `all`. All six durations are multiplied
202
+ by `--mw-motion-scale` (default 1), the tempo counterpart to
203
+ `--mw-radius-scale`; `prefers-reduced-motion` overrides the result.
197
204
 
198
205
  **Control sizes** `--mw-control-height-sm|base|lg` = 1.875 / 2.125 / 2.375rem (an
199
- even 30 / 34 / 38px step) and
206
+ even 30 / 34 / 38px step, moved by `mw-density-compact` and `-roomy`) and
200
207
  `--mw-control-font-sm|base|lg` = 0.8 / 0.9 / 1rem, shared by `mw-input`,
201
208
  `mw-select`, `mw-textarea` and `mw-btn`. A field and the button beside it are
202
209
  the same height by construction. Buttons run one font step above the fields.
@@ -264,7 +271,8 @@ feature frame) ·
264
271
  `mw-skip-link` · `mw-row-split` (+ `center`) · `mw-text-numeric` /
265
272
  `mw-text-currency` · `mw-text-truncate` / `mw-text-clamp-2..5` /
266
273
  `mw-text-break` / `mw-text-nowrap` · `mw-text-balance` / `mw-text-pretty` /
267
- `mw-text-eyebrow` / `mw-text-measure` · `mw-elevation-0..5` ·
274
+ `mw-text-eyebrow` / `mw-text-measure` · `mw-elevation-0..5` · `mw-glow` /
275
+ `mw-glow-{primary,secondary,info,success,warning,danger}` ·
268
276
  `mw-corner-plain` ·
269
277
  `mw-aspect-square|video|wide|portrait|photo` · `mw-d-{sm,md,lg,xl}-*` /
270
278
  `mw-hide-mobile` / `mw-hide-desktop` · `mw-overflow-*` / `mw-snap-x` ·
@@ -342,10 +350,12 @@ feature frame) ·
342
350
  and the open state come from, and it works without script. The framework JS
343
351
  only adds Escape and click-outside. Writing your own div-plus-click loses all
344
352
  of it.
345
- 17. **An open dropdown is clipped by anything that hides its overflow.** The menu
346
- is absolutely positioned. The framework's own containers - panel, card, tile,
347
- modal body, responsive table - lift the clip while a menu is open. On your
348
- own container it is one line:
353
+ 17. **An open dropdown can be clipped by anything that hides its overflow.**
354
+ Where the browser supports anchor positioning the menu is `position: fixed`
355
+ and anchored to its trigger, so it escapes the clip on its own. Everywhere
356
+ else it is absolutely positioned and cut off. Either way the framework's own
357
+ containers (panel, card, tile, modal body, responsive table) lift the clip
358
+ while a menu is open. On your own container it is one line:
349
359
  `:has(.mw-dropdown[open]) { overflow: visible }`.
350
360
  18. **Never write `box-shadow` by hand.** Use `var(--mw-elevation-1..5)` or the
351
361
  `mw-elevation-*` class. A hand-rolled shadow is the wrong colour in one of
@@ -18,7 +18,13 @@ const STORAGE_KEY = 'mw-theme';
18
18
 
19
19
  @Injectable({ providedIn: 'root' })
20
20
  export class ThemeService {
21
- readonly light = signal(localStorage.getItem(STORAGE_KEY) === 'light');
21
+ // With nothing stored the stylesheet already follows the OS, so the initial
22
+ // value only has to agree with what is on screen
23
+ readonly light = signal(
24
+ localStorage.getItem(STORAGE_KEY)
25
+ ? localStorage.getItem(STORAGE_KEY) === 'light'
26
+ : matchMedia('(prefers-color-scheme: light)').matches
27
+ );
22
28
 
23
29
  constructor() {
24
30
  effect(() => {
@@ -27,6 +33,9 @@ export class ThemeService {
27
33
 
28
34
  root.classList.add('mw-theme-switching');
29
35
  document.body.classList.toggle('mw-theme-light', light);
36
+ // the explicit counterpart - without it a dark choice on a light machine
37
+ // falls back to the OS preference
38
+ document.body.classList.toggle('mw-theme-dark', !light);
30
39
  void root.offsetHeight; // commit the colours with transitions off
31
40
  root.classList.remove('mw-theme-switching');
32
41
 
@@ -20,7 +20,7 @@ accordion FAQ.
20
20
 
21
21
  <link
22
22
  rel="stylesheet"
23
- href="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.css"
23
+ href="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.css"
24
24
  />
25
25
  <link
26
26
  rel="stylesheet"
@@ -487,7 +487,7 @@ accordion FAQ.
487
487
  <div class="mw-modal-backdrop" onclick="closeModal('demo')"></div>
488
488
  </div>
489
489
 
490
- <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.js"></script>
490
+ <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.js"></script>
491
491
  <script>
492
492
  // The only thing the shipped script does not cover: opening a modal.
493
493
  // Closing works through .mw-modal-close, the backdrop is wired above.
@@ -505,7 +505,8 @@ accordion FAQ.
505
505
  ## Notes
506
506
 
507
507
  - The theme toggle needs no code - the script persists the choice under
508
- `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and wraps the
508
+ `localStorage['mw-theme']`, toggles `mw-theme-light` / `mw-theme-dark` on
509
+ `<body>` and wraps the
509
510
  flip in `mw-theme-switching` so it starts no transitions.
510
511
  - The scroll spy sets `mw-active` on the `mw-navbar-link` whose `href` matches
511
512
  the `section[id]` currently in view - the `<section id="…">` elements are
@@ -224,13 +224,21 @@ picker.
224
224
  means it sits right, not left. Everything is vertically centered, so plain text
225
225
  lines up with a button next to it. Actions that no longer fit next to each
226
226
  other wrap.
227
- - The footer stacks to full-width children once the **card** is narrower than
228
- 360px, not once the window is - a card in a three-column grid is just as narrow
229
- on a 1200px desktop as it is on a phone, and now looks the same in both. This
230
- is a container query: a card that has a footer declares
231
- `container: mw-card / inline-size` and takes `width: 100%`, so it fills its
232
- slot instead of sizing to its content. Give such a card an explicit width if
233
- you need it to hug its content.
227
+ - The card measures **itself**, not the window - a card in a three-column grid
228
+ is just as narrow on a 1200px desktop as it is on a phone, and looks the same
229
+ in both. Every card declares `container: mw-card / inline-size` and takes
230
+ `width: 100%`, so it fills its slot instead of sizing to its content. Give a
231
+ card an explicit width if you need it to hug its content.
232
+ - What steps on the card's own width: the footer stacks to full-width children
233
+ below 360px, and the title, the subtitle and the body padding step up at
234
+ 420px. The image height is the one that still follows the window, because it
235
+ is declared on the card itself and a container cannot query itself.
236
+ - `mw-tile` works the same way, and so do `mw-stepper` and both timelines: the
237
+ step labels shrink and the two-sided timeline collapses to one side once the
238
+ component is narrow, whatever the window does. One catch on tiles: a tile
239
+ clips its overflow, and being a container makes it the containing block for
240
+ `position: fixed`, so a `data-tooltip` **inside** a tile is cut off at its
241
+ edge. Cards do not clip and are unaffected.
234
242
  - `mw-card-badge` (top right corner) and `mw-card-ribbon` (diagonal banner) are
235
243
  absolutely positioned overlays; colour them with `mw-card-addon-primary`,
236
244
  `-secondary`, `-success`, `-warning`, `-danger`, `-info`.
@@ -813,6 +821,10 @@ tables you know overflow, leave it off the ones that fit.
813
821
  A board is a grid of equally wide lanes; a ticket is a plain `mw-card` with
814
822
  `mw-kanban-card` on top. Everything except the counters and the composer is CSS.
815
823
 
824
+ The board puts as many lanes in a row as fit at 260px each and wraps the rest
825
+ onto the next. It measures its own width for that, so a board inside a panel or
826
+ a half-width column wraps on a desktop too.
827
+
816
828
  ```html
817
829
  <div class="mw-kanban" style="--mw-kanban-column-min-height: 390px">
818
830
  <div class="mw-kanban-column">
@@ -1207,8 +1219,10 @@ Parts: `mw-dropdown-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
1207
1219
  picks rather than acts. Icons inside items keep one column, so labels line up
1208
1220
  whether or not every item has one.
1209
1221
 
1210
- The menu is absolutely positioned and is clipped by any ancestor that hides its
1211
- overflow. The framework's own containers lift that clip while a menu is open; on
1222
+ Where the browser supports anchor positioning the menu is `position: fixed`,
1223
+ anchored to its trigger and flipped into whichever side has room, so it escapes
1224
+ a clipping ancestor by itself. Everywhere else it is absolutely positioned and
1225
+ is clipped by any ancestor that hides its overflow. The framework's own containers lift that clip while a menu is open; on
1212
1226
  your own it is `:has(.mw-dropdown[open]) { overflow: visible }`.
1213
1227
 
1214
1228
  On a coarse pointer the rows grow to 2.75rem and the menu takes at least the
@@ -33,7 +33,7 @@ classes are the entire contract.
33
33
  | Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, writes `aria-expanded` when the button is a `<button>`, closes on anchor click and on Escape (focus returns to the button) | One signal, bound to both; reset it on navigation end |
34
34
  | Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
35
35
  | Anchor scrolling | Intercepts `a[href^="#"]` and runs its own eased scroll - duration scales with distance, capped at 1.4s, cancelled by wheel or touch. Lands on `scroll-padding-top`, moves focus to the target, writes the hash with `replaceState`, and measures a sticky target unpinned | The router; for in-page anchors `scrollIntoView({ behavior: 'smooth' })` or your own animation |
36
- | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `mw-active` on the toggle, wrapped in `mw-theme-switching` on `<html>` so the flip starts no transitions | A theme service - see `examples/angular-services.md` |
36
+ | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` / `mw-theme-dark` on `<body>` and `mw-active` on the toggle, wrapped in `mw-theme-switching` on `<html>` so the flip starts no transitions. With nothing stored it takes `prefers-color-scheme` and keeps following it | A theme service - see `examples/angular-services.md` |
37
37
  | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
38
38
  | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
39
39
  | Alerts | Close button adds `mw-alert-closing` (fade out), then `mw-alert-closed` (`display: none`) after `--mw-duration-base` | Remove the alert from the list/signal |
@@ -183,8 +183,8 @@ as the header's first child under the same condition.
183
183
  `data-tooltip="..."` is a global attribute hook, not a class - it works on any
184
184
  element and shows a tooltip above it on hover or keyboard focus. It is pure CSS
185
185
  (a pseudo element on the trigger), which also means anything that clips its
186
- overflow cuts it off: a scroll container, or a card carrying a ribbon - plain
187
- cards do not clip. Close to the screen edge the bubble can run out of the
186
+ overflow cuts it off: a scroll container, a tile, or a card carrying a ribbon -
187
+ plain cards do not clip. Close to the screen edge the bubble can run out of the
188
188
  viewport, so keep long tooltips off the outermost elements.
189
189
 
190
190
  ## Sections
@@ -444,7 +444,7 @@ automatically.
444
444
  `mw-d-inline-block`, `mw-d-grid`, `mw-d-none`, `mw-d-contents`.
445
445
 
446
446
  **Responsive display** - `mw-d-{sm|md|lg|xl}-{none|block|flex|inline-flex|grid|inline-block}`.
447
- Mobile-first and min-width, like everything else here: `mw-d-md-flex` means
447
+ Mobile-first like everything else here: `mw-d-md-flex` means
448
448
  "flex from the md breakpoint up", and the unprefixed class beside it is what
449
449
  applies below that. The pair `mw-d-none mw-d-md-flex` is hidden on a phone and a
450
450
  row from a tablet on. `mw-hide-mobile` (gone below md) and `mw-hide-desktop`
@@ -469,6 +469,11 @@ with it; `mw-text-capitalize` is the plain transform.
469
469
  **Elevation** - `mw-elevation-0` through `-5`, the same ramp every component
470
470
  uses. See the scale table in `SKILL.md`. Never hand-roll a `box-shadow`.
471
471
 
472
+ **Glow** - `mw-glow` plus
473
+ `mw-glow-{primary,secondary,info,success,warning,danger}`. Elevation without the
474
+ offset, so the surface gives off the light instead of casting it. Once per
475
+ screen at most.
476
+
472
477
  **Scroll entrance** - `mw-reveal` lets a block rise briefly as it scrolls into
473
478
  view, driven by the browser's scroll timeline (`animation-timeline: view()`).
474
479
  The block stays hidden while it is still below the bottom edge and has arrived
@@ -135,8 +135,19 @@ Two rules that prevent most colour bugs:
135
135
 
136
136
  ## Light & dark
137
137
 
138
- - Dark is the base. Light is applied by putting `mw-theme-light` on `<body>` -
139
- that class only re-points the theme aliases at the `--mw-light-*` set.
138
+ - Every theme token is declared once on `:root` as `light-dark(light, dark)`.
139
+ Which half applies is decided by `color-scheme` on the element that **uses**
140
+ the token, and `:root` carries `color-scheme: light dark` - so a page follows
141
+ the OS until someone chooses otherwise.
142
+ - `mw-theme-light` and `mw-theme-dark` on `<body>` are that choice. They set
143
+ little more than `color-scheme`, and every token follows. `mw-theme-dark` is
144
+ not redundant: without it, a reader who picks dark on a machine set to light
145
+ would be pulled straight back to the OS preference.
146
+ - Chrome 119-122 and Safari 16.4-17.4 are in the browserslist but predate
147
+ `light-dark()`. `base/_base.scss` keeps the old route for them behind
148
+ `@supports` - dark on `:root`, light by class, no OS tracking.
149
+ - `--mw-hero-image-filter` is the one theme value that is not a colour and so
150
+ cannot ride along; it follows `prefers-color-scheme` instead.
140
151
  - Card, footer and border are derived from the page background by scaling its
141
152
  OKLch lightness and chroma by one factor. A card steps **away from the text
142
153
  colour** - darker than the page in the dark theme, lighter in the light one.
@@ -176,7 +187,9 @@ Two rules that prevent most colour bugs:
176
187
  or move it down with the bar.
177
188
  - Persisting the choice, the toggle UI and the initial class are the
178
189
  application's job in a SPA (`examples/angular-services.md`). The shipped JS
179
- does it for static pages using `localStorage` under the key `mw-theme`.
190
+ does it for static pages using `localStorage` under the key `mw-theme`. With
191
+ nothing stored it takes the OS preference and keeps tracking it live, so
192
+ changing the system theme moves the page until the reader picks a side.
180
193
  - **Suppress transitions while the class flips.** Colour changes on nearly every
181
194
  element at once, and the shared `--mw-transition` turns that into thousands of
182
195
  concurrent animations - on a documentation-sized page it is seconds of blocked
@@ -189,29 +202,35 @@ const root = document.documentElement;
189
202
 
190
203
  root.classList.add('mw-theme-switching');
191
204
  document.body.classList.toggle('mw-theme-light', isLight);
205
+ document.body.classList.toggle('mw-theme-dark', !isLight);
192
206
  void root.offsetHeight; // commit the new colours with transitions off
193
207
  root.classList.remove('mw-theme-switching');
194
208
  ```
195
209
 
196
210
  ## Site-wide variants
197
211
 
198
- Twelve classes on `<html>` and five custom properties retune the whole look
199
- without a rebuild. They stack, and none of them need a class per element.
200
-
201
- | Class | Effect |
202
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
203
- | `mw-corners-even` | Drops the surface signature - every card, panel and modal becomes an evenly rounded `radius('md')` box |
204
- | `mw-accent-single` | `--mw-secondary-color` follows the primary; `mw-btn-secondary` turns outline so the two stay apart |
205
- | `mw-shadows-flat` | Elevation 1-3 to `none`. The dropdown (4) and the modal (5) keep their shadow |
206
- | `mw-surfaces-flush` | Card, panel and footer background drop to the page colour and the border is redrawn from the ink, because the old one is a neighbouring shade of a surface that is now the page |
207
- | `mw-hover-static` | No hover travels - lifts, image zooms and slides go. Colour and border still respond |
208
- | `mw-scroll-static` | No scroll entrance - `mw-reveal` and `mw-reveal-stagger` blocks sit where they land |
209
- | `mw-sections-plain` | The diagonal hatch behind `mw-section-alternate` collapses into the page colour |
210
- | `mw-headings-caps` | `h1`-`h3` in capitals with 0.045em tracking |
211
- | `mw-btn-pill` | `mw-btn` fully rounded. Form fields keep their own radius |
212
- | `mw-btn-square` | `mw-btn` to `border-radius: 0`. The other end of the same axis, and unlike `--mw-radius-scale: 0` it leaves the rest of the page rounded |
213
- | `mw-btn-glass` | Filled buttons become tinted glass: translucent wash, vertical ramp, specular top edge. No `backdrop-filter` - it would trap a fixed-position tooltip inside the button |
214
- | `mw-btn-tactile` | The four solid variants stand on a 3px edge mixed toward black and travel its full height on `:active`. Outline, ghost and link have no fill to darken and are untouched |
212
+ Fifteen classes on `<html>` and six custom properties retune the whole look
213
+ without a rebuild. They stack, and none of them need a class per element. The
214
+ showcase at https://maverick-wave.m1well.com has a picker for the set, and its
215
+ URL carries the choice - a link is how a look gets agreed on before it is built.
216
+
217
+ | Class | Effect |
218
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
219
+ | `mw-corners-even` | Drops the surface signature - every card, panel and modal becomes an evenly rounded `radius('md')` box |
220
+ | `mw-accent-single` | `--mw-secondary-color` follows the primary; `mw-btn-secondary` turns outline so the two stay apart |
221
+ | `mw-shadows-flat` | Elevation 1-3 to `none`. The dropdown (4) and the modal (5) keep their shadow |
222
+ | `mw-surfaces-flush` | Card, panel and footer background drop to the page colour and the border is redrawn from the ink, because the old one is a neighbouring shade of a surface that is now the page |
223
+ | `mw-hover-static` | No hover travels - lifts, image zooms and slides go. Colour and border still respond |
224
+ | `mw-scroll-static` | No scroll entrance - `mw-reveal` and `mw-reveal-stagger` blocks sit where they land |
225
+ | `mw-sections-plain` | The diagonal hatch behind `mw-section-alternate` collapses into the page colour |
226
+ | `mw-headings-caps` | `h1`-`h3` in capitals with 0.045em tracking |
227
+ | `mw-links-underline` | `mw-link`, `mw-link-muted` and `mw-btn-link` carry their underline at rest. The hover signal moves to the stroke, which thickens to 3px - `mw-link` has no colour change of its own |
228
+ | `mw-btn-pill` | `mw-btn` fully rounded. Form fields keep their own radius |
229
+ | `mw-btn-square` | `mw-btn` to `border-radius: 0`. The other end of the same axis, and unlike `--mw-radius-scale: 0` it leaves the rest of the page rounded |
230
+ | `mw-btn-glass` | Filled buttons become tinted glass: translucent wash, vertical ramp, specular top edge. No `backdrop-filter` - it would trap a fixed-position tooltip inside the button |
231
+ | `mw-btn-tactile` | The four solid variants stand on a 3px edge mixed toward black and travel its full height on `:active`. Outline, ghost and link have no fill to darken and are untouched |
232
+ | `mw-density-compact` | Card and panel padding and the three control heights drop one step down the spacing scale. Type is untouched - `--mw-root-font-size` is the switch that scales everything together |
233
+ | `mw-density-roomy` | The same one step up |
215
234
 
216
235
  | Property | Effect |
217
236
  | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
@@ -220,20 +239,24 @@ without a rebuild. They stack, and none of them need a class per element.
220
239
  | `--mw-font-family-heading` | Headline typeface; body copy is untouched |
221
240
  | `--mw-container-width` | Where the content stops growing |
222
241
  | `--mw-section-padding-block` | Air above and below each section |
242
+ | `--mw-motion-scale` | Multiplies all six duration tokens - `0.6` brisk, `1.6` relaxed. `prefers-reduced-motion` still overrides it |
223
243
 
224
244
  **Writing your own.** A variant that retunes a _theme-bound_ token - anything in
225
- the dark/light maps, elevation and shadow above all - cannot be written on
226
- `:root` alone: `mw-theme-light` re-declares those on `<body>`, which shadows the
227
- root value for the entire subtree and the switch does nothing in light mode.
228
- Target both:
245
+ the dark/light maps - cannot be written on `:root` alone: `mw-theme-light`
246
+ re-declares those on `<body>`, which shadows the root value for the entire
247
+ subtree and the switch does nothing in light mode. Target both:
229
248
 
230
249
  ```scss
231
250
  :root.my-variant,
232
251
  :root.my-variant .mw-theme-light {
233
- --mw-elevation-2: none;
252
+ --mw-card-background: var(--mw-page-background);
234
253
  }
235
254
  ```
236
255
 
256
+ `--mw-elevation-1..5` are the exception and need only `:root`: the ramp is one
257
+ set for both themes and reaches the theme through `--mw-shadow-near/-far`, so
258
+ there is no second declaration further down the tree to shadow it.
259
+
237
260
  The same trap the other way round: never copy a theme alias _into_ a token on
238
261
  `:root` (`--mw-x: var(--mw-page-background)`), because up there it still holds
239
262
  the dark value. Write that rule on the element instead.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ Patch releases are only for test purposes - here I only document major and minor
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [5.10.0] - 2026-09-13
10
+
11
+ ### Fixed
12
+
13
+ - technical issues
14
+
15
+ ## [5.9.0] - 2026-09-13
16
+
17
+ ### Changed
18
+
19
+ - finalized showcase variants
20
+ - changed some little glow up
21
+
9
22
  ## [5.8.0] - 2026-09-12
10
23
 
11
24
  ### Added
package/README.md CHANGED
@@ -23,7 +23,7 @@ The result is a framework that balances utility with simplicity, offering develo
23
23
  - A five-step elevation ramp and a motion scale, so every shadow and every transition in the framework comes from one place
24
24
  - Mobile as a first-class target: 44px touch targets on a coarse pointer, modals that become bottom sheets, press states on everything, and hover effects that do not latch after a tap
25
25
  - Easy Customization via CSS Custom Properties
26
- - Built-in Light & Dark Mode with optional theme switching
26
+ - Built-in Light & Dark Mode - follows the OS by default, switchable per reader
27
27
  - SCSS Source Files for advanced customization (Dart Sass, `@use`/`@forward`)
28
28
  - Modals as `<div>` or as `<dialog>` - the latter brings the focus trap, Escape and the inert background from the platform
29
29
  - Native form validation is styled through `:user-invalid`, alongside the class-driven error states for reactive forms
@@ -42,12 +42,12 @@ The result is a framework that balances utility with simplicity, offering develo
42
42
  <title>My MaverickWave Project</title>
43
43
  <link
44
44
  rel="stylesheet"
45
- href="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.css"
45
+ href="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.css"
46
46
  />
47
47
  </head>
48
48
  <body>
49
49
  <!-- Your content here -->
50
- <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.8.0/maverick-wave.min.js"></script>
50
+ <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.9.0/maverick-wave.min.js"></script>
51
51
  </body>
52
52
  </html>
53
53
  ```
@@ -130,7 +130,8 @@ assembled rather than designed.
130
130
  :root {
131
131
  /* Elevation. Each level is two shadows - a tight contact layer that gives
132
132
  the box weight, and a wide ambient one that says how high it floats.
133
- Declared per theme, because the tones they are mixed from differ. */
133
+ One ramp for both themes; the theme arrives through the shadow tokens
134
+ the levels are built from. */
134
135
  --mw-elevation-1: /* resting: inputs, tags, small controls */;
135
136
  --mw-elevation-2: /* raised: cards and panels at rest */;
136
137
  --mw-elevation-3: /* floating: a card under the pointer */;
@@ -347,32 +348,42 @@ layer of its own:
347
348
 
348
349
  ### Site-wide variants
349
350
 
350
- Twelve classes on `<html>` retune the whole look without touching markup or
351
+ Fifteen classes on `<html>` retune the whole look without touching markup or
351
352
  rebuilding. They stack.
352
353
 
353
- | Class | Effect |
354
- | ------------------- | ----------------------------------------------------------------------- |
355
- | `mw-corners-even` | Drops the surface signature - every panel becomes an evenly rounded box |
356
- | `mw-accent-single` | One brand colour instead of two; `mw-btn-secondary` turns outline |
357
- | `mw-shadows-flat` | Elevation 1-3 to `none`; dropdown and modal keep theirs |
358
- | `mw-surfaces-flush` | Cards, panels and footer on the page colour, held by their border |
359
- | `mw-hover-static` | No hover travels - lifts and image zooms go, colour still responds |
360
- | `mw-scroll-static` | No scroll entrance - `mw-reveal` blocks sit where they land |
361
- | `mw-sections-plain` | The hatch behind `mw-section-alternate` collapses into the page colour |
362
- | `mw-headings-caps` | `h1`-`h3` in capitals |
363
- | `mw-btn-pill` | Fully rounded buttons; form fields keep their radius |
364
- | `mw-btn-square` | Buttons cut to a hard corner while the page keeps its radius |
365
- | `mw-btn-glass` | Filled buttons become a translucent wash with a lit top edge |
366
- | `mw-btn-tactile` | Filled buttons stand on a darker edge and sink onto it when pressed |
367
-
368
- Five properties do the rest: `--mw-radius-scale` multiplies every radius (`0`
354
+ | Class | Effect |
355
+ | -------------------- | ----------------------------------------------------------------------- |
356
+ | `mw-corners-even` | Drops the surface signature - every panel becomes an evenly rounded box |
357
+ | `mw-accent-single` | One brand colour instead of two; `mw-btn-secondary` turns outline |
358
+ | `mw-shadows-flat` | Elevation 1-3 to `none`; dropdown and modal keep theirs |
359
+ | `mw-surfaces-flush` | Cards, panels and footer on the page colour, held by their border |
360
+ | `mw-hover-static` | No hover travels - lifts and image zooms go, colour still responds |
361
+ | `mw-scroll-static` | No scroll entrance - `mw-reveal` blocks sit where they land |
362
+ | `mw-sections-plain` | The hatch behind `mw-section-alternate` collapses into the page colour |
363
+ | `mw-headings-caps` | `h1`-`h3` in capitals |
364
+ | `mw-links-underline` | Links underlined at rest; the stroke thickens on hover |
365
+ | `mw-btn-pill` | Fully rounded buttons; form fields keep their radius |
366
+ | `mw-btn-square` | Buttons cut to a hard corner while the page keeps its radius |
367
+ | `mw-btn-glass` | Filled buttons become a translucent wash with a lit top edge |
368
+ | `mw-btn-tactile` | Filled buttons stand on a darker edge and sink onto it when pressed |
369
+ | `mw-density-compact` | Less padding in cards, panels and controls; type stays put |
370
+ | `mw-density-roomy` | More of the same |
371
+
372
+ Six properties do the rest: `--mw-radius-scale` multiplies every radius (`0`
369
373
  squares the framework off), `--mw-root-font-size` moves the whole rem scale,
370
- plus `--mw-font-family-heading`, `--mw-container-width` and
374
+ `--mw-motion-scale` the tempo of every transition, plus
375
+ `--mw-font-family-heading`, `--mw-container-width` and
371
376
  `--mw-section-padding-block`.
372
377
 
378
+ The [showcase](https://maverick-wave.m1well.com) has a picker for all of them,
379
+ and the URL carries whatever is set - send that link and the next person opens
380
+ the page exactly as you left it. The panel prints the same setup as markup and
381
+ CSS to copy into a project.
382
+
373
383
  A variant of your own that retunes a theme-bound token has to target
374
384
  `:root.your-class` **and** `:root.your-class .mw-theme-light` - the light theme
375
- re-declares those on `<body>` and would shadow a root-only value.
385
+ re-declares those on `<body>` and would shadow a root-only value. The elevation
386
+ tokens are the exception and need only `:root`.
376
387
 
377
388
  ### SCSS Source
378
389
 
@@ -458,7 +469,7 @@ above):
458
469
  > change detection. The behaviors it covers (accordion, tabs, modal close, mobile
459
470
  > nav, scroll spy, theme toggle, progress bars, sliders, alerts, galleries) are a
460
471
  > few lines each in a component - the framework's state classes are the whole
461
- > contract. Theme switching, for example, is a single class on `<body>`:
472
+ > contract. Theme switching, for example, is a pair of classes on `<body>`:
462
473
 
463
474
  ```typescript
464
475
  // theme.service.ts - mw-theme-switching suppresses the transitions the flip
@@ -467,10 +478,16 @@ const root = document.documentElement;
467
478
 
468
479
  root.classList.add('mw-theme-switching');
469
480
  document.body.classList.toggle('mw-theme-light', isLight);
481
+ // the explicit counterpart - without it a dark choice on a light machine
482
+ // falls back to the OS preference
483
+ document.body.classList.toggle('mw-theme-dark', !isLight);
470
484
  void root.offsetHeight;
471
485
  root.classList.remove('mw-theme-switching');
472
486
  ```
473
487
 
488
+ With neither class the page follows `prefers-color-scheme`, so an app that has
489
+ nothing stored yet can simply leave both off.
490
+
474
491
  The `mw-field` wrapper groups label, control, hint and error. Bind the error
475
492
  state yourself - the framework does not style Angular's `ng-invalid` /
476
493
  `ng-touched` classes: