maverick-wave 3.11.0 → 4.0.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 (58) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +59 -30
  2. package/.claude/skills/maverick-wave/examples/angular-form.md +1 -1
  3. package/.claude/skills/maverick-wave/examples/angular-services.md +15 -8
  4. package/.claude/skills/maverick-wave/examples/static-landing-page.md +4 -4
  5. package/.claude/skills/maverick-wave/references/components.md +269 -25
  6. package/.claude/skills/maverick-wave/references/forms.md +8 -4
  7. package/.claude/skills/maverick-wave/references/javascript.md +20 -8
  8. package/.claude/skills/maverick-wave/references/layout.md +48 -3
  9. package/.claude/skills/maverick-wave/references/theming.md +34 -25
  10. package/CHANGELOG.md +46 -0
  11. package/index.html +25 -0
  12. package/maverick-wave.min.css +2 -2
  13. package/maverick-wave.min.js +1 -1
  14. package/package.json +2 -2
  15. package/src/js/main.js +314 -20
  16. package/src/partials/accordions-container.html +2 -2
  17. package/src/partials/button-bar-container.html +4 -2
  18. package/src/partials/buttons-container.html +35 -0
  19. package/src/partials/calendar-container.html +205 -0
  20. package/src/partials/cards-container.html +43 -0
  21. package/src/partials/documentation-container.html +3 -0
  22. package/src/partials/form-container.html +1 -1
  23. package/src/partials/get-started-container.html +2 -2
  24. package/src/partials/kanban-container.html +680 -715
  25. package/src/partials/modals-container.html +5 -5
  26. package/src/partials/pagination-container.html +50 -13
  27. package/src/partials/segmented-container.html +66 -0
  28. package/src/partials/tables-container.html +96 -24
  29. package/src/partials/tabs-container.html +16 -6
  30. package/src/partials/tags-container.html +15 -0
  31. package/src/partials/timelines-container.html +2 -2
  32. package/src/partials/utilities-container.html +81 -0
  33. package/src/scss/abstracts/_mixins.scss +34 -0
  34. package/src/scss/abstracts/_variables.scss +4 -0
  35. package/src/scss/base/_base.scss +21 -0
  36. package/src/scss/components/_accordions.scss +2 -0
  37. package/src/scss/components/_button-bar.scss +6 -0
  38. package/src/scss/components/_buttons.scss +67 -0
  39. package/src/scss/components/_calendar.scss +290 -0
  40. package/src/scss/components/_content-slider.scss +1 -0
  41. package/src/scss/components/_index.scss +2 -0
  42. package/src/scss/components/_modals.scss +15 -4
  43. package/src/scss/components/_pagination.scss +68 -64
  44. package/src/scss/components/_segmented.scss +85 -0
  45. package/src/scss/components/_tables.scss +16 -0
  46. package/src/scss/components/_tabs.scss +15 -0
  47. package/src/scss/components/_tags.scss +16 -0
  48. package/src/scss/components/_theme-toggle.scss +2 -0
  49. package/src/scss/components/_timelines.scss +3 -0
  50. package/src/scss/form-elements/_form.scss +13 -7
  51. package/src/scss/layout/_header.scss +3 -0
  52. package/src/scss/layout/_main.scss +5 -1
  53. package/src/scss/layout/_section.scss +1 -1
  54. package/src/scss/utilities/_accessibility.scss +14 -0
  55. package/src/scss/utilities/_flex.scss +17 -0
  56. package/src/scss/utilities/_index.scss +2 -0
  57. package/src/scss/utilities/_text.scss +17 -5
  58. package/src/scss/utilities/_touch-targets.scss +54 -0
@@ -19,9 +19,27 @@ exhaustive: what is not named does not exist.
19
19
  - Sizes: `mw-btn-sm`, `mw-btn-lg`
20
20
  - `mw-btn` is `inline-flex` with a gap - icons need no wrapper and no extra class
21
21
  - `disabled` gets `opacity: .6` and `not-allowed`; there is no disabled class
22
- - `active` (no prefix) gives the pressed/selected look on the coloured variants
22
+ - `mw-active` gives the pressed/selected look on the coloured variants
23
23
  - `mw-btn-link` still has the button padding - `mw-p-0` makes it read as inline
24
24
  text
