maverick-wave 3.10.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 (60) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +62 -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 +416 -27
  6. package/.claude/skills/maverick-wave/references/forms.md +8 -4
  7. package/.claude/skills/maverick-wave/references/javascript.md +32 -18
  8. package/.claude/skills/maverick-wave/references/layout.md +48 -3
  9. package/.claude/skills/maverick-wave/references/theming.md +34 -24
  10. package/CHANGELOG.md +56 -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 +6 -2
  35. package/src/scss/base/_base.scss +21 -0
  36. package/src/scss/components/_accordions.scss +2 -0
  37. package/src/scss/components/_alerts.scss +5 -19
  38. package/src/scss/components/_button-bar.scss +6 -0
  39. package/src/scss/components/_buttons.scss +67 -0
  40. package/src/scss/components/_calendar.scss +290 -0
  41. package/src/scss/components/_content-slider.scss +1 -0
  42. package/src/scss/components/_index.scss +2 -0
  43. package/src/scss/components/_kanban.scss +2 -6
  44. package/src/scss/components/_modals.scss +15 -4
  45. package/src/scss/components/_pagination.scss +68 -64
  46. package/src/scss/components/_segmented.scss +85 -0
  47. package/src/scss/components/_tables.scss +16 -0
  48. package/src/scss/components/_tabs.scss +15 -0
  49. package/src/scss/components/_tags.scss +16 -0
  50. package/src/scss/components/_theme-toggle.scss +2 -0
  51. package/src/scss/components/_timelines.scss +3 -0
  52. package/src/scss/form-elements/_form.scss +13 -7
  53. package/src/scss/layout/_header.scss +3 -0
  54. package/src/scss/layout/_main.scss +5 -1
  55. package/src/scss/layout/_section.scss +1 -1
  56. package/src/scss/utilities/_accessibility.scss +14 -0
  57. package/src/scss/utilities/_flex.scss +17 -0
  58. package/src/scss/utilities/_index.scss +2 -0
  59. package/src/scss/utilities/_text.scss +17 -5
  60. 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
 
@@ -96,8 +161,9 @@ Row of equally treated buttons, centered by default.
96
161
  `-secondary`, `-success`, `-warning`, `-danger`, `-info`.
97
162
  - A card does not clip its content, so a tooltip or dropdown inside it can reach
98
163
  outside. The exception is `mw-card-ribbon`: that banner has to be cut off at
99
- the edge, so a card containing one switches to `overflow: hidden` and clips
100
- everything else too.
164
+ the edge, so a card with one as its **direct child** switches to
165
+ `overflow: hidden` and clips everything else too. A ribbon deeper inside - on
166
+ a card nested in another card - does not make the outer one clip.
101
167
 
102
168
  ```html
103
169
  <div class="mw-card">
@@ -168,11 +234,11 @@ Tile sizes: `mw-tile-sm`, `mw-tile-lg`.
168
234
  ```html
169
235
  <div class="mw-accordion">
170
236
  <div class="mw-accordion-item">
171
- <div class="mw-accordion-header active">
237
+ <div class="mw-accordion-header mw-active">
172
238
  <h3>Question</h3>
173
239
  <i class="fas fa-chevron-down mw-accordion-icon"></i>
174
240
  </div>
175
- <div class="mw-accordion-content active">
241
+ <div class="mw-accordion-content mw-active">
176
242
  <div class="mw-accordion-content-inner">Answer</div>
177
243
  </div>
178
244
  </div>
