maverick-wave 5.24.0 → 5.26.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 (90) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +32 -5
  2. package/.claude/skills/mw-maverick-wave/references/components.md +4 -0
  3. package/.claude/skills/mw-maverick-wave/references/javascript.md +44 -8
  4. package/.claude/skills/mw-maverick-wave/references/layout.md +39 -11
  5. package/.claude/skills/mw-maverick-wave/references/theming.md +34 -10
  6. package/CHANGELOG.md +20 -0
  7. package/CLAUDE.md +4 -0
  8. package/README.md +4 -2
  9. package/index.html +13 -0
  10. package/maverick-wave.min.css +7 -5
  11. package/maverick-wave.min.js +1 -1
  12. package/package.json +2 -2
  13. package/src/js/main.js +149 -57
  14. package/src/partials/accordions-container.html +1 -1
  15. package/src/partials/alerts-container.html +1 -1
  16. package/src/partials/announcement-container.html +1 -1
  17. package/src/partials/avatars-container.html +1 -1
  18. package/src/partials/badges-container.html +1 -1
  19. package/src/partials/blog-posts-container.html +2 -2
  20. package/src/partials/breadcrumbs-container.html +1 -1
  21. package/src/partials/button-bar-container.html +1 -1
  22. package/src/partials/buttons-container.html +1 -1
  23. package/src/partials/calendar-container.html +1 -1
  24. package/src/partials/cards-container.html +1 -1
  25. package/src/partials/code-container.html +1 -1
  26. package/src/partials/colors-container.html +3 -3
  27. package/src/partials/coming-soon-container.html +1 -1
  28. package/src/partials/contact-container.html +1 -1
  29. package/src/partials/divider-container.html +1 -1
  30. package/src/partials/dropdown-container.html +1 -1
  31. package/src/partials/empty-state-container.html +1 -1
  32. package/src/partials/form-container.html +1 -1
  33. package/src/partials/form-elements-container.html +1 -1
  34. package/src/partials/form-field-container.html +1 -1
  35. package/src/partials/gallery-container.html +1 -1
  36. package/src/partials/get-started-container.html +5 -10
  37. package/src/partials/grid-container.html +1 -1
  38. package/src/partials/header-utilities-container.html +2 -2
  39. package/src/partials/html-lists-container.html +1 -1
  40. package/src/partials/info-container.html +1 -1
  41. package/src/partials/input-group-container.html +2 -4
  42. package/src/partials/item-lists-container.html +1 -1
  43. package/src/partials/kanban-container.html +2 -2
  44. package/src/partials/kbd-container.html +1 -1
  45. package/src/partials/leader-row-container.html +2 -2
  46. package/src/partials/login-container.html +2 -2
  47. package/src/partials/lunch-menu-container.html +2 -2
  48. package/src/partials/media-container.html +1 -1
  49. package/src/partials/meta-info-container.html +1 -1
  50. package/src/partials/modals-container.html +1 -1
  51. package/src/partials/page-header-container.html +1 -1
  52. package/src/partials/pagination-container.html +2 -2
  53. package/src/partials/palette-container.html +2 -2
  54. package/src/partials/panels-container.html +2 -2
  55. package/src/partials/parallax-container.html +1 -1
  56. package/src/partials/pricing-container.html +2 -2
  57. package/src/partials/progress-container.html +1 -1
  58. package/src/partials/prose-container.html +1 -1
  59. package/src/partials/ratings-container.html +1 -1
  60. package/src/partials/section-head-container.html +6 -3
  61. package/src/partials/segmented-container.html +1 -1
  62. package/src/partials/skeleton-container.html +1 -1
  63. package/src/partials/spinners-container.html +1 -1
  64. package/src/partials/stepper-container.html +1 -1
  65. package/src/partials/tables-container.html +1 -1
  66. package/src/partials/tabs-container.html +1 -1
  67. package/src/partials/tags-container.html +1 -7
  68. package/src/partials/testimonials-container.html +1 -1
  69. package/src/partials/tiles-container.html +1 -1
  70. package/src/partials/typography-container.html +1 -1
  71. package/src/partials/utilities-container.html +1 -1
  72. package/src/scss/base/_base.scss +21 -5
  73. package/src/scss/components/_blog-post.scss +15 -10
  74. package/src/scss/components/_button-bar.scss +10 -6
  75. package/src/scss/components/_code.scss +23 -34
  76. package/src/scss/components/_info.scss +8 -1
  77. package/src/scss/components/_kanban.scss +6 -6
  78. package/src/scss/components/_meta-info.scss +7 -6
  79. package/src/scss/components/_tables.scss +6 -4
  80. package/src/scss/components/_tiles.scss +20 -30
  81. package/src/scss/layout/_grid.scss +28 -0
  82. package/src/scss/layout/_header.scss +16 -9
  83. package/src/scss/layout/_home.scss +21 -2
  84. package/src/scss/layout/_main.scss +7 -7
  85. package/src/scss/layout/_page-header.scss +4 -4
  86. package/src/scss/layout/_section.scss +33 -10
  87. package/src/scss/main-lean.scss +16 -0
  88. package/src/scss/utilities/_index.scss +1 -0
  89. package/src/scss/utilities/_print.scss +107 -0
  90. package/src/scss/utilities/_touch-targets.scss +4 -0