25
+ - On a coarse pointer or below 768px `mw-btn-sm` grows to a 2.5rem minimum
26
+ height on its own
27
+
28
+ ### `mw-btn-plain` - a button with no button in it
29
+
30
+ For everywhere a `<button>` is the right element and a button is the wrong look:
31
+ a calendar day, a time in a request row, a settings tab, a modal backdrop.
32
+
33
+ ```html
34
+ <button type="button" class="mw-btn-plain">14:30</button>
35
+ ```
36
+
37
+ It clears `appearance`, background, border, padding and margin, inherits font
38
+ and colour, and keeps exactly two things: the pointer and the focus ring. Used
39
+ on its own - `mw-btn` is neither needed nor wanted next to it.
40
+
41
+ Not the same as `mw-btn-link`, which is a button dressed as a link and brings
42
+ its own colour and minimum width.
25
43
 
26
44
  **Destructive actions use `mw-btn-danger`.** `mw-btn-secondary` is a brand
27
45
  colour, not a semantic one.
@@ -54,9 +72,56 @@ Row of equally treated buttons, centered by default.
54
72
  - Colouring: `mw-button-bar-primary`, `-secondary`, `-outline` style the bare
55
73
  `mw-btn` children, so the buttons carry no variant class themselves.
56
74
  `mw-button-bar-nav` mixes them for prev/center/next navigation.
57
- - Alignment: `mw-button-bar-left`, `-right` (default is centered),
58
- `mw-button-bar-between` pushes first and last apart.
75
+ - Alignment: `mw-button-bar-left`, `-right`, `-center` (which is also the
76
+ default), `mw-button-bar-between` pushes first and last apart.
59
77
  - Sizes: `mw-button-bar-sm`, `mw-button-bar-lg`.
78
+ - Below 576px it stacks and gives every button 96% width. Right for a row of
79
+ independent actions - wrong for a switch, see below.
80
+
81
+ ### `mw-actions-note` - one line above a row of actions
82
+
83
+ ```html
84
+ <div class="mw-modal-footer">
85
+ <p class="mw-actions-note">This action cannot be undone.</p>
86
+ <button class="mw-btn mw-btn-outline">Cancel</button>
87
+ <button class="mw-btn mw-btn-danger">Delete</button>
88
+ </div>
89
+ ```
90
+
91
+ Right-aligned, muted, one size smaller. For what the buttons apply to, why one
92
+ of them is disabled, or that something cannot be taken back.
93
+
94
+ - Works in `mw-modal-footer`, `mw-card-footer`, `mw-form-actions` and
95
+ `mw-panel-footer`. It is a child of the action row, not a wrapper around it:
96
+ the line takes the full width and pushes the buttons onto the row below.
97
+ - Only rows that actually carry a note start wrapping, so adding one changes
98
+ nothing anywhere else.
99
+ - Inside `mw-form-actions` the alignment variants steer it as well -
100
+ `mw-form-actions-left` makes the note left-aligned too.
101
+ - `mw-form-actions-hint` is the old, form-only name for the same thing. Still
102
+ styled, but use `mw-actions-note`: the hint name is wrong the moment the line
103
+ sits in a modal.
104
+
105
+ ### Segmented control
106
+
107
+ Two or three positions of **one** switch: income/expense, offer/quote, a period
108
+ picker.
109
+
110
+ ```html
111
+ <div class="mw-segmented">
112
+ <button type="button" class="mw-segmented-item mw-active">Income</button>
113
+ <button type="button" class="mw-segmented-item">Expense</button>
114
+ </div>
115
+ ```
116
+
117
+ - Stays horizontal at every width and splits the row into equal shares - which
118
+ is the whole point. Stacked in a `mw-button-bar`, two positions of a switch
119
+ look like two buttons you could press both of.
120
+ - Active position: `mw-active` on the item.
121
+ - `mw-segmented-secondary` switches the active fill to the secondary colour,
122
+ `mw-segmented-auto` shrinks the control to its content instead of filling the
123
+ row. `disabled` works on an item.
124
+ - Items reach a 2.5rem minimum height on a coarse pointer.
60
125
 
61
126
  ## Cards
62
127
 
@@ -169,11 +234,11 @@ Tile sizes: `mw-tile-sm`, `mw-tile-lg`.
169
234
  ```html
170
235
  <div class="mw-accordion">
171
236
  <div class="mw-accordion-item">
172
- <div class="mw-accordion-header active">
237
+ <div class="mw-accordion-header mw-active">
173
238
  <h3>Question</h3>
174
239
  <i class="fas fa-chevron-down mw-accordion-icon"></i>
175
240
  </div>
176
- <div class="mw-accordion-content active">
241
+ <div class="mw-accordion-content mw-active">
177
242
  <div class="mw-accordion-content-inner">Answer</div>
178
243
  </div>
179
244
  </div>
@@ -189,11 +254,11 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
189
254
  ```html
190
255
  <div class="mw-tabs">
191
256
  <div class="mw-tabs-nav">
