maverick-wave 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +112 -12
  2. package/.claude/skills/maverick-wave/references/components.md +152 -4
  3. package/.claude/skills/maverick-wave/references/forms.md +26 -0
  4. package/.claude/skills/maverick-wave/references/javascript.md +34 -27
  5. package/.claude/skills/maverick-wave/references/layout.md +38 -3
  6. package/.claude/skills/maverick-wave/references/theming.md +33 -7
  7. package/CHANGELOG.md +6 -0
  8. package/README.md +79 -4
  9. package/index.html +16 -0
  10. package/maverick-wave.min.css +3 -3
  11. package/maverick-wave.min.js +1 -1
  12. package/package.json +2 -2
  13. package/src/js/main.js +71 -1
  14. package/src/partials/accordions-container.html +5 -0
  15. package/src/partials/alerts-container.html +5 -0
  16. package/src/partials/avatars-container.html +5 -0
  17. package/src/partials/badges-container.html +114 -0
  18. package/src/partials/blog-posts-container.html +5 -0
  19. package/src/partials/breadcrumbs-container.html +1 -1
  20. package/src/partials/button-bar-container.html +5 -0
  21. package/src/partials/buttons-container.html +84 -0
  22. package/src/partials/calendar-container.html +1 -1
  23. package/src/partials/cards-container.html +5 -0
  24. package/src/partials/code-container.html +6 -0
  25. package/src/partials/colors-container.html +1 -1
  26. package/src/partials/coming-soon-container.html +4 -0
  27. package/src/partials/divider-container.html +4 -0
  28. package/src/partials/dropdown-container.html +119 -0
  29. package/src/partials/empty-state-container.html +5 -0
  30. package/src/partials/form-container.html +5 -0
  31. package/src/partials/form-elements-container.html +5 -0
  32. package/src/partials/form-field-container.html +5 -0
  33. package/src/partials/gallery-container.html +5 -0
  34. package/src/partials/get-started-container.html +16 -4
  35. package/src/partials/header-container.html +6 -2
  36. package/src/partials/header-utilities-container.html +8 -2
  37. package/src/partials/html-lists-container.html +5 -0
  38. package/src/partials/info-container.html +4 -0
  39. package/src/partials/input-group-container.html +5 -0
  40. package/src/partials/item-lists-container.html +5 -0
  41. package/src/partials/kanban-container.html +1 -1
  42. package/src/partials/kbd-container.html +63 -0
  43. package/src/partials/meta-info-container.html +5 -0
  44. package/src/partials/pagination-container.html +1 -1
  45. package/src/partials/panels-container.html +4 -0
  46. package/src/partials/progress-container.html +4 -0
  47. package/src/partials/ratings-container.html +1 -1
  48. package/src/partials/skeleton-container.html +5 -0
  49. package/src/partials/spinners-container.html +5 -1
  50. package/src/partials/stepper-container.html +4 -0
  51. package/src/partials/tables-container.html +7 -0
  52. package/src/partials/tabs-container.html +35 -13
  53. package/src/partials/tags-container.html +5 -0
  54. package/src/partials/tiles-container.html +1 -1
  55. package/src/partials/typography-container.html +5 -0
  56. package/src/partials/utilities-container.html +153 -0
  57. package/src/scss/abstracts/_mixins.scss +176 -0
  58. package/src/scss/abstracts/_variables.scss +192 -4
  59. package/src/scss/base/_base.scss +103 -2
  60. package/src/scss/base/_reset.scss +43 -1
  61. package/src/scss/base/_typography.scss +47 -0
  62. package/src/scss/components/_accordions.scss +11 -1
  63. package/src/scss/components/_alerts.scss +3 -3
  64. package/src/scss/components/_avatars.scss +27 -6
  65. package/src/scss/components/_badge.scss +157 -0
  66. package/src/scss/components/_blog-post.scss +10 -7
  67. package/src/scss/components/_breadcrumbs.scss +11 -2
  68. package/src/scss/components/_button-bar.scss +9 -2
  69. package/src/scss/components/_buttons.scss +133 -17
  70. package/src/scss/components/_calendar.scss +13 -2
  71. package/src/scss/components/_cards.scss +12 -5
  72. package/src/scss/components/_code.scss +15 -0
  73. package/src/scss/components/_coming-soon.scss +5 -2
  74. package/src/scss/components/_content-slider.scss +1 -1
  75. package/src/scss/components/_dropdown.scss +219 -0
  76. package/src/scss/components/_gallery.scss +17 -8
  77. package/src/scss/components/_index.scss +3 -0
  78. package/src/scss/components/_info.scss +14 -8
  79. package/src/scss/components/_kanban.scss +40 -6
  80. package/src/scss/components/_kbd.scss +43 -0
  81. package/src/scss/components/_lists.scss +26 -23
  82. package/src/scss/components/_modals.scss +71 -6
  83. package/src/scss/components/_pagination.scss +4 -3
  84. package/src/scss/components/_panels.scss +13 -14
  85. package/src/scss/components/_progress.scss +1 -1
  86. package/src/scss/components/_segmented.scss +8 -4
  87. package/src/scss/components/_stepper.scss +3 -3
  88. package/src/scss/components/_tables.scss +5 -8
  89. package/src/scss/components/_tabs.scss +22 -3
  90. package/src/scss/components/_tags.scss +1 -2
  91. package/src/scss/components/_techstack-bucket.scss +8 -5
  92. package/src/scss/components/_theme-toggle.scss +16 -8
  93. package/src/scss/components/_tiles.scss +14 -4
  94. package/src/scss/components/_timelines.scss +9 -6
  95. package/src/scss/components/_toasts.scss +1 -1
  96. package/src/scss/form-elements/_checkbox.scss +2 -1
  97. package/src/scss/form-elements/_form.scss +2 -2
  98. package/src/scss/form-elements/_input-group.scss +30 -1
  99. package/src/scss/form-elements/_input.scss +10 -10
  100. package/src/scss/form-elements/_login.scss +1 -1
  101. package/src/scss/form-elements/_radio.scss +4 -3
  102. package/src/scss/form-elements/_select.scss +9 -10
  103. package/src/scss/form-elements/_slider.scss +11 -11
  104. package/src/scss/form-elements/_textarea.scss +5 -8
  105. package/src/scss/form-elements/_toggle.scss +6 -5
  106. package/src/scss/layout/_footer.scss +14 -7
  107. package/src/scss/layout/_header.scss +18 -17
  108. package/src/scss/layout/_home.scss +1 -1
  109. package/src/scss/layout/_main.scss +6 -6
  110. package/src/scss/layout/_section.scss +7 -4
  111. package/src/scss/utilities/_accessibility.scss +38 -0
  112. package/src/scss/utilities/_aspect.scss +27 -0
  113. package/src/scss/utilities/_display.scss +52 -0
  114. package/src/scss/utilities/_elevation.scss +16 -0
  115. package/src/scss/utilities/_index.scss +2 -0
  116. package/src/scss/utilities/_text.scss +63 -0
  117. package/src/scss/utilities/_touch-targets.scss +63 -0