@@ -75,6 +75,22 @@ Or from the npm package via SCSS: `@use 'maverick-wave/src/scss/main';`
75
75
  (details and configuration in `references/theming.md`). The viewport meta above
76
76
  belongs in `src/index.html` there, with `viewport-fit=cover` just the same.
77
77
 
78
+ **`main-lean` for a page that uses a handful of components.** The full build is
79
+ ~41 kB gzipped and carries all 48 of them. `main-lean` is the same framework
80
+ without any component - tokens, reset, typography, layout, utilities - and the
81
+ components come in one by one next to it:
82
+
83
+ ```scss
84
+ @use 'maverick-wave/src/scss/main-lean' with (
85
+ $primary-color: #1b1b1d
86
+ );
87
+ @use 'maverick-wave/src/scss/components/buttons';
88
+ @use 'maverick-wave/src/scss/components/cards';
89
+ ```
90
+
91
+ That lands at ~16 kB gzipped plus what the components add (three of them ≈ 19 kB
92
+ in total). The configuration goes on `main-lean`, not on the component imports.
93
+
78
94
  > **Never load `maverick-wave.min.js` in a SPA.** Everything in it is wired up
79
95
  > once on `DOMContentLoaded` and writes straight into the DOM. In an Angular
80
96
  > app that fires once during bootstrap: components rendered later are never
@@ -166,6 +182,11 @@ between them moved.
166
182
  Column grids and most components are mobile-first. The mixins emit range syntax
167
183
  (`media-up` is `width >= bp`, `media-down` is `width < bp`), so the two never
168
184
  overlap at the breakpoint itself and neither has to stop a fraction short of it.
185
+ Write `media-up`: the base block is the phone and each breakpoint adds to it.
186
+ `media-down` is right in three cases - a rule that sets what the base never sets
187
+ (turned around it would hard-wire an inherited value), a narrow layout that costs
188
+ more properties than the wide one (the navbar sheet, the modal bottom sheet), and
189
+ a class that only ever means "below here", like `mw-hide-mobile`.
169
190
 
170
191
  **Overriding.** Everything the framework emits sits in
171
192
  `@layer mw.reset, mw.base, mw.forms, mw.components, mw.layout, mw.utilities`.
@@ -234,7 +255,8 @@ none of them survive into the compiled CSS. Full list in `references/theming.md`
234
255
  zoom. A headline passes `$to: 'xl'`.
235
256
  - `fluid-container($min, $max, $from, $to)` - the same ramp in `cqi` against the
236
257
  container. Use it instead of `fluid()` inside a `@container` block.
237
- - `media-up($bp)` / `media-down($bp)` - the breakpoint map as a query.
258
+ - `media-up($bp)` / `media-down($bp)` - the breakpoint map as a query. `media-up`
259
+ is the default direction, see **Breakpoints** above.
238
260
  - `touch-context($bp: 'md')` - coarse pointer _or_ narrow viewport, the condition
239
261
  every target-size rule in the framework hangs under.
240
262
  - `touch-floor($size: 2.75rem)` - that condition plus a `min-height`.
@@ -299,7 +321,7 @@ feature frame) ·
299
321
  `mw-content` ·
300
322
  `mw-section` · `mw-section-head` (+ `-title`, `-intro`, `-numbered`, colours) ·