192
- <div class="mw-tabs-nav-item active" data-tab="tab1">Details</div>
257
+ <div class="mw-tabs-nav-item mw-active" data-tab="tab1">Details</div>
193
258
  <div class="mw-tabs-nav-item" data-tab="tab2">History</div>
194
259
  </div>
195
260
  <div class="mw-tabs-content">
196
- <div class="mw-tabs-panel active" id="tab1">...</div>
261
+ <div class="mw-tabs-panel mw-active" id="tab1">...</div>
197
262
  <div class="mw-tabs-panel" id="tab2">...</div>
198
263
  </div>
199
264
  </div>
@@ -202,8 +267,15 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
202
267
  - Variants: `mw-tabs-vertical` (nav on the left, horizontal again below `sm`),
203
268
  `mw-tabs-pills`.
204
269
  - `data-tab` matches the panel `id` - that pairing is only needed for the
205
- shipped JS. In a SPA, bind `active` yourself and drop the attribute.
206
- - The nav scrolls horizontally on small screens instead of wrapping.
270
+ shipped JS. In a SPA, bind `mw-active` yourself and drop the attribute.
271
+ - The nav scrolls horizontally instead of wrapping, and says so: a shadow shows
272
+ on whichever side still has tabs behind it and disappears once that end is
273
+ reached. Pure CSS, no scroll listener. The fade colour comes from
274
+ `--mw-scroll-hint-cover`, which is preset to the page background and
275
+ re-pointed to the card background inside `mw-card`, `mw-panel`, `mw-modal`,
276
+ `mw-tile` and `mw-calendar` - override it if the strip sits on some other
277
+ surface.
278
+ - Tab items reach a 2.75rem minimum height on a coarse pointer.
207
279
 
208
280
  ## Modal
209
281
 
@@ -215,9 +287,10 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
215
287
  <button class="mw-modal-close" type="button">&#120299;</button>
216
288
  </div>
217
289
  <div class="mw-modal-body">
218
- <p>This cannot be undone.</p>
290
+ <p>Invoice 2026-0042 will be removed.</p>
219
291
  </div>
220
292
  <div class="mw-modal-footer">
293
+ <p class="mw-actions-note">This action cannot be undone.</p>
221
294
  <button class="mw-btn mw-btn-outline">Cancel</button>
222
295
  <button class="mw-btn mw-btn-danger">Delete</button>
223
296
  </div>
@@ -230,11 +303,12 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
230
303
  class is the whole open/close mechanism.
231
304
  - Body scroll lock is automatic: the stylesheet uses
232
305
  `body:has(.mw-modal-open)`. Nothing to implement.
233
- - Sizes: `mw-modal-sm` 370px, default 550px, `mw-modal-lg` 680px,
234
- `mw-modal-xl` 900px. Height is capped at 80-92dvh, the body scrolls.
306
+ - Sizes: `mw-modal-sm` 370px, default 520px, `mw-modal-lg` 720px,
307
+ `mw-modal-xl` 960px. Height is capped at 80-92dvh, the body scrolls.
235
308
  - `mw-modal-backdrop` is the click-to-close surface; put the close handler on it.
236
309
  - `mw-modal-body` takes a `mw-form` directly - the form brings the field gaps,
237
310
  the body brings the padding.
311
+ - `mw-actions-note` is the muted line above the buttons - see below.
238
312
 
239
313
  Angular: `<div class="mw-modal-overlay" [class.mw-modal-open]="isOpen()">`.
240
314
 