@@ -155,12 +155,38 @@ keys at all (negative gap is invalid CSS and is not generated).
155
155
 
156
156
  **Radius** (`mw-radius-none|xs|sm|md|lg|xl|2xl|full`): 0, 2, 5, 10, 15, 20, 30 px, 50%.
157
157
 
158
+ **Elevation** (`mw-elevation-0` … `-5`, and `var(--mw-elevation-N)` inside SCSS).
159
+ Two shadows per level - a tight contact layer plus a wide ambient one:
160
+ `1` resting (inputs, tags), `2` raised (cards, panels at rest), `3` floating
161
+ (a card under the pointer), `4` overlay (dropdown, popover, drawer), `5` modal.
162
+ `0` is explicitly flat. Never write a `box-shadow` by hand - the twelve one-off
163
+ values that used to exist are exactly what this replaced.
164
+
165
+ **Motion** `--mw-duration-instant|fast|base|slow|slower` = 90/150/240/400/700ms,
166
+ `--mw-ease-out` (things arriving - the default), `--mw-ease-in-out` (A to B and
167
+ back), `--mw-ease-spring` (a pop). Two ready-made transitions:
168
+ `var(--mw-transition)` for hover and focus states, `var(--mw-transition-fast)`
169
+ for anything that should feel instant under the pointer. Both list their
170
+ properties explicitly rather than saying `all`.
171
+
172
+ **Control sizes** `--mw-control-height-sm|base|lg` = 2 / 2.25 / 2.5rem (an even
173
+ 32 / 36 / 40px step) and
174
+ `--mw-control-font-sm|base|lg` = 0.8 / 0.9 / 1rem, shared by `mw-input`,
175
+ `mw-select`, `mw-textarea` and `mw-btn`. A field and the button beside it are
176
+ the same height by construction. Buttons run one font step above the fields.
177
+
178
+ **Focus** `--mw-focus-ring-width` 2px, `--mw-focus-ring-offset` 2px,
179
+ `--mw-focus-ring-color`, plus `--mw-focus-halo-size` 3px /
180
+ `--mw-focus-halo-opacity` 28% for the soft ring a form field gets instead of a
181
+ hard outline. SCSS: `@include focus-ring`, `focus-ring-inset`, `field-focus`.
182
+
158
183
  ## Component index
159
184
 
160
185
  Everything below is documented in `references/components.md` unless marked otherwise.
161
186
 