301
323
  `mw-page-header` · `mw-grid-*`
302
- (+ `mw-grid-even`) ·
324
+ (+ `mw-grid-even`, `mw-grid-from-{sm,md,lg,xl}`) ·
303
325
  `mw-columns-2/3` · `mw-row-split` · `mw-hero` (+ `mw-scroll-hint`, `-end`) ·
304
326
  `mw-parallax` (+ `-media`, `-content`, `-dimmed`, `-sticky`, `-rise`,
305
327
  `-slow`, `-pattern`) · `mw-footer`
@@ -312,7 +334,7 @@ feature frame) ·
312
334
  `mw-glow-{primary,secondary,info,success,warning,danger}` ·
313
335
  `mw-corner-plain` ·
314
336
  `mw-aspect-square|video|wide|portrait|photo` · `mw-d-{sm,md,lg,xl}-*` /
315
- `mw-hide-mobile` / `mw-hide-desktop` · `mw-overflow-*` / `mw-snap-x` ·
337
+ `mw-hide-mobile` / `mw-hide-desktop` / `mw-hide-print` / `mw-print-only` · `mw-overflow-*` / `mw-snap-x` ·
316
338
  `mw-reveal` / `mw-reveal-stagger` · spacing, flex, display, text
317
339
 
318
340
  ## Pitfalls
@@ -477,13 +499,18 @@ feature frame) ·
477
499
  directly after the block, so the overlap reads as a surface and not a cut.
478
500
  It sits at `:where()` weight, so a plain `box-shadow` of your own on that
479
501
  section replaces it.
480
- 30. **`viewport-fit=cover` belongs in the viewport meta.** The container
502
+ 30. **A stored theme needs a script in the `<head>`.** `main.js` runs at the
503
+ end of the body, so a reader whose choice differs from the OS sees the
504
+ other theme for one frame. Five inline lines in the head apply it before
505
+ the first paint - the snippet is in `references/javascript.md`. The theme
506
+ classes live on `<html>` for exactly this reason.
507
+ 31. **`viewport-fit=cover` belongs in the viewport meta.** The container
481
508
  gutter, the mobile nav panel and the modal padding all budget for the
482
509
  cutout with `env(safe-area-inset-*)`, and iOS resolves every one of those
483
510
  to `0` without it - on a notched phone the content then sits under the
484
511
  rounded corner. Nothing errors, which is why the shipped JS writes a
485
512
  console warning on localhost when the meta is missing.
486
- 31. **Half the layout reads its own width, not the viewport.** `mw-card`,
513
+ 32. **Half the layout reads its own width, not the viewport.** `mw-card`,
487
514
  `mw-tile`, `mw-stepper`, both timelines and - since 5.13.0 -
488
515
  `mw-gallery-container` and `mw-image-slider` are `@container` queries, so
489
516
  any of them in a half-width column takes its narrow layout there while the
@@ -1319,6 +1319,10 @@ edge drops and the key sits 1px lower.
1319
1319
  - The cursor is `default` because a figure is usually not clickable. On `<a>`
1320
1320
  and `<button>` it turns into a pointer automatically; on anything else use
1321
1321
  `mw-info-clickable`.
1322
+ - `mw-info-block` is the same surface carrying a paragraph: 68 characters
1323
+ wide, left-aligned, and it does not lift on hover. It sits at the left edge
1324
+ like everything else with running text in it - `mw-info-block-center` puts it
1325
+ back in the middle of its container.
1322
1326
  - `mw-info-mini` is the 20px status dot: sizes `-sm`, `-lg`, `-xl`, colours
1323
1327
  `-primary`, `-secondary`, `-success`, `-warning`, `-danger`, `-info`.
1324
1328
 
@@ -2,15 +2,28 @@
2
2
 
3
3
  ## What `maverick-wave.min.js` is
4
4
 