@@ -188,11 +254,11 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
188
254
  ```html
189
255
  <div class="mw-tabs">
190
256
  <div class="mw-tabs-nav">
191
- <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>
192
258
  <div class="mw-tabs-nav-item" data-tab="tab2">History</div>
193
259
  </div>
194
260
  <div class="mw-tabs-content">
195
- <div class="mw-tabs-panel active" id="tab1">...</div>
261
+ <div class="mw-tabs-panel mw-active" id="tab1">...</div>
196
262
  <div class="mw-tabs-panel" id="tab2">...</div>
197
263
  </div>
198
264
  </div>
@@ -201,8 +267,15 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
201
267
  - Variants: `mw-tabs-vertical` (nav on the left, horizontal again below `sm`),
202
268
  `mw-tabs-pills`.
203
269
  - `data-tab` matches the panel `id` - that pairing is only needed for the
204
- shipped JS. In a SPA, bind `active` yourself and drop the attribute.
205
- - 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.
206
279
 
207
280
  ## Modal
208
281
 
@@ -214,9 +287,10 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
214
287
  <button class="mw-modal-close" type="button">&#120299;</button>
215
288
  </div>
216
289
  <div class="mw-modal-body">
217
- <p>This cannot be undone.</p>
290
+ <p>Invoice 2026-0042 will be removed.</p>
218
291
  </div>
219
292
  <div class="mw-modal-footer">
293
+ <p class="mw-actions-note">This action cannot be undone.</p>
220
294
  <button class="mw-btn mw-btn-outline">Cancel</button>
221
295
  <button class="mw-btn mw-btn-danger">Delete</button>
222
296
  </div>
@@ -229,11 +303,12 @@ via the header state. Content taller than 500px scrolls. Toggling is JS - see
229
303
  class is the whole open/close mechanism.
230
304
  - Body scroll lock is automatic: the stylesheet uses
231
305
  `body:has(.mw-modal-open)`. Nothing to implement.
232
- - Sizes: `mw-modal-sm` 370px, default 550px, `mw-modal-lg` 680px,
233
- `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.
234
308
  - `mw-modal-backdrop` is the click-to-close surface; put the close handler on it.
235
309
  - `mw-modal-body` takes a `mw-form` directly - the form brings the field gaps,
236
310
  the body brings the padding.
311
+ - `mw-actions-note` is the muted line above the buttons - see below.
237
312
 
238
313
  Angular: `<div class="mw-modal-overlay" [class.mw-modal-open]="isOpen()">`.
239
314
 