162
187
  **Actions** `mw-btn` (+ `primary`, `secondary`, `danger`, `success`, `outline`,
163
- `link`, `link-muted`, `plain`, `sm`, `lg`) · `mw-btn-mini` · `mw-button-bar`
188
+ `ghost`, `ghost-danger`, `link`, `link-muted`, `plain`, `icon`, `block`, `sm`,
189
+ `lg`) · `mw-btn-mini` · `mw-button-bar`
164
190
  (+ `left`, `right`, `center`, `between`) · `mw-segmented` · `mw-actions-note`
165
191
 
166
192
  **Containers** `mw-card` (+ `simple`, `lg`, `xl`, `stack`, badge, ribbon,
@@ -171,7 +197,8 @@ feature frame) ·
171
197
  **Data & status** `mw-table` (+ `subtle`, `sticky-head`, `cards`, `compact`,
172
198
  `hover`, responsive wrappers) · `mw-kanban` (+ `plain`, `compact`) ·
173
199
  `mw-calendar` (+ `compact`, `plain`) · `mw-tag` /
174
- `mw-tags` · `mw-info` / `mw-info-mini` / `mw-info-counter` · `mw-progress-bar` ·
200
+ `mw-tags` · `mw-badge` (+ `-dot`, `-status`, `-anchor`, `-float`, `-pulse`) ·
201
+ `mw-info` / `mw-info-mini` / `mw-info-counter` · `mw-progress-bar` ·
175
202
  `mw-rating` · `mw-meta-header` · `mw-stepper` · `mw-timeline-big` /
176
203
  `mw-timeline-simple`
177
204
 
@@ -179,11 +206,13 @@ feature frame) ·
179
206
  `mw-spinner-border` / `mw-spinner-dots` / `mw-spinner-dual-ring` · `mw-skeleton`
180
207
 
181
208
  **Navigation** `mw-header` + `mw-navbar` · `mw-breadcrumbs` · `mw-pagination` ·