5
- One vanilla IIFE, no dependencies, ~24 kB. It queries the DOM **once** on
6
- `DOMContentLoaded` and attaches listeners. There is no re-init API, no
7
- `MutationObserver`, no exported module - it is built for a server-rendered or
8
- static page.
5
+ One vanilla IIFE, no dependencies, ~24 kB. It queries the DOM on
6
+ `DOMContentLoaded` and attaches listeners. No `MutationObserver`, no exported
7
+ module - it is built for a server-rendered or static page.
8
+
9
+ Markup that arrives later gets wired through the one public entry point:
10
+
11
+ ```js
12
+ window.MaverickWave.init(subtree); // or init() for the whole document
13
+ ```
14
+
15
+ Call it after an htmx swap, after filling a modal from a fetch, or after a view
16
+ transition replaced the body. Every element that already carries listeners is
17
+ skipped, so calling it twice over the same markup does nothing - the components
18
+ are safe to re-run, the document- and window-level behaviour is wired once and
19
+ never again.
20
+
21
+ What that does **not** make it is a SPA library - see below.
9
22
 
10
23
  ## Why a SPA must not load it
11
24
 
12
- - It runs once during bootstrap. Anything rendered afterwards - i.e. everything
13
- in a routed application - never gets initialised.
25
+ - Anything rendered after bootstrap needs `MaverickWave.init()` by hand, for
26
+ every subtree, on every render - in a routed application that is all of it.
14
27
  - It writes straight into the DOM (class toggles, generated elements, inline
15
28
  styles). In Angular that happens outside change detection; in a zoneless app
16
29
  the framework never learns about it, and on the next re-render your bindings
@@ -23,6 +36,29 @@ in a Vite entry point. Load the **CSS only** and rebuild the handful of
23
36
  behaviours in components. Each one is a few lines - the framework's state
24
37
  classes are the entire contract.
25
38
 
39
+ ## The theme has to be applied before the first paint
40
+
41
+ `main.js` sits at the end of the body, so a stored choice that differs from the
42
+ operating system reaches the page one frame too late - the reader sees the other
43
+ theme flash. The fix is five lines in the `<head>`, inline and synchronous,
44
+ before the stylesheet has painted anything:
45
+
46
+ ```html
47
+ <script>
48
+ (function () {
49
+ var stored = localStorage.getItem('mw-theme');
50
+ if (stored === 'light' || stored === 'dark') {
51
+ document.documentElement.classList.add('mw-theme-' + stored);
52
+ }
53
+ })();
54
+ </script>
55
+ ```
56
+
57
+ That is also why the toggle writes the class on `<html>` and not on `<body>`:
58
+ in the head there is no body yet. An external file would not help - it paints
59
+ first. With nothing stored, nothing happens here and the page follows the OS on
60
+ its own through `light-dark()`.
61
+
26
62
  ## Behaviour inventory
27
63
 
28
64
  | Behaviour | What the shipped JS does | What to do instead |
@@ -33,11 +69,11 @@ classes are the entire contract.
33
69
  | Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar` plus `mw-nav-open` on `<body>`, 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 all three - the body class carries the scrim, the scroll lock and the pinning of the bar; reset it on navigation end |
34
70
  | Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
35
71
  | 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` / `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` |
72
+ | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` / `mw-theme-dark` on `<html>` 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
73
  | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
38
74
  | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
39
75
  | 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 |
40
- | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
76
+ | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.mwToggleCheckbox` | `[class.mw-selected]="item.checked"` |
41
77
  | Gallery | Generates the dots, moves the track, swipe handling (single finger, dominant axis, passive listeners), writes `mw-gallery-desc` | Render dots in the template, bind the track transform and `mw-active` on the current dot |
42
78
  | Image slider | Toggles `mw-active` on the overlay image and the control button with the matching `data-index` | Bind `mw-active` from the selected index |
43
79
  | Kanban board | Counts the tickets per lane, moves a card between lanes (`data-kanban-move`) and marks the arrival with `mw-kanban-card-moved-forward` / `-back`, clones `mw-kanban-card-template` on save, derives the next key from `data-kanban-prefix`, toggles `mw-active` on the composer | Keep the tickets in a signal/store and render the lanes from it; `mw-active` on the composer, `mw-kanban-editing` on the ticket it replaces. Neither the `<template>` nor the `mw-kanban-composer-*` hook classes are needed |
@@ -237,16 +237,16 @@ pointing at the trigger in every combination.
237
237
 
238
238
  ## Sections
239
239
 