@@ -355,9 +429,17 @@ Shapes: `mw-skeleton-title`, `-text`, `-circle`, `-rect` (+ `-rect-sm`,
355
429
  | `mw-table-hover` | Stronger row hover |
356
430
  | `mw-table-cards` | Below `md` every row becomes a card; keep `<thead>` (hidden via CSS) and give each cell a `data-label` |
357
431
  | `mw-table-responsive` | Wrapper, horizontal scroll |
432
+ | `mw-table-responsive-hint` | On top of the wrapper: a soft right edge showing there are more columns. Opt-in - see below |
358
433
  | `mw-table-responsive-scroll` | Wrapper with a height cap (`--mw-table-scroll-height`, 400px / 260px below `sm`) and vertical scroll |
359
434
  | `mw-table-sticky-head` | Header stays put while the body scrolls - only works inside a height-limited wrapper, i.e. `mw-table-responsive-scroll` |
360
435
 
436
+ `mw-table-responsive-hint` is opt-in, unlike the automatic hint on a tab bar,
437
+ and works differently for a reason: a table paints its own opaque surface, so
438
+ the gradient trick used on `mw-tabs-nav` would sit _behind_ the rows and never
439
+ show. A mask sits in front and does show - but a mask has no `local` attachment,
440
+ so it cannot fade away once the last column is reached. Put the class on the
441
+ tables you know overflow, leave it off the ones that fit.
442
+
361
443
  ```html
362
444
  <div class="mw-table-responsive-scroll" style="--mw-table-scroll-height: 250px">
363
445
  <table class="mw-table mw-table-subtle mw-table-sticky-head">
@@ -510,6 +592,139 @@ lane it is needed in.
510
592
  them.
511
593
  - `mw-kanban-editing` hides the ticket the composer is currently replacing.
512
594
 
595
+ ## Calendar
596
+
597
+ A card-shaped surface with a header and a seven column grid. A month is six
598
+ rows, a week is one - same classes, same cell size, the only difference is how
599
+ many cells you render. Which page is on screen is application state: the shipped
600
+ JS renders it from `data-calendar`, an Angular app renders the same markup
601
+ itself.
602
+
603
+ ```html
604
+ <div
605
+ class="mw-calendar"
606
+ data-calendar="month"
607
+ data-calendar-markers='{"2026-08-19": ["success", "warning"]}'
608
+ >
609
+ <div class="mw-calendar-header">
610
+ <button
611
+ type="button"
612
+ class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
613
+ data-calendar-nav="-1"
614
+ aria-label="Previous month"
615
+ >
616
+ <i class="fas fa-chevron-left"></i>
617
+ </button>
618
+
619
+ <div class="mw-calendar-title" aria-live="polite">August 2026</div>
620
+
621
+ <button
622
+ type="button"
623
+ class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
624
+ data-calendar-nav="1"
625
+ aria-label="Next month"
626
+ >
627
+ <i class="fas fa-chevron-right"></i>
628
+ </button>
629
+ </div>
630
+
631
+ <div class="mw-calendar-grid">
632
+ <div class="mw-calendar-weekday" aria-hidden="true">Mon</div>
633
+ ...
634
+ <div class="mw-calendar-weekday mw-calendar-weekend" aria-hidden="true">
635
+ Sat
636
+ </div>
637
+
638
+ <button
639
+ type="button"
640
+ class="mw-calendar-day mw-calendar-adjacent"
641
+ aria-label="Monday, 27 July 2026"
642
+ >
643
+ <span class="mw-calendar-date">27</span>
644
+ </button>
645
+
646
+ <button
647
+ type="button"
648
+ class="mw-calendar-day mw-calendar-today"
649
+ aria-current="date"
650
+ aria-pressed="false"
651
+ aria-label="Wednesday, 19 August 2026"
652
+ >
653
+ <span class="mw-calendar-date">19</span>
654
+ <span class="mw-calendar-dots">
655
+ <span class="mw-calendar-dot mw-calendar-dot-success"></span>
656
+ <span class="mw-calendar-dot mw-calendar-dot-warning"></span>
657
+ </span>
658
+ </button>
659
+ ...
660
+ </div>
661
+
662
+ <div class="mw-calendar-legend">
663
+ <span class="mw-calendar-legend-item">
664
+ <span class="mw-calendar-dot mw-calendar-dot-success"></span>
665
+ Slots free
666
+ </span>
667
+ ...
668
+ </div>
669
+ </div>
670
+ ```
671
+
672
+ | Class | Role |
673
+ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
674
+ | `mw-calendar` | Surface. Same border, radius and shadow as a card, without the hover lift |
675
+ | `mw-calendar-plain` | Drops the surface - for a calendar already sitting on a card or panel |
676
+ | `mw-calendar-compact` | Date picker density: dots hidden, 29px cells, tighter header and labels |
677
+ | `mw-calendar-header` | Arrow, title, arrow |
678
+ | `mw-calendar-nav` | Sits **on** `mw-btn` - only squares it off around the chevron |
679
+ | `mw-calendar-title` | Takes the space between the arrows, stays optically centred |
680
+ | `mw-calendar-grid` | The seven column grid; weekday labels and day cells are its only children |
681
+ | `mw-calendar-weekday` | Column label. Decorative - the day buttons carry the weekday themselves |
682
+ | `mw-calendar-day` | One day. A `button`, so `:disabled` gives you an unavailable day |
683
+ | `mw-calendar-date` | The number |
684
+ | `mw-calendar-adjacent` | Day of the neighbouring month - muted, still readable |
685
+ | `mw-calendar-weekend` | Saturday/Sunday. Tinted cell; on the label it turns the text secondary |
686
+ | `mw-calendar-today` | Outlined in the secondary colour, bold |
687
+ | `mw-selected` | Filled with the primary colour |
688
+ | `mw-calendar-dots` | Dot row, absolutely placed at the bottom of the cell |
689
+ | `mw-calendar-dot` | 5px dot. Colour comes from `--mw-calendar-dot`; the `-primary`, `-secondary`, `-success`, `-warning`, `-danger`, `-info` classes are presets for it |
690
+ | `mw-calendar-legend` | Rule plus a row of dot/label pairs below the grid |
691
+ | `mw-calendar-legend-item` | One dot/label pair |
692
+
693
+ - **Today is outlined in secondary, the picked day is filled with primary.** Two
694
+ different colours on purpose: an outline and a fill in the same colour read as
695
+ two states of the same thing. Do not give one day both.
696
+ - The dots are positioned absolutely, so a day without any keeps the exact same
697
+ height and its number sits on the same line as every other. Two or three per
698
+ day stay readable, more do not. A dot on a picked day keeps its status colour;
699
+ only the untoned one flips to the accent text colour.
700
+ - **What a dot means belongs to the caller.** The same calendar reads "request
701
+ pending / booked / time offered" in a scheduling app and something entirely
702
+ different on a public booking page, so the colour is a custom property, not a
703
+ fixed set: `style="--mw-calendar-dot: #7a4fd4"` on the dot, or on the cell to
704
+ colour all of its dots. The six tone classes are named presets for exactly
705
+ that property - use them when they fit, ignore them when they do not.
706
+ - **Render the days spilling in from the neighbouring month.** An empty first
707
+ row reads like a broken calendar, not like a short month. `mw-calendar-adjacent`
708
+ is what pushes them back.
709
+ - A month gets six rows even when five would do, so the calendar - and
710
+ everything under it - keeps its height while you page through.
711
+ - Cell height is fixed (38px, 44px on a coarse pointer, 29px compact), width
712
+ follows the container. No aspect ratio: a full-width calendar would otherwise
713
+ grow rows several hundred pixels tall. A calendar therefore wants a column,
714
+ not the full page width - give it one, or cap it.
715
+ - **A week has no class of its own.** Render seven cells instead of forty-two
716
+ and you have one; everything else is identical.
717
+ - `mw-calendar-compact` hides the dots and shortens the cell - the dots are what
718
+ a full cell needs its height for, so the two go together. It costs about a
719
+ third of the calendar height and leaves a plain date picker; drop the legend
720
+ there, it has nothing left to explain. 29px is below a comfortable tap target,
721
+ so keep it for pointer-first surfaces. Its grid gap drops to 2px as well, which
722
+ puts the space back into the cells.
723
+ - Give every day an `aria-label` with the full date - the bare number is not an
724
+ accessible name. The weekday labels are `aria-hidden`, they would only repeat
725
+ it. With a `button` per day, keep one tab stop for the grid (roving
726
+ `tabindex`) instead of 42.
727
+
513
728
  ## Tags
514
729
 
515
730
  Two forms, picked by count - see also the pitfall list in `SKILL.md`.
@@ -521,8 +736,14 @@ Two forms, picked by count - see also the pitfall list in `SKILL.md`.
521
736
  ```
522
737
 
523
738
  Variants: `mw-tag-primary`, `-secondary`, `-success`, `-info`, `-warning`,
524
- `-danger`, `-muted`. Size: `mw-tag-lg`. `mw-tag-muted` is the neutral tone for
525
- states that should not shout (draft, archived).
739
+ `-danger`, `-muted`, `-neutral`. Size: `mw-tag-lg`.
740
+
741
+ `mw-tag-muted` and `mw-tag-neutral` are not the same and the difference carries
742
+ meaning. **Muted** means the label steps back - draft, archived, a shortcoming
743
+ ("no e-invoice"). **Neutral** is an identifier with no judgement attached and
744
+ keeps the normal text colour - a customer number, a document type, a price on a
745
+ category ("XRechnung" is a property, not a verdict). Both are also available on
746
+ the list container: `mw-tags-muted`, `mw-tags-neutral`.
526
747
 
527
748
  **List** - the container carries the colour for all its items:
528
749
 
@@ -670,7 +891,7 @@ Both are chronicles (CV, changelog), not schedulable time axes.
670
891
 
671
892
  ```html
672
893
  <div class="mw-timeline-big">
673
- <div class="mw-timeline-big-step active">
894
+ <div class="mw-timeline-big-step mw-active">
674
895
  <div class="mw-timeline-big-date-container">
675
896
  <div class="mw-timeline-big-date-main">01/2023 - today</div>
676
897
  <div class="mw-timeline-big-date-sub">(3 years)</div>
@@ -686,7 +907,7 @@ Both are chronicles (CV, changelog), not schedulable time axes.
686
907
  </div>
687
908
 
688
909
  <div class="mw-timeline-simple">
689
- <div class="mw-timeline-simple-step active">
910
+ <div class="mw-timeline-simple-step mw-active">
690
911
  <div class="mw-timeline-simple-date">2024-03-22</div>
691
912
  <div class="mw-timeline-simple-content">
692
913
  <div class="mw-card mw-card-simple">...</div>
@@ -803,23 +1024,46 @@ The last item takes `mw-breadcrumbs-current` on the `li` and
803
1024
 
804
1025
  ## Pagination
805
1026
 
806
- A titled content frame with prev/next controls - not a page-number list.
1027
+ A titled content frame with prev/next controls - not a page-number list. Same
1028
+ surface and header as `mw-calendar`; only the body differs, and the body is
1029
+ yours.
807
1030
 
808
1031
  ```html
809
1032
  <div class="mw-pagination">
810
1033
  <div class="mw-pagination-header">
811
- <button class="mw-pagination-nav"><i class="fas fa-arrow-left"></i></button>
812
- <h3 class="mw-pagination-title">Week 10</h3>
813
- <button class="mw-pagination-nav">
814
- <i class="fas fa-arrow-right"></i>
1034
+ <button
1035
+ type="button"
1036
+ class="mw-btn mw-btn-outline mw-btn-sm mw-pagination-nav"
1037
+ aria-label="Previous week"
1038
+ >
1039
+ <i class="fas fa-chevron-left"></i>
1040
+ </button>
1041
+
1042
+ <div class="mw-pagination-title" aria-live="polite">Week 10</div>
1043
+
1044
+ <button
1045
+ type="button"
1046
+ class="mw-btn mw-btn-outline mw-btn-sm mw-pagination-nav"
1047
+ aria-label="Next week"
1048
+ >
1049
+ <i class="fas fa-chevron-right"></i>
815
1050
  </button>
816
1051
  </div>
1052
+
817
1053
  <div class="mw-pagination-content">...</div>
818
1054
  </div>
819
1055
  ```
820
1056
 
821
- `mw-pagination-loading` on the container dims the content while it swaps;
822
- `mw-pagination-slide-left` / `-slide-right` are the directional transitions.
1057
+ - `mw-pagination-nav` sits **on** `mw-btn` - it only squares the button off
1058
+ around the chevron. The arrows carry no accessible name of their own, so give
1059
+ each one an `aria-label`; `aria-live="polite"` on the title is what tells a
1060
+ screen reader where you landed.
1061
+ - `mw-pagination-loading` goes on `mw-pagination-content` (not on the
1062
+ container) and dims it while the next page is on its way.
1063
+ - `mw-pagination-slide-left` / `-slide-right` are the directional transitions,
1064
+ also on the content.
1065
+ - The body has a 200px floor so a short page does not collapse the frame. Paging
1066
+ is application state - the framework ships no JavaScript for it.
823
1067
 
824
1068
  ## Divider
825
1069
 
@@ -867,12 +1111,12 @@ shipped JS, as is the track transform. In a SPA, render the dots and set
867
1111
  <img class="mw-image-slider-base-image" src="base.jpg" alt="" />
868
1112
  </div>
869
1113
  <div class="mw-image-slider-overlay">
870
- <div class="mw-image-slider-overlay-image active" data-index="1">
1114
+ <div class="mw-image-slider-overlay-image mw-active" data-index="1">
871
1115
  <img src="a.jpg" alt="" />
872
1116
  </div>
873
1117
  </div>
874
1118
  <div class="mw-image-slider-controls-grid-3">
875
- <button class="mw-btn mw-btn-primary active" data-index="0">Base</button>
1119
+ <button class="mw-btn mw-btn-primary mw-active" data-index="0">Base</button>
876
1120
  </div>
877
1121
  </div>
878
1122
  ```
@@ -62,7 +62,7 @@ reactive form control.
62
62
  </div>
63
63
 
64
64
  <div class="mw-form-actions">
65
- <p class="mw-form-actions-hint">Changes are saved immediately.</p>
65
+ <p class="mw-actions-note">Changes are saved immediately.</p>
66
66
  <button type="button" class="mw-btn mw-btn-outline">Cancel</button>
67
67
  <button type="submit" class="mw-btn mw-btn-primary">Save</button>
68
68
  </div>
@@ -77,9 +77,13 @@ reactive form control.
77
77
  `mw-form-group-title` is the heading hook inside it - put it on the `<h3>` /
78
78
  `<h4>`, otherwise the heading keeps its full document-level size.
79
79
  - `mw-form-actions` is a right-aligned wrapping button row.
80
- `mw-form-actions-hint` is a full-width note above the buttons.
81
- Alignment variants: `mw-form-actions-left`, `mw-form-actions-center`,
82
- `mw-form-actions-full-width` (stacked, buttons at 100% - login forms).
80
+ `mw-actions-note` is a full-width note above the buttons and works the same
81
+ way in a modal, card or panel footer (`references/components.md`). The
82
+ alignment variants steer it along: `mw-form-actions-left`,
83
+ `mw-form-actions-center`, `mw-form-actions-full-width` (stacked, buttons at
84
+ 100% - login forms).
85
+ `mw-form-actions-hint` is the old name for the note and still styled, but new
86
+ markup should use `mw-actions-note`.
83
87
  - Put multi-column layouts inside a group with `mw-grid-2` etc. and let a field
84
88
  span everything with `style="grid-column: 1 / -1"`.
85
89
 
@@ -26,32 +26,44 @@ classes are the entire contract.
26
26
  ## Behaviour inventory
27
27
 
28
28
  | Behaviour | What the shipped JS does | What to do instead |
29
- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
- | Accordion | Toggles `active` on `mw-accordion-header` and the following `mw-accordion-content` | `[class.active]="isOpen()"` on both elements |
31
- | Tabs | `data-tab` → panel `id`; sets `active` on nav item and panel | Track the selected index/key, bind `active` on both; drop `data-tab` |
29
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
30
+ | Accordion | Toggles `mw-active` on `mw-accordion-header` and the following `mw-accordion-content` | `[class.mw-active]="isOpen()"` on both elements |
31
+ | Tabs | `data-tab` → panel `id`; sets `mw-active` on nav item and panel | Track the selected index/key, bind `mw-active` on both; drop `data-tab` |
32
32
  | Modal | Click on `mw-modal-close` removes `mw-modal-open` from the overlay | `[class.mw-modal-open]="isOpen()"`; backdrop click closes. Opening is not in the script at all (the showcase has its own `openModal`) |
33
33
  | Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, closes on anchor click | One signal, bound to both; reset it on navigation end |
34
- | Scroll spy | Sets `active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="active"` |
35
- | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `active` on the toggle | A theme service - see `examples/angular-services.md` |
34
+ | Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
35
+ | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `mw-active` on the toggle | A theme service - see `examples/angular-services.md` |
36
36
  | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
37
37
  | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
38
38
  | Alerts | Close button adds `mw-alert-closed` (`display: none`) | Remove the alert from the list/signal |
39
39
  | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
40
40
  | Gallery | Generates the dots, moves the track, swipe handling, writes `mw-gallery-desc` | Render dots in the template, bind the track transform and `mw-active` on the current dot |
41
- | Image slider | Toggles `active` on the overlay image and the control button with the matching `data-index` | Bind `active` from the selected index |
41
+ | 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 |
42
42
  | Kanban board | Counts the tickets per lane, moves a card between lanes (`data-kanban-move`), 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 |
43
+ | Calendar | Renders the month or week grid from `data-calendar="month | week"`, pages with `data-calendar-nav`, draws the status dots from `data-calendar-markers`, toggles `mw-selected`and emits`mw-calendar-select` | Render the cells from a signal and bind `mw-calendar-adjacent`, `mw-calendar-weekend`, `mw-calendar-today` and `mw-selected` yourself; none of the `data-calendar-*` attributes are needed |
43
44
  | Localhost indicator | On a local hostname, prepends `mw-localhost-indicator-pulse` to the header when it carries `mw-localhost-indicator-activated` | Render the element conditionally |
44
45
  | Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
45
46
  | Color swatches | Showcase-only (prints computed hex values) | Not needed |
46
47
 
48
+ ## A note on the state class
49
+
50
+ Since 4.0.0 the script writes `mw-active` and clears both `mw-active` and the
51
+ deprecated bare `active` when it switches a state off. That is deliberate: HTML
52
+ written against an older version marks the first tab with `active`, and if
53
+ switching away only removed `mw-active`, that first tab would stay lit next to
54
+ the newly chosen one. In your own components bind `mw-active` and forget the
55
+ other spelling exists.
56
+
47
57
  ## What works without any JavaScript
48
58
 
49
59
  Pure CSS, nothing to wire up: hover and focus states, the card lift, tooltips
50
60
  (`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table card
51
61
  view (`data-label`), all grids and utilities, the body scroll lock while a modal
52
62
  is open (`body:has(.mw-modal-open)`), toast entry animations, the sticky table
53
- header, the kanban empty-lane placeholder (hidden via `:has()` as soon as the
54
- lane holds a ticket), `prefers-reduced-motion` handling.
63
+ header, the scroll hint on a tab bar (four gradients, no scroll listener), the
64
+ kanban empty-lane placeholder (hidden via `:has()` as soon as the lane holds a
65
+ ticket), touch target sizing on a coarse pointer, `prefers-reduced-motion`
66
+ handling.
55
67
 
56
68
  ## When you do keep the shipped JS
57
69
 
@@ -26,7 +26,11 @@
26
26
  - `mw-main` is a full-height flex column - the footer stays at the bottom on
27
27
  short pages.
28
28
  - `mw-container` is `min(1200px, 89%)`, horizontally centered. Nest it inside
29
- every full-bleed band (header, section, footer), never around them.
29
+ every full-bleed band (header, section, footer), never around them. Below
30
+ 576px it switches to a fixed 1rem gutter instead of the percentage - 89% of a
31
+ 375px screen leaves 41px of nothing on a card that has to fit an amount, a
32
+ status and two labels into one row. Both widths are tokens,
33
+ `--mw-container-width` and `--mw-container-width-sm`.
30
34
  - `mw-content` (`flex: 1` + top padding) is the alternative to `mw-section` when
31
35
  a page has one single content area. `mw-content-centered` centers it
32
36
  vertically over the full viewport - login pages, error pages.
@@ -59,7 +63,7 @@ its children are styled through descendant selectors:
59
63
  <nav class="mw-navbar mw-navbar-medium">
60
64
  <ul class="mw-navbar-list">
61
65
  <li class="mw-navbar-item">
62
- <a href="#start" class="mw-navbar-link active">Start</a>
66
+ <a href="#start" class="mw-navbar-link mw-active">Start</a>
63
67
  </li>
64
68
  <li class="mw-navbar-item">
65
69
  <a href="#docs" class="mw-navbar-link">Docs</a>
@@ -153,7 +157,7 @@ viewport, so keep long tooltips off the outermost elements.
153
157
 
154
158
  | Class | Use |
155
159
  | ----------------------------------- | -------------------------------------------------------------------------- |
156
- | `mw-section` | Vertical rhythm (1.75rem top/bottom) for a page band |
160
+ | `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 1.75rem |
157
161
  | `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
158
162
  | `mw-section-title` | Centered `3xl` heading with a decorative primary underline - landing pages |
159
163
  | `mw-section-subtitle` | Centered `2xl` heading with a thin secondary underline |
@@ -277,6 +281,47 @@ automatically.
277
281
  **Display** - `mw-d-flex`, `mw-d-inline-flex`, `mw-d-block`, `mw-d-inline`,
278
282
  `mw-d-inline-block`, `mw-d-grid`, `mw-d-none`, `mw-d-contents`.
279
283
 
284
+ **Numbers** - `mw-text-numeric` is fixed-width digits and nothing else, for a
285
+ clock, a counter or an ID that must not jitter while it changes.
286
+ `mw-text-currency` adds right alignment and `nowrap` on top, which is what a
287
+ money column in a table wants. Do not reach for `mw-text-currency` just to get
288
+ the digits - that was the old behaviour of `mw-text-numeric` and the reason
289
+ people wrote `font-variant-numeric` out by hand.
290
+
291
+ ### `mw-row-split` - the row with two ends
292
+
293
+ The most common layout in any application: what it is on the left, the value or
294
+ the action on the right, wrapping to two lines when it runs out of room. Card
295
+ header, section header, key figure, week row.
296
+
297
+ ```html
298
+ <div class="mw-row-split">
299
+ <strong>Invoice 2026-0042</strong>
300
+ <span class="mw-text-currency">1,204.50</span>
301
+ </div>
302
+ ```
303
+
304
+ Five declarations you would otherwise write again in every component -
305
+ including the `flex-wrap` everyone forgets, which is what keeps the left half
306
+ from being squashed on a phone.
307
+
308
+ `mw-row-split` aligns on the baseline, which is right for text against text. Add
309
+ `mw-row-split-center` when the two sides differ in height - a heading beside a
310
+ button, a label beside an icon.
311
+
312
+ ### `mw-sr-only` - text for screen readers only
313
+
314
+ ```html
315
+ <button type="button" class="mw-btn mw-btn-outline">
316
+ <i class="fas fa-trash"></i>
317
+ <span class="mw-sr-only">Delete invoice 2026-0042</span>
318
+ </button>
319
+ ```
320
+
321
+ Wherever an icon carries the whole message. Not `display: none` and not
322
+ `visibility: hidden` - both drop the element out of the accessibility tree,
323
+ which is precisely what must not happen.
324
+
280
325
  There are no responsive display variants. Show/hide per breakpoint is the
281
326
  application's job (media query in your own stylesheet, or `@if` in the
282
327
  template).