182
- `mw-section-nav` (`references/layout.md`)
209
+ `mw-dropdown` (+ `-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
210
+ `-caret`, `-end`, `-up`) · `mw-section-nav` (`references/layout.md`)
183
211
 
184
212
  **Media & content** `mw-avatar` (+ `initials`, `group`) · `mw-gallery` ·
185
213
  `mw-image-slider` · `mw-blog-post` · `mw-code-block` / `mw-terminal` ·
186
- `mw-techstack-bucket` · `mw-coming-soon` · `mw-divider` · `mw-list` family
214
+ `mw-techstack-bucket` · `mw-coming-soon` · `mw-divider` · `mw-kbd` ·
215
+ `mw-list` family
187
216
 
188
217
  **Forms** (`references/forms.md`) `mw-field` · `mw-input` · `mw-select` ·
189
218
  `mw-textarea` · `mw-checkbox` · `mw-radio` · `mw-toggle` · `mw-slider` ·
@@ -194,9 +223,14 @@ feature frame) ·
194
223
  `mw-section` · `mw-page-header` · `mw-grid-*` · `mw-row-split` · `mw-hero` ·
195
224
  `mw-footer`
196
225
 
197
- **Utilities** (`references/layout.md`) `mw-sr-only` · `mw-row-split`
198
- (+ `center`) · `mw-text-numeric` / `mw-text-currency` · spacing, flex, display,
199
- text
226
+ **Utilities** (`references/layout.md`) `mw-sr-only` / `mw-sr-only-focusable` /
227
+ `mw-skip-link` · `mw-row-split` (+ `center`) · `mw-text-numeric` /
228
+ `mw-text-currency` · `mw-text-truncate` / `mw-text-clamp-2..5` /
229
+ `mw-text-break` / `mw-text-nowrap` · `mw-text-balance` / `mw-text-pretty` /
230
+ `mw-text-eyebrow` / `mw-text-measure` · `mw-elevation-0..5` ·
231
+ `mw-aspect-square|video|wide|portrait|photo` · `mw-d-{sm,md,lg,xl}-*` /
232
+ `mw-hide-mobile` / `mw-hide-desktop` · `mw-overflow-*` / `mw-snap-x` ·
233
+ spacing, flex, display, text
200
234
 
201
235
  ## Pitfalls
202
236
 
@@ -238,11 +272,13 @@ text
238
272
  Safari 16.4+, Firefox 128+. Both carry the derived tones, so older browsers
239
273
  get no colours at all, not merely worse ones.
240
274
  11. **Touch targets grow on their own.** On `pointer: coarse` or below 768px,
241
- `mw-btn-sm`, `mw-input-sm`, `mw-select-sm` and `mw-textarea-sm` get a 2.5rem
242
- minimum height, a tab 2.75rem, a calendar day 44px. `mw-btn-mini` keeps its
243
- 18px circle - it sits in tag rows and table cells where a bigger one would
244
- shift the layout - and grows its _hit area_ to 28px via a pseudo-element.
245
- Nothing to switch on, and no reason to write the media query again in an app.
275
+ `mw-btn` gets a 2.75rem minimum height, `mw-btn-sm`, `mw-input-sm`,
276
+ `mw-select-sm` and `mw-textarea-sm` 2.5rem, a tab 2.75rem, a calendar day
277
+ 44px, and list rows / menu items / pager pages / accordion headers 2.75rem.
278
+ `mw-btn-mini` keeps its 18px circle - it sits in tag rows and table cells
279
+ where a bigger one would shift the layout - and grows its _hit area_ to 28px
280
+ via a pseudo-element. Nothing to switch on, and no reason to write the media
281
+ query again in an app.
246
282
  12. **`mw-empty-state` has a `-desc`, not a `-text`.** The parts are
247
283
  `mw-empty-state-icon`, `-title`, `-desc`, plus the size variant
248
284
  `mw-empty-state-sm`. Invented names fail silently, as always.
@@ -259,3 +295,67 @@ text
259
295
  that host - it then generates no box and the children take the item role
260
296
  back. Angular: `host: { class: 'mw-d-contents' }`. `mw-header` handles this
261
297
  case on its own, the others do not.
298
+ 15. **A badge is not a tag.** `mw-tag` names something - a topic, a state - and
299
+ sits in a row of its own kind, with a tinted surface. `mw-badge` carries a
300
+ _count_ or a _status_ and usually sits **on** something, filled rather than
301
+ tinted. Number on a bell: badge. "Draft" next to a title: tag.
302
+ 16. **A dropdown is a `<details>`, not a div.** `<details class="mw-dropdown">`
303
+ with a `<summary>` trigger - that is where the keyboard handling, the focus
304
+ and the open state come from, and it works without script. The framework JS
305
+ only adds Escape and click-outside. Writing your own div-plus-click loses all
306
+ of it.
307
+ 17. **An open dropdown is clipped by anything that hides its overflow.** The menu
308
+ is absolutely positioned. The framework's own containers - panel, card, tile,
309
+ modal body, responsive table - lift the clip while a menu is open. On your
310
+ own container it is one line:
311
+ `:has(.mw-dropdown[open]) { overflow: visible }`.
312
+ 18. **Never write `box-shadow` by hand.** Use `var(--mw-elevation-1..5)` or the
313
+ `mw-elevation-*` class. A hand-rolled shadow is the wrong colour in one of
314
+ the two themes - the dark theme's shadow is a light rim over a dark contact
315
+ layer, not a black blur.
316
+ 19. **Never write a duration or an easing curve by hand** either. Use
317
+ `var(--mw-transition)` for a hover or focus state,
318
+ `var(--mw-transition-fast)` for something that should feel instant, and
319
+ `var(--mw-duration-*)` with `var(--mw-ease-*)` for anything else. That is
320
+ also what makes `prefers-reduced-motion` work - it turns the duration tokens
321
+ down, so anything built on them is covered for free.
322
+ 20. **A hover effect that _moves_ something needs the `hover` mixin.** On touch
323
+ `:hover` latches after a tap and stays on, so a lifted card stays lifted,
324
+ visibly out of line with its row. `@include hover { transform: ... }` -
325
+ colour changes are fine unguarded, movement is not. The framework's own
326
+ components already do this.
327
+ 21. **Below 576px a modal is a bottom sheet.** Full width, anchored to the bottom
328
+ edge, rounded on the top two corners, with a grab handle and full-width
329
+ actions in the footer. Nothing to switch on - do not fight it with your own
330
+ media query, and do not put a fixed height on `mw-modal`.
331
+ 22. **Press states exist on every control**, because a finger never hovers.
332
+ `mw-btn` and friends dip 1px and invert their highlight, `mw-btn-mini` and
333
+ `mw-modal-close` scale down. If you build your own control, give it an
334
+ `:active` - on touch it is the only feedback there is.
335
+ 23. **Never set a height on a form control.** `mw-input`, `mw-select`,
336
+ `mw-textarea` and `mw-btn` all take their minimum height from the control
337
+ scale, so a field and the button next to it line up on their own. Pick the
338
+ size step (`-sm` / nothing / `-lg`) and leave the height alone - a hand-set
339
+ one puts that control back out of line with everything around it.
340
+ 24. **Put the size modifier on the input group, not inside it.**
341
+ `mw-input-group-sm` and `-lg` size the prefix, the suffix _and_ the field.
342
+ Adding `mw-input-sm` inside as well is redundant, and mixing the two steps
343
+ is what makes a group look broken.
344
+ 25. **Loops keep their own timing, entrances go on the scale.** A spinner, a
345
+ skeleton shimmer and a pulse ring are ambient - running them at 300ms would
346
+ be frantic, and they are the one place a hand-written duration is right.
347
+ Anything that plays once - a panel appearing, a card sliding in, a drawer -
348
+ uses `var(--mw-duration-*)`.
349
+ 26. **`transition: all` is out.** `var(--mw-transition)` lists paint-only
350
+ properties on purpose. The one exception in the framework is the header
351
+ burger, which morphs by animating `top` and `bottom`, and it says so in a
352
+ comment. If your component really does need to animate a size, name that
353
+ property - do not reach for `all`.
354
+ 27. **A control is a `<button>`, never a styled `<div>`.** `mw-tabs-nav-item`,
355
+ `mw-theme-toggle` and `mw-gallery-dot` are all written for one, and all
356
+ three shipped as divs and spans before 4.11 that no keyboard could reach.
357
+ Each class clears what a `<button>` brings with it, so
358
+ `<button type="button" class="mw-tabs-nav-item" data-tab="...">` is the
359
+ whole markup. If you build your own clickable thing: the element decides
360
+ whether anyone without a mouse can use it, the class only decides how it
361
+ looks.
@@ -15,15 +15,55 @@ exhaustive: what is not named does not exist.
15
15
  ```
16
16
 
17
17
  - Variants: `mw-btn-primary`, `mw-btn-secondary`, `mw-btn-danger`,
18
- `mw-btn-success`, `mw-btn-outline`, `mw-btn-link`, `mw-btn-link-muted`
18
+ `mw-btn-success`, `mw-btn-outline`, `mw-btn-ghost`, `mw-btn-ghost-danger`,
19
+ `mw-btn-link`, `mw-btn-link-muted`
20
+ - Shapes: `mw-btn-icon` (square, for an icon-only button), `mw-btn-block`
21
+ (full width)
19
22
  - Sizes: `mw-btn-sm`, `mw-btn-lg`
20
23
  - `mw-btn` is `inline-flex` with a gap - icons need no wrapper and no extra class
21
24
  - `disabled` gets `opacity: .6` and `not-allowed`; there is no disabled class
22
25
  - `mw-active` gives the pressed/selected look on the coloured variants
26
+ - Every variant dips 1px on `:active`, and the solid ones invert their top
27
+ highlight into an inner shadow. On touch that press is the only feedback there
28
+ is, so do not override it away
23
29
  - `mw-btn-link` still has the button padding - `mw-p-0` makes it read as inline
24
30
  text
25
- - On a coarse pointer or below 768px `mw-btn-sm` grows to a 2.5rem minimum
26
- height on its own
31
+ - On a coarse pointer or below 768px `mw-btn` grows to a 2.75rem minimum height
32
+ and `mw-btn-sm` to 2.5rem, on their own
33
+
34
+ ### Three emphasis levels
35
+
36
+ `mw-btn-primary` (or any solid colour) **fills**, `mw-btn-outline` **draws a
37
+ line**, `mw-btn-ghost` does **neither** until you point at it. Ghost is what a
38
+ toolbar, a card's own actions or a row of icon buttons wants - five outlined
39
+ boxes in a row fight the content they sit on.
40
+
41
+ ```html
42
+ <button type="button" class="mw-btn mw-btn-ghost">Rename</button>
43
+ <button type="button" class="mw-btn mw-btn-ghost mw-btn-ghost-danger">
44
+ Remove
45
+ </button>
46
+ ```
47
+
48
+ Ghost is not `mw-btn-plain`: it keeps the button's shape, padding and hit area
49
+ and only leaves them unpainted at rest. `mw-btn-plain` has no look at all.
50
+
51
+ ### Icon-only buttons
52
+
53
+ ```html
54
+ <button
55
+ type="button"
56
+ class="mw-btn mw-btn-ghost mw-btn-icon"
57
+ aria-label="Duplicate"
58
+ >
59
+ <i class="fas fa-copy"></i>
60
+ </button>
61
+ ```
62
+
63
+ `mw-btn-icon` makes the button square and follows the size modifier next to it
64
+ (`mw-btn-sm` / `mw-btn-lg`), growing to 2.75rem on a coarse pointer. It always
65
+ needs an `aria-label` - there is no text to read out. Combine it with any
66
+ variant; ghost is usually the right one.
27
67
 
28
68
  ### `mw-btn-plain` - a button with no button in it
29
69
 
@@ -75,7 +115,7 @@ Row of equally treated buttons, centered by default.
75
115
  - Alignment: `mw-button-bar-left`, `-right`, `-center` (which is also the
76
116
  default), `mw-button-bar-between` pushes first and last apart.
77
117
  - 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
118
+ - Below 576px it stacks and gives every button the full width. Right for a row of
79
119
  independent actions - wrong for a switch, see below.
80
120
 
81
121
  ### `mw-actions-note` - one line above a row of actions
@@ -809,6 +849,114 @@ Container variants: `mw-tags-primary`, `-secondary`, `-success`, `-info`,
809
849
  `-warning`, `-danger`, `-muted`, size `mw-tags-lg`. `mw-tags-remove` only
810
850
  positions the button - its look comes from `mw-btn-mini` plus a colour variant.
811
851
 
852
+ ## Badges
853
+
854
+ A count or a status, not a label. See the pitfall list in `SKILL.md` for the
855
+ badge-versus-tag rule: a tag names something and sits in a row of its own kind,
856
+ a badge carries a number or a state and usually sits _on_ something.
857
+
858
+ ```html
859
+ <span class="mw-badge mw-badge-danger">99+</span>
860
+ ```
861
+
862
+ Colours: `mw-badge-primary` (the default), `-secondary`, `-success`, `-warning`,
863
+ `-danger`, `-info`, `-muted`, plus `mw-badge-outline`. Sizes: `mw-badge-sm`,
864
+ `mw-badge-lg`.
865
+
866
+ Round on one character, a pill from two on - a `min-width` the padding grows
867
+ past. The digits are `tabular-nums`, so a counter ticking 9 to 10 does not shift
868
+ what sits beside it.
869
+
870
+ **On an icon or a button:**
871
+
872
+ ```html
873
+ <span class="mw-badge-anchor">
874
+ <button class="mw-btn mw-btn-outline">
875
+ <i class="fas fa-inbox"></i> Inbox
876
+ </button>
877
+ <span class="mw-badge mw-badge-danger mw-badge-float">5</span>
878
+ </span>
879
+ ```
880
+
881
+ `mw-badge-anchor` on the thing being badged, `mw-badge-float` on the badge. It
882
+ hangs half off the corner and carries a ring in the surface colour, so it reads
883
+ as a separate object rather than a blob on the icon. The ring follows the
884
+ surface automatically inside a card, panel, modal, tile or the header.
885
+
886
+ **Status:**
887
+
888
+ ```html
889
+ <span class="mw-badge-status mw-badge-status-success">
890
+ <span class="mw-badge-dot"></span> Online
891
+ </span>
892
+ ```
893
+
894
+ `mw-badge-status-{primary|secondary|success|warning|danger|info|muted}` colours
895
+ the dot inside it. A bare `mw-badge-dot-{colour}` works on its own too. Add
896
+ `mw-badge-pulse` for something actually running - a job, an open connection: a
897
+ ring grows out and fades while the dot underneath stays put, so a column of them
898
+ in a table stays readable. Not for decoration.
899
+
900
+ ## Dropdown
901
+
902
+ Built on `<details>` / `<summary>`. That is where the keyboard handling, the
903
+ focus behaviour and the open state a screen reader can see come from, and it
904
+ works with JavaScript switched off. The framework script only adds Escape and
905
+ click-outside.
906
+
907
+ ```html
908
+ <details class="mw-dropdown">
909
+ <summary class="mw-btn mw-btn-primary">
910
+ Actions <i class="fas fa-chevron-down mw-dropdown-caret"></i>
911
+ </summary>
912
+ <div class="mw-dropdown-menu">
913
+ <div class="mw-dropdown-label">Manage</div>
914
+ <button class="mw-dropdown-item"><i class="fas fa-pen"></i> Edit</button>
915
+ <button class="mw-dropdown-item mw-active">Duplicate</button>
916
+ <button class="mw-dropdown-item" disabled>Move</button>
917
+ <hr class="mw-dropdown-divider" />
918
+ <button class="mw-dropdown-item mw-dropdown-item-danger">
919
+ <i class="fas fa-trash"></i> Delete
920
+ </button>
921
+ </div>
922
+ </details>
923
+ ```
924
+
925
+ Parts: `mw-dropdown-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
926
+ `-caret` (rotates with the open state on its own). Alignment:
927
+ `mw-dropdown-end` anchors the menu to the trigger's right edge,
928
+ `mw-dropdown-up` opens it upward.
929
+
930
+ `mw-active` (or `aria-checked="true"`) marks the current choice, for a menu that
931
+ picks rather than acts. Icons inside items keep one column, so labels line up
932
+ whether or not every item has one.
933
+
934
+ The menu is absolutely positioned and is clipped by any ancestor that hides its
935
+ overflow. The framework's own containers lift that clip while a menu is open; on
936
+ your own it is `:has(.mw-dropdown[open]) { overflow: visible }`.
937
+
938
+ On a coarse pointer the rows grow to 2.75rem and the menu takes at least the
939
+ trigger's full width.
940
+
941
+ ## Keyboard keys
942
+
943
+ `<kbd>` is styled directly, so a shortcut in prose needs no class:
944
+
945
+ ```html
946
+ <p>
947
+ Press
948
+ <span class="mw-kbd-group"
949
+ ><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd></span
950
+ >
951
+ to open the palette, <kbd>Esc</kbd> to close.
952
+ </p>
953
+ ```
954
+
955
+ `mw-kbd` is the same look for a `<span>` when the markup is not yours to change.
956
+ `mw-kbd-group` wraps a combination so the pluses are spaced and the whole thing
957
+ cannot wrap apart. `mw-kbd-pressed` shows a key mid-press - the raised bottom
958
+ edge drops and the key sits 1px lower.
959
+
812
960
  ## Info badges & counters
813
961
 
814
962
  ```html
@@ -1,5 +1,31 @@
1
1
  # Forms
2
2
 
3
+ ## Control sizes
4
+
5
+ `mw-input`, `mw-select`, `mw-textarea` and `mw-btn` share one scale:
6
+
7
+ | step | height | font |
8
+ | ----- | ------- | ------ |
9
+ | `-sm` | 2rem | 0.8rem |
10
+ | base | 2.25rem | 0.9rem |
11
+ | `-lg` | 2.5rem | 1rem |
12
+
13
+ A field and the button beside it are therefore the same height by construction -
14
+ before 4.11.0 each control worked its own height out and an input, a select and
15
+ a button in one row measured 32.2, 34.4 and 37.2 pixels. Buttons sit one font
16
+ step above the fields: a button carries a label, a field carries what the user
17
+ typed.
18
+
19
+ Never set a height on a control yourself. Pick the step and leave it alone.
20
+
21
+ The minimum only governs while it is the larger of the two numbers. Block
22
+ padding above it wins, and the control silently leaves the scale - which is
23
+ exactly how a button once ended up 1.2px taller than the field beside it. So if
24
+ you do override `padding-block` on a control, keep
25
+ `2 x padding + line-height + border` under the height its step allows.
26
+ On a coarse pointer or below 768px every control grows to 2.75rem on its own.
27
+ Retune the whole scale through `--mw-control-height*` / `--mw-control-font*`.
28
+
3
29
  ## The field pattern
4
30
 
5
31
  `mw-field` groups label, control, hint and error into one unit. It is the
@@ -25,25 +25,26 @@ classes are the entire contract.
25
25
 
26
26
  ## Behaviour inventory
27
27
 
28
- | Behaviour | What the shipped JS does | What to do instead |
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
- | 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
- | 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 `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
- | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
37
- | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
38
- | Alerts | Close button adds `mw-alert-closed` (`display: none`) | Remove the alert from the list/signal |
39
- | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
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 `mw-active` on the overlay image and the control button with the matching `data-index` | Bind `mw-active` from the selected index |
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 |
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 |
45
- | Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
46
- | Color swatches | Showcase-only (prints computed hex values) | Not needed |
28
+ | Behaviour | What the shipped JS does | What to do instead |
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
+ | 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
+ | 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 `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
+ | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
37
+ | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
38
+ | Alerts | Close button adds `mw-alert-closed` (`display: none`) | Remove the alert from the list/signal |
39
+ | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
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 `mw-active` on the overlay image and the control button with the matching `data-index` | Bind `mw-active` from the selected index |
42
+ | 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 |
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 |
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 |
45
+ | Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
46
+ | Color swatches | Showcase-only (prints computed hex values) | Not needed |
47
+ | Dropdown | Delegated to the document: closes the open `mw-dropdown` on Escape, on a click elsewhere and on a click on a `mw-dropdown-item`, and returns focus to the `summary` | The `<details>` does the opening, the keyboard and the state on its own. Rebuild only the two behaviours markup cannot express - or bind `[attr.open]` and keep them in the component |
47
48
 
48
49
  ## A note on the state class
49
50
 
@@ -56,14 +57,20 @@ other spelling exists.
56
57
 
57
58
  ## What works without any JavaScript
58
59
 
59
- Pure CSS, nothing to wire up: hover and focus states, the card lift, tooltips
60
- (`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table card
61
- view (`data-label`), all grids and utilities, the body scroll lock while a modal
62
- is open (`body:has(.mw-modal-open)`), toast entry animations, the sticky table
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.
60
+ Pure CSS, nothing to wire up: hover, focus and press states, the card lift,
61
+ tooltips (`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table
62
+ card view (`data-label`), all grids and utilities, the body scroll lock while a
63
+ modal is open (`body:has(.mw-modal-open)`), the modal turning into a bottom
64
+ sheet below 576px, toast entry animations, the sticky table header, the scroll
65
+ hint on a tab bar (four gradients, no scroll listener), the kanban empty-lane
66
+ placeholder (hidden via `:has()` as soon as the lane holds a ticket), touch
67
+ target sizing on a coarse pointer, `prefers-reduced-motion` handling.
68
+
69
+ `mw-dropdown` is _almost_ in this list: opening, closing, the keyboard and the
70
+ open state are all the `<details>` element, so it works with no script at all.
71
+ The only two things the shipped JS adds are closing on Escape and closing on a
72
+ click somewhere else - worth rebuilding in a SPA, but a menu without them is
73
+ still a working menu, not a broken one.
67
74
 
68
75
  ## When you do keep the shipped JS
69
76
 
@@ -117,7 +117,7 @@ same height as the login and burger buttons.
117
117
  palette. All of them derive from `--mw-primary-color`, not from a theme
118
118
  colour: the bar is dark in both themes and a light-only project must not have
119
119
  to configure dark-theme values to change it. The bar itself is the primary
120
- darkened toward black, `$header-surface` (16%) - see `theming.md`.
120
+ darkened toward black, `$header-surface` (14%) - see `theming.md`.
121
121
 
122
122
  **Localhost indicator.** Put `mw-localhost-indicator-activated` on the header
123
123
  and the shipped JS prepends a pulsing bar when the host is localhost/127.0.0.1/
@@ -238,8 +238,8 @@ mode with the background image from `--mw-hero-background`.
238
238
  `mw-home-content-fade` fades the content in.
239
239
 
240
240
  The image is treated per theme through `--mw-hero-image-filter` - a filter, not
241
- a tinted overlay: `$hero-filter-dark` (`brightness(0.5)`) against
242
- `$hero-filter-light` (`brightness(1.15) saturate(1.2)`). A translucent layer can
241
+ a tinted overlay: `$hero-filter-dark` (`brightness(0.7)`) against
242
+ `$hero-filter-light` (`brightness(1.1)`). A translucent layer can
243
243
  only darken toward its own colour, so on an already dark photo both themes look
244
244
  the same until the alpha is high enough that the tint colour is what you see.
245
245
  Moving the image's brightness reads as a difference even on a dark photo, and
@@ -314,6 +314,41 @@ automatically.
314
314
  **Display** - `mw-d-flex`, `mw-d-inline-flex`, `mw-d-block`, `mw-d-inline`,
315
315
  `mw-d-inline-block`, `mw-d-grid`, `mw-d-none`, `mw-d-contents`.
316
316
 
317
+ **Responsive display** - `mw-d-{sm|md|lg|xl}-{none|block|flex|inline-flex|grid|inline-block}`.
318
+ Mobile-first and min-width, like everything else here: `mw-d-md-flex` means
319
+ "flex from the md breakpoint up", and the unprefixed class beside it is what
320
+ applies below that. The pair `mw-d-none mw-d-md-flex` is hidden on a phone and a
321
+ row from a tablet on. `mw-hide-mobile` (gone below md) and `mw-hide-desktop`
322
+ (gone from md up) spell out the two everyone actually reaches for.
323
+
324
+ **Text overflow** - `mw-text-truncate` is one line ending in an ellipsis and
325
+ carries `min-width: 0` with it, which is the reason truncation "does not work"
326
+ nine times out of ten inside a flex row. `mw-text-clamp-2` through `-5` is that
327
+ many lines ending in one. `mw-text-break` is for a string with nothing to break
328
+ at - a URL, a hash, an API key - and is what keeps a phone page from scrolling
329
+ sideways. `mw-text-nowrap` is the opposite.
330
+
331
+ **Wrapping and measure** - `mw-text-balance` evens out the lines of a short
332
+ block so a headline never leaves one word alone (headings get it already);
333
+ `mw-text-pretty` only prevents the orphan and is the one for body copy
334
+ (paragraphs get it already). `mw-text-measure` caps a column at 68 characters -
335
+ past roughly 75 the eye loses the start of the next line on the way back.
336
+ `mw-text-eyebrow` is the small spaced upper-case kicker above a heading.
337
+ `mw-text-uppercase` carries the letter-spacing capitals need to stay legible
338
+ with it; `mw-text-capitalize` is the plain transform.
339
+
340
+ **Elevation** - `mw-elevation-0` through `-5`, the same ramp every component
341
+ uses. See the scale table in `SKILL.md`. Never hand-roll a `box-shadow`.
342
+
343
+ **Aspect ratio** - `mw-aspect-square|video|wide|portrait|photo`. Reserves the
344
+ box before the image inside it has loaded, so the page does not reflow when the
345
+ picture arrives. The child fills the box and crops rather than stretching.
346
+
347
+ **Overflow and scrolling** - `mw-overflow-auto`, `mw-overflow-x-auto` (which
348
+ also contains the overscroll, so a sideways swipe on a wide table does not walk
349
+ the whole page), `mw-overflow-hidden`, and `mw-snap-x` to make a horizontal
350
+ strip come to rest on an item instead of halfway between two.
351
+
317
352
  **Numbers** - `mw-text-numeric` is fixed-width digits and nothing else, for a
318
353
  clock, a counter or an ID that must not jitter while it changes.
319
354
  `mw-text-currency` adds right alignment and `nowrap` on top, which is what a