240
- | Class | Use |
241
- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
242
- | `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 3.3rem (2.5rem below `md`, 1.75rem below `sm`); point it at `--mw-section-padding-fluid` for spacing that grows with the screen |
243
- | `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
244
- | `mw-section-head` | The opening of a section: hairline rule, a mark on its left end, title and lead - all on one left edge. Variants `-secondary`, `-success`, `-warning`, `-danger`, `-info` recolour the mark |
245
- | `mw-section-head-title` | The heading inside it, `2xl` growing to `4xl` up to 992px, tight tracking |
246
- | `mw-section-head-intro` | The lead under it - muted, 65 characters, and from `md` up at most four fifths of the container |
247
- | `mw-section-head-numbered` | Adds the running number at the right end of the rule; the browser counts, restarting per `mw-main` |
248
- | `mw-section-subtitle` | Left-aligned heading with a thin secondary underline, `xl` growing to `2xl` up to 576px - the level inside a section |
249
- | `mw-section-nav` + `mw-section-btn` | Sticky single-row strip of outline-style jump links; parks under the header and scrolls sideways when it overflows |
240
+ | Class | Use |
241
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
242
+ | `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 3.3rem (2.5rem below `md`, 1.75rem below `sm`). Point it at `--mw-section-padding-compact` or `--mw-section-padding-airy`, which step down the same way, or at `--mw-section-padding-fluid`, which ramps with the screen instead |
243
+ | `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
244
+ | `mw-section-head` | The opening of a section: hairline rule, a mark on its left end, title and lead - all on one left edge. Variants `-secondary`, `-success`, `-warning`, `-danger`, `-info` recolour the mark |
245
+ | `mw-section-head-title` | The heading inside it, `2xl` growing to `4xl` up to 992px, tight tracking |
246
+ | `mw-section-head-intro` | The lead under it - muted, 65 characters, and from `md` up at most four fifths of the container |
247
+ | `mw-section-head-numbered` | Adds the running number at the right end of the rule; the browser counts, restarting per `mw-main` |
248
+ | `mw-section-subtitle` | Left-aligned heading with a thin secondary underline, `xl` growing to `2xl` up to 576px - the level inside a section. A paragraph right under it is treated as its lead and gets the gap to the content from the framework - no margin utility needed |
249
+ | `mw-section-nav` + `mw-section-btn` | Sticky single-row strip of outline-style jump links; parks under the header and scrolls sideways when it overflows |
250
250
 