@@ -354,9 +429,17 @@ Shapes: `mw-skeleton-title`, `-text`, `-circle`, `-rect` (+ `-rect-sm`,
354
429
  | `mw-table-hover` | Stronger row hover |
355
430
  | `mw-table-cards` | Below `md` every row becomes a card; keep `<thead>` (hidden via CSS) and give each cell a `data-label` |
356
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 |
357
433
  | `mw-table-responsive-scroll` | Wrapper with a height cap (`--mw-table-scroll-height`, 400px / 260px below `sm`) and vertical scroll |
358
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` |
359
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
+
360
443
  ```html
361
444
  <div class="mw-table-responsive-scroll" style="--mw-table-scroll-height: 250px">
362
445
  <table class="mw-table mw-table-subtle mw-table-sticky-head">
@@ -365,6 +448,283 @@ Shapes: `mw-skeleton-title`, `-text`, `-circle`, `-rect` (+ `-rect-sm`,
365
448
  </div>
366
449
  ```
367
450
 
451
+ ## Kanban
452
+
453
+ A board is a grid of equally wide lanes; a ticket is a plain `mw-card` with
454
+ `mw-kanban-card` on top. Everything except the counters and the composer is CSS.
455
+
456
+ ```html
457
+ <div class="mw-kanban" style="--mw-kanban-column-min-height: 390px">
458
+ <div class="mw-kanban-column">
459
+ <div class="mw-kanban-column-header">
460
+ <h5 class="mw-kanban-title">In Progress</h5>
461
+ <span class="mw-kanban-count">2</span>
462
+ <button
463
+ type="button"
464
+ class="mw-btn mw-btn-secondary mw-btn-sm mw-kanban-add"
465
+ aria-label="Add ticket to In Progress"
466
+ >
467
+ <i class="fas fa-plus"></i>
468
+ </button>
469
+ </div>
470
+
471
+ <div class="mw-kanban-column-body">
472
+ <article class="mw-card mw-kanban-card">
473
+ <div class="mw-card-ribbon mw-card-addon-danger">High</div>
474
+ <p class="mw-kanban-card-title">Sticky table header jitters</p>
475
+ <p class="mw-kanban-card-text">
476
+ On Safari the header shifts by a pixel.
477
+ </p>
478
+ <div class="mw-kanban-card-footer">
479
+ <span class="mw-kanban-card-id">MW-102</span>
480
+ <div class="mw-kanban-card-actions">
481
+ <div class="mw-avatar mw-avatar-xs mw-avatar-initials">jd</div>
482
+ <button
483
+ type="button"
484
+ class="mw-kanban-action"
485
+ aria-label="Move MW-102 one lane left"
486
+ >
487
+ <i class="fas fa-chevron-left"></i>
488
+ </button>
489
+ <button
490
+ type="button"
491
+ class="mw-kanban-action"
492
+ aria-label="Move MW-102 one lane right"
493
+ >
494
+ <i class="fas fa-chevron-right"></i>
495
+ </button>
496
+ <button
497
+ type="button"
498
+ class="mw-kanban-action"
499
+ aria-label="Edit MW-102"
500
+ >
501
+ <i class="fas fa-pen"></i>
502
+ </button>
503
+ <button
504
+ type="button"
505
+ class="mw-kanban-action mw-kanban-action-danger"
506
+ aria-label="Delete MW-102"
507
+ >
508
+ <i class="fas fa-trash"></i>
509
+ </button>
510
+ </div>
511
+ </div>
512
+ </article>
513
+
514
+ <div class="mw-kanban-empty">No tickets</div>
515
+ </div>
516
+ </div>
517
+ ...
518
+ </div>
519
+ ```
520
+
521
+ | Class | Role |
522
+ | ------------------------- | ---------------------------------------------------------------------------------------- |
523
+ | `mw-kanban` | Board. Grid, one column per lane, same width for all of them |
524
+ | `mw-kanban-plain` | Board without its own surface or padding - for a board that already sits on a panel |
525
+ | `mw-kanban-compact` | Tighter padding, description clamped to 2 lines instead of 4, lane floor 90px |
526
+ | `mw-kanban-column` | Lane: dashed border, flex column |
527
+ | `mw-kanban-column-header` | Title + counter + add button in one row |
528
+ | `mw-kanban-title` | Lane title, uppercase, truncates |
529
+ | `mw-kanban-count` | Ticket counter pill |
530
+ | `mw-kanban-add` | Sits **on** `mw-btn` - only trims it to the header line height |
531
+ | `mw-kanban-column-body` | Ticket stack; fills the lane so the empty state stays centred |
532
+ | `mw-kanban-card` | Ticket. Needs `mw-card` next to it |
533
+ | `mw-kanban-card-title` | Ticket title |
534
+ | `mw-kanban-card-text` | Description, clamped to 4 lines (2 on a compact board) |
535
+ | `mw-kanban-card-footer` | Rule + key on the left, avatar and actions on the right |
536
+ | `mw-kanban-card-id` | Ticket key, monospaced so equal-length keys line up across cards |
537
+ | `mw-kanban-card-actions` | Right-hand group; resets the avatar margin |
538
+ | `mw-kanban-action` | 26px square icon button (32px below `md`), `mw-kanban-action-danger` turns the hover red |
539
+ | `mw-kanban-empty` | Placeholder; hides itself as soon as the lane holds a ticket or an open composer |
540
+ | `mw-kanban-card-in` | One-shot entry animation for a freshly created ticket |
541
+
542
+ - **Priority is the regular `mw-card-ribbon`** and brings its own colour through
543
+ `mw-card-addon-danger|warning|info`. It has to be a **direct child** of the
544
+ card - that is what reserves the space next to the title. No ribbon means no
545
+ priority; there is no separate priority class.
546
+ - Lanes are as tall as the fullest one, with `--mw-kanban-column-min-height` as
547
+ the floor (120px, 90px compact). Raise it per board so a board that starts out
548
+ empty still reads as a board.
549
+ - Board surface and lane border are `--mw-kanban-background` and
550
+ `--mw-kanban-lane-border` - set them on the board, or drop the surface with
551
+ `mw-kanban-plain`.
552
+ - Responsive: below `lg` the lanes reflow into two columns, below `sm` into one.
553
+ - The actions are ordinary buttons - which of them exist is your decision. Give
554
+ every one an `aria-label` naming the ticket; the icon alone has no accessible
555
+ name.
556
+
557
+ ### Composer
558
+
559
+ The inline form for creating and editing a ticket. One per board, moved into the
560
+ lane it is needed in.
561
+
562
+ ```html
563
+ <form class="mw-kanban-composer mw-active">
564
+ <input type="text" class="mw-input mw-input-sm mw-kanban-composer-title" />
565
+ <textarea class="mw-textarea mw-kanban-composer-text" rows="2"></textarea>
566
+ <div class="mw-kanban-composer-row">
567
+ <select class="mw-select mw-select-sm mw-kanban-composer-priority">
568
+ ...
569
+ </select>
570
+ <select class="mw-select mw-select-sm mw-kanban-composer-assignee">
571
+ ...
572
+ </select>
573
+ </div>
574
+ <div class="mw-kanban-composer-actions">
575
+ <button type="button" class="mw-btn mw-btn-outline mw-btn-sm">
576
+ Cancel
577
+ </button>
578
+ <button type="submit" class="mw-btn mw-btn-primary mw-btn-sm">Save</button>
579
+ </div>
580
+ </form>
581
+ ```
582
+
583
+ - **`mw-kanban-composer` is `display: none` until it also carries `mw-active`.**
584
+ Rendering it conditionally is not enough - the class has to be there too, or
585
+ the form stays invisible.
586
+ - `mw-kanban-composer-row` puts priority and assignee side by side and stacks
587
+ them once the lane gets too narrow; `mw-kanban-composer-actions` is the button
588
+ row and gives its buttons the same 80px floor as a modal footer.
589
+ - The `-title`, `-text`, `-priority`, `-assignee` and `-cancel` classes carry no
590
+ styling of their own beyond a min-height on the textarea - they are the hooks
591
+ the shipped JS queries. In a SPA you bind the controls yourself and can drop
592
+ them.
593
+ - `mw-kanban-editing` hides the ticket the composer is currently replacing.
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
+
368
728
  ## Tags
369
729
 
370
730
  Two forms, picked by count - see also the pitfall list in `SKILL.md`.
@@ -376,8 +736,14 @@ Two forms, picked by count - see also the pitfall list in `SKILL.md`.
376
736
  ```
377
737
 
378
738
  Variants: `mw-tag-primary`, `-secondary`, `-success`, `-info`, `-warning`,
379
- `-danger`, `-muted`. Size: `mw-tag-lg`. `mw-tag-muted` is the neutral tone for
380
- 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`.
381
747
 
382
748
  **List** - the container carries the colour for all its items:
383
749
 
@@ -525,7 +891,7 @@ Both are chronicles (CV, changelog), not schedulable time axes.
525
891
 
526
892
  ```html
527
893
  <div class="mw-timeline-big">
528
- <div class="mw-timeline-big-step active">
894
+ <div class="mw-timeline-big-step mw-active">
529
895
  <div class="mw-timeline-big-date-container">
530
896
  <div class="mw-timeline-big-date-main">01/2023 - today</div>
531
897
  <div class="mw-timeline-big-date-sub">(3 years)</div>
@@ -541,7 +907,7 @@ Both are chronicles (CV, changelog), not schedulable time axes.
541
907
  </div>
542
908
 
543
909
  <div class="mw-timeline-simple">
544
- <div class="mw-timeline-simple-step active">
910
+ <div class="mw-timeline-simple-step mw-active">
545
911
  <div class="mw-timeline-simple-date">2024-03-22</div>
546
912
  <div class="mw-timeline-simple-content">
547
913
  <div class="mw-card mw-card-simple">...</div>
@@ -658,23 +1024,46 @@ The last item takes `mw-breadcrumbs-current` on the `li` and
658
1024
 
659
1025
  ## Pagination
660
1026
 
661
- 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.
662
1030
 
663
1031
  ```html
664
1032
  <div class="mw-pagination">
665
1033
  <div class="mw-pagination-header">
666
- <button class="mw-pagination-nav"><i class="fas fa-arrow-left"></i></button>
667
- <h3 class="mw-pagination-title">Week 10</h3>
668
- <button class="mw-pagination-nav">
669
- <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>
670
1050
  </button>
671
1051
  </div>
1052
+
672
1053
  <div class="mw-pagination-content">...</div>
673
1054
  </div>
674
1055
  ```
675
1056
 
676
- `mw-pagination-loading` on the container dims the content while it swaps;
677
- `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.
678
1067
 
679
1068
  ## Divider
680
1069
 
@@ -722,12 +1111,12 @@ shipped JS, as is the track transform. In a SPA, render the dots and set
722
1111
  <img class="mw-image-slider-base-image" src="base.jpg" alt="" />
723
1112
  </div>
724
1113
  <div class="mw-image-slider-overlay">
725
- <div class="mw-image-slider-overlay-image active" data-index="1">
1114
+ <div class="mw-image-slider-overlay-image mw-active" data-index="1">
726
1115
  <img src="a.jpg" alt="" />
727
1116
  </div>
728
1117
  </div>
729
1118
  <div class="mw-image-slider-controls-grid-3">
730
- <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>
731
1120
  </div>
732
1121
  </div>
733
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