251
251
  ```html
252
252
  <section class="mw-section mw-section-alternate">
@@ -282,6 +282,12 @@ Its tokens: `--mw-section-head-gap` (space to the content below),
282
282
  `--mw-section-head-pad` (rule to title), `--mw-section-head-mark-width` (80px)
283
283
  and `--mw-section-head-mark-color`.
284
284
 
285
+ The first two are multiples of `--mw-section-head-step`, which is derived from
286
+ `--mw-section-padding-block` - so a section spacing preset moves the heads with
287
+ it, and the fluid preset carries them along without a ramp of its own. A head
288
+ with no `mw-section-head-intro` takes a smaller gap: the gap sized to follow a
289
+ paragraph reads as a hole under a rule and one line of type.
290
+
285
291
  The strip sticks at `--mw-header-height` and is `--mw-section-nav-height` tall
286
292
  (3.7rem). Anything it can cover on a jump needs the sum as `scroll-margin-top`;
287
293
  a block wrapping a `mw-section-subtitle` gets that from the framework already.
@@ -335,7 +341,12 @@ width.
335
341
 
336
342
  A `mw-container` that contains a `mw-hero` switches to full-bleed, full-height
337
343
  mode with the background image from `--mw-hero-background`.
338
- `mw-home-content-fade` fades the content in.
344
+ `mw-home-content-fade` fades the content in. `mw-home-start` puts the whole
345
+ block against the left container edge instead of centring it - for a hero that
346
+ points somewhere rather than sitting in the middle. `mw-home` carries the
347
+ container width itself, because the hero container runs the full width of the
348
+ screen; without that the hero content would start at a different edge than
349
+ every section below it.
339
350
 
340
351
  The image is treated per theme through `--mw-hero-image-filter` - a filter, not
341
352
  a tinted overlay: `$hero-filter-dark` (`brightness(0.7)`) against
@@ -489,6 +500,13 @@ row becomes as tall as the tallest instead of as tall as its own contents. For a
489
500
  column of cards that should end on one line even when one of them carries an
490
501
  extra sentence.
491
502
 
503
+ `mw-grid-from-sm|md|lg|xl` is the second modifier: it holds the grid at one
504
+ column until that breakpoint, and the grid's own ramp takes over from there.
505
+ Every ramp turns two-column at `sm`, and 576px is generous for running text
506
+ next to a picture - `mw-grid-2 mw-grid-from-md` is the pair that fixes that.
507
+ It also resets the `mw-reveal-stagger` cycle below the breakpoint, so two cards
508
+ standing above each other do not rise on different beats.
509
+
492
510
  Every one of them has a `-lg` twin with a wider gap (2.5rem instead of 1.35rem):
493
511
  `mw-grid-2-lg`, `mw-grid-3-lg`, `mw-grid-4-lg`, `mw-grid-5-lg`, `mw-grid-1-lg`,
494
512
  `mw-grid-auto-lg`, `mw-grid-flex-lg`, `mw-grid-lg`.
@@ -543,6 +561,16 @@ applies below that. The pair `mw-d-none mw-d-md-flex` is hidden on a phone and a
543
561
  row from a tablet on. `mw-hide-mobile` (gone below md) and `mw-hide-desktop`
544
562
  (gone from md up) spell out the two everyone actually reaches for.
545
563
 
564
+ **Print** - the framework ships a print stylesheet, so a page that is dark on
565
+ screen comes out of the printer as black on white: the surface, ink, border and
566
+ elevation tokens are redeclared for paper, the fixed chrome (header, ribbon,
567
+ section nav, scroll cue, burger, theme toggle, toasts) is dropped, cards keep a
568
+ hairline instead of a tone, headings do not end a page and cards, rows and
569
+ figures are not torn across one. A link in `mw-prose` or `mw-disclaimer` that
570
+ points at an http(s) address prints its target after the text. `mw-hide-print`
571
+ takes an element off the paper, `mw-print-only` is the note that exists only
572
+ there. Nothing to switch on.
573
+
546
574
  **Text overflow** - `mw-text-truncate` is one line ending in an ellipsis and
547
575
  carries `min-width: 0` with it, which is the reason truncation "does not work"
548
576
  nine times out of ten inside a flex row. `mw-text-clamp-2` through `-5` is that
@@ -83,6 +83,8 @@ Safari 16.4+, Firefox 128+. The same range is declared as `browserslist` in
83
83
  | `--mw-container-gutter`, `--mw-container-width` | Page gutter of `mw-container` (fluid `clamp(1rem, 4.2vw + 0.5rem, 4rem)`, never below the safe-area inset) and the width derived from it (`min(1200px, 100% - 2 * gutter)`) |
84
84
  | `--mw-section-padding-block` | Top/bottom rhythm of `mw-section` (3.3rem, stepping down to 2.5rem below `md` and 1.75rem below `sm`) |
85
85
  | `--mw-section-padding-fluid` | The fluid alternative to the value above - 1.75rem on a phone up to 5.5rem at `xl`. Not applied by itself: set `--mw-section-padding-block: var(--mw-section-padding-fluid)` |
86
+ | `--mw-section-padding-compact`, `--mw-section-padding-airy` | The two presets, as tokens so the phone steps reach them - point `--mw-section-padding-block` at one |
87
+ | `--mw-section-head-step` | One step of the head rhythm, derived from `--mw-section-padding-block`. Every measure in a head and around a subtitle is a multiple of it, so a spacing preset moves them all |
86
88
  | `--mw-section-nav-height` | Height the sticky `mw-section-nav` reserves (3.7rem). Add it to `--mw-header-height` for the `scroll-margin-top` of anything the strip can cover |
87
89
  | `--mw-root-font-size` | Percentage the whole rem scale is built on (`100%`). Type, spacing and control heights move with it; px breakpoints do not |
88
90
  | `--mw-calendar-dot` | Colour of a single calendar dot - set it per dot or per cell; the `mw-calendar-dot-*` classes are presets for it |
@@ -233,15 +235,17 @@ URL carries the choice - a link is how a look gets agreed on before it is built.
233
235
  | `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 |
234
236
  | `mw-density-roomy` | The same one step up |
235
237
 
236
- | Property | Effect |
237
- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
238
- | `--mw-radius-scale` | Multiplies the whole radius scale - `0` squares everything off |
239
- | `--mw-root-font-size` | Percentage on `html`. The scale is rem, so type, padding and control heights move together; breakpoints are px and stay put |
240
- | `--mw-font-family-heading` | Headline typeface; body copy is untouched |
241
- | `--mw-container-width` | Where the content stops growing |
242
- | `--mw-section-padding-block` | Air above and below each section |
243
- | `--mw-section-padding-fluid` | The same, but growing with the screen - point the line above at it |
244
- | `--mw-motion-scale` | Multiplies all six duration tokens - `0.6` brisk, `1.6` relaxed. `prefers-reduced-motion` still overrides it |
238
+ | Property | Effect |
239
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
240
+ | `--mw-radius-scale` | Multiplies the whole radius scale - `0` squares everything off |
241
+ | `--mw-root-font-size` | Percentage on `html`. The scale is rem, so type, padding and control heights move together; breakpoints are px and stay put |
242
+ | `--mw-font-family-heading` | Headline typeface; body copy is untouched |
243
+ | `--mw-container-width` | Where the content stops growing |
244
+ | `--mw-section-padding-block` | Air above and below each section |
245
+ | `--mw-section-padding-fluid` | The same, but growing with the screen - point the line above at it |
246
+ | `--mw-section-padding-compact`, `--mw-section-padding-airy` | A tighter and an airier fixed rhythm - point the line above at one |
247
+ | `--mw-section-head-step` | Scales every gap in a section head and around a subtitle at once |
248
+ | `--mw-motion-scale` | Multiplies all six duration tokens - `0.6` brisk, `1.6` relaxed. `prefers-reduced-motion` still overrides it |
245
249
 
246
250
  **Writing your own.** A variant that retunes a _theme-bound_ token - anything in
247
251
  the dark/light maps - cannot be written on `:root` alone: `mw-theme-light`
@@ -396,6 +400,10 @@ Functions read the maps: `spacing('4')`, `font-size('lg')`,
396
400
  container widths, not breakpoint names, because a container has none.
397
401
  - `media-up($bp)` / `media-down($bp)` - the `$breakpoints` map as range syntax,
398
402
  `width >= bp` and `width < bp`, so the two never overlap at the breakpoint.
403
+ `media-up` is the direction the framework is written in: the base block is the
404
+ phone and each breakpoint adds to it. `media-down` is for a rule that sets what
405
+ the base never sets - turned around it would hard-wire an inherited value - and
406
+ for a narrow layout that costs more properties than the wide one.
399
407
  - `touch-context($bp: 'md')` - coarse pointer _or_ narrow viewport. Every
400
408
  target-size rule in the framework hangs under it; put your own control under
401
409
  the same condition instead of guessing it.
@@ -440,7 +448,7 @@ Give it a layer of its own:
440
448
 
441
449
  ## Importing only what you need
442
450
 
443
- The full stylesheet is ~281 kB raw / ~40 kB gzipped. Marketing components
451
+ The full stylesheet is ~290 kB raw / ~41 kB gzipped. Marketing components
444
452
  (`blog-post`, `gallery`, `content-slider`, `techstack-bucket`, `tiles`,
445
453
  `coming-soon`, `ratings`, `home`, `hero`) are dead weight in an application, and
446
454
  Angular bundle budgets notice.
@@ -452,6 +460,22 @@ colourless. The cascade layer order comes along on its own: every module loads
452
460
  `abstracts`, which declares it, so a hand-picked subset orders itself the same
453
461
  way the full build does.
454
462
 
463
+ **The short way is `main-lean`**: the whole framework minus every component -
464
+ tokens, reset, typography, the complete layout and all utilities, ~98 kB raw /
465
+ ~16 kB gzipped. Configure it like `main` and add the components next to it:
466
+
467
+ ```scss
468
+ @use 'maverick-wave/src/scss/main-lean' with (
469
+ $primary-color: #0f766e
470
+ );
471
+ @use 'maverick-wave/src/scss/components/buttons';
472
+ @use 'maverick-wave/src/scss/components/cards';
473
+ @use 'maverick-wave/src/scss/components/alerts';
474
+ ```
475
+
476
+ Those three land at ~19 kB gzipped against ~41 kB for the full build. Pick the
477
+ files by hand, as below, when you also want to leave parts of the layout out.
478
+
455
479
  ```scss
456
480
  // styles.scss - configure first, then pick
457
481
  @use 'maverick-wave/src/scss/abstracts/variables' with (
package/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ 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.26.0] - 2026-09-20
10
+
11
+ ### Changed
12
+
13
+ - Spacing
14
+
15
+ ## [5.25.0] - 2026-09-20
16
+
17
+ ### Added
18
+
19
+ - print stylesheet
20
+ - `main-lean` entry point
21
+ - `mw-home-start` for a hero at the left container edge
22
+ - `mw-grid-from-*` keeps a grid single column up to that breakpoint
23
+
24
+ ### Changed
25
+
26
+ - more "mobile first" design
27
+ - `mw-home` carries the container width
28
+
9
29
  ## [5.24.0] - 2026-09-20
10
30
 
11
31
  ### Changed
package/CLAUDE.md CHANGED
@@ -36,6 +36,10 @@ There are no test or lint scripts.
36
36
  - `utilities/` - Spacing, flex, display utility classes
37
37
  - `main.scss` - Entry point using `@use`/`@forward` (not `@import`)
38
38
 
39
+ **Mobile-first** - The unprefixed block is the phone layout; `media-up($bp)` adds
40
+ to it. `media-down($bp)` only for rules that set what the base never sets, or
41
+ where the narrow layout costs more properties than the wide one.
42
+
39
43
  **JavaScript** (`src/js/main.js`) - Single file. All components auto-initialize on `DOMContentLoaded`. Includes: gallery and image sliders, theme toggle (persisted to localStorage, transitions suppressed during the flip), accordion, mobile nav, progress bar (IntersectionObserver), scroll spy, tabs (which also get their ARIA and arrow-key handling here), alerts/toasts, modals, range inputs, dropdowns, language switcher, checkbox lists, kanban board, calendar.
40
44
 
41
45
  **HTML showcase** (`index.html` + `src/partials/`) - The top-level `index.html` uses `@@include()` syntax to pull in 63 partials from `src/partials/`. These compile into `dist/index.html`.
package/README.md CHANGED
@@ -45,12 +45,12 @@ The result is a framework that balances utility with simplicity, offering develo
45
45
  <title>My MaverickWave Project</title>
46
46
  <link
47
47
  rel="stylesheet"
48
- href="https://cdn.jsdelivr.net/npm/maverick-wave@5.24.0/maverick-wave.min.css"
48
+ href="https://cdn.jsdelivr.net/npm/maverick-wave@5.26.0/maverick-wave.min.css"
49
49
  />
50
50
  </head>
51
51
  <body>
52
52
  <!-- Your content here -->
53
- <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.24.0/maverick-wave.min.js"></script>
53
+ <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.26.0/maverick-wave.min.js"></script>
54
54
  </body>
55
55
  </html>
56
56
  ```
@@ -437,6 +437,8 @@ They exist on the SCSS path only - the compiled CSS carries none of them.
437
437
  ignores the reader's font size. The default range stops at `md`, a headline
438
438
  passes `$to: 'xl'`.
439
439
  - `media-up($bp)` / `media-down($bp)` - the `$breakpoints` map as a query.
440
+ `media-up` is the direction the framework is written in: the base block is the
441
+ phone, each breakpoint adds to it.
440
442
  - `touch-context($bp: 'md')` - coarse pointer _or_ narrow viewport, the condition
441
443
  every target-size rule in the framework hangs under.
442
444
  - `touch-floor($size: 2.75rem)` - that condition plus a `min-height`. Not for a
package/index.html CHANGED
@@ -25,6 +25,19 @@
25
25
  cached: the filename never changes, and without it a stale stylesheet
26
26
  survives a normal reload -->
27
27
  <link rel="stylesheet" href="maverick-wave.min.css?v={{VERSION}}" />
28
+
29
+ <!-- Applies a stored theme choice before the first paint. Without it a
30
+ reader whose choice differs from the OS sees the other theme for a
31
+ frame, because main.js only runs at the end of the body. Inline and
32
+ synchronous on purpose - an external file would paint first. -->
33
+ <script>
34
+ (function () {
35
+ var stored = localStorage.getItem('mw-theme');
36
+ if (stored === 'light' || stored === 'dark') {
37
+ document.documentElement.classList.add('mw-theme-' + stored);
38
+ }
39
+ })();
40
+ </script>
28
41
  <!-- Favicons -->
29
42
  <link rel="icon" type="image/svg+xml" href="favicon/favicon.svg" />
30
43
  <link