mote.css 0.5.1 → 0.6.1

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.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,109 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.5.1] - 2026-10-20
8
+ ## [0.6.1] - 2026-10-20
9
+
10
+ ### Fixed
11
+
12
+ - `.mote-print-only` now actually displays in print (the unconditional
13
+ `display: none` was after the print block at equal specificity —
14
+ scoped to `@media not print` and moved before it).
15
+ - `.mote-stack` no longer stretches `.mote-sr-only` inputs to 100%
16
+ width (hidden file inputs were creating invisible horizontal scroll).
17
+ - `.mote-doc > main` now uses `overflow-x: auto` (was `clip`, which
18
+ made wide tables invisible rather than scrollable).
19
+ - `.mote-theme-switch` shipped in mote.css: disables transitions during
20
+ `data-theme` flips so Chromium doesn't wedge elements at stale colors
21
+ (custom-property changes don't restart CSS transitions). The docs'
22
+ toggle snippet now includes it; the demo uses it.
23
+ - `.mote-doc > aside` gets the same sticky treatment as the nav rail
24
+ (top, max-height, overflow) — TOCs and aside content no longer scroll
25
+ out of view.
26
+ - Nav rail `max-height` accounts for `--mote-rail-top` (was
27
+ `calc(100svh - 2rem)` regardless of header height).
28
+ - Rail heading treatment (small uppercase labels) now applies to
29
+ `.mote-doc > aside` and `nav[aria-label="toc"]` headings too.
30
+ - `legend` gets `font-weight: 600` so the group caption reads stronger
31
+ than the field labels inside it.
32
+
33
+ ### Added
34
+
35
+ - `tabular-nums` on all `td` — figures align in data tables without
36
+ any page CSS (invisible on non-numeric content).
37
+ - `.mote-table-scroll`: scrollable table wrapper with sticky `thead`,
38
+ no margin jump on scroll start, and no page-level horizontal scroll.
39
+ - `.mote-grid-sparse`: auto-fit variant that collapses empty grid
40
+ tracks (footers, short card rows).
41
+ - `--mote-control-pad` / `--mote-control-pad-inline` and
42
+ `--mote-cell-pad` / `--mote-cell-pad-inline` density tokens.
43
+ - Docs: toggle snippet includes the theme-switch workaround; Chromium
44
+ note references the shipped class.
45
+
46
+ ### Changed
47
+
48
+ - Third ten-app evaluation completed against v0.6.0; all round-2 fixes
49
+ verified holding. Round-3 findings addressed in this patch release.
50
+
51
+ ### Fixed
52
+
53
+ - Pagination `<span>` current page: the pill box (display, min-width,
54
+ padding) now targets `[aria-current="page"]` as well as `<a>` — the
55
+ docs-recommended span form renders correctly (was a 6.5px sliver).
56
+ - Pagination: `flex-wrap: wrap` on the link row — 10 pills at 390px
57
+ no longer scroll the page horizontally.
58
+ - Anchor ghosts: `.mote-ghost` now carries its own padding, border-radius,
59
+ text-decoration:none and cursor:pointer — `<a class="mote-ghost">`
60
+ renders as a proper pill (was an underlined unpadded rectangle).
61
+ - Badge links: `a.mote-badge` no longer underlines; `user-select: none`
62
+ dropped from badges so version numbers and tags can be copied.
63
+ - `.mote-display` reads `--mote-status` — themed pages (error, warning)
64
+ tint their giant display text automatically.
65
+ - Default `--mote-rail-top` is now 4.75rem when `body > header` exists
66
+ (the sticky rail clears the sticky header without manual overrides).
67
+ - `.mote-doc > main` gets `overflow-x: clip` — wide tables truly scroll
68
+ inside main, never the page.
69
+ - Sticky header: default `padding-block: 0.7rem` — content no longer hugs
70
+ the border.
71
+ - `.mote-row` zeroes heading margins inside the row — version headers
72
+ and card titles no longer balloon the flex context.
73
+ - Checkbox/radio `width: 1em` hardened against page-level
74
+ `width: 100%` rules.
75
+
76
+ ### Added
77
+
78
+ - `.mote-btn`: solid button recipe on any element (anchors, spans) —
79
+ the full solid chrome including gated status classes; hero CTAs and
80
+ pricing links no longer need page CSS.
81
+ - `.mote-media`: media object (avatar/icon beside body) — flex row,
82
+ last child grows; forum posts, comments, chat.
83
+ - `nav[aria-label="toc"]`: semantic table-of-contents pattern (mirrors
84
+ breadcrumbs and pagination) — right-rail asides, "On this page" nav.
85
+ - `.mote-no-print` / `.mote-print-only`: opt-in print visibility helpers.
86
+ - `aria-invalid="true"` styled alongside `:user-invalid` — programmatic
87
+ validation states (server-side, failed-save) without waiting for user
88
+ interaction.
89
+ - Rail headings: `.mote-doc > nav :is(h2, h3)` at label scale —
90
+ grouped sidebar navigation without page CSS.
91
+ - `.mote-card` reads `--mote-status` for its border — featured/emphasized
92
+ cards from one status class.
93
+ - Docs recipes: card flush-media, numeric table columns with sticky
94
+ headers, sparse grids (auto-fit), canonical search form.
95
+ - `.mote-stack` opt-in class: stretches form fields to 100% while
96
+ keeping checkboxes/radios at native 1em — no page CSS needed.
97
+ - `--mote-container-max` token (default 70rem): narrow the reading
98
+ measure for long-form pages without new classes.
99
+ - Spinner sized in em (1.25em, 0.15em border) — scales with context
100
+ (buttons, table rows, loading text).
101
+ - Docs: display text section with error-code example and status tinting;
102
+ `--mote-avatar` sizing variable documented; `--mote-grid-min` density
103
+ variable documented; `[role="alert"]`/`[role="status"]` semantic
104
+ feedback helpers documented; safe stacked-form recipe and `.mote-stack`;
105
+ Chromium theme-toggle transition workaround.
106
+
107
+ ### Changed
108
+
109
+ - Second ten-app evaluation completed against v0.5.1; all round-1 fixes
110
+ verified holding. Round-2 findings addressed in this release.
9
111
 
10
112
  ### Fixed
11
113
 
@@ -274,6 +376,8 @@ Components:
274
376
  - Element gallery demo page (`demo.html`).
275
377
 
276
378
  [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
379
+ [0.6.1]: https://gitlab.com/abmach/mote/compare/v0.6.0...v0.6.1
380
+ [0.6.0]: https://gitlab.com/abmach/mote/compare/v0.5.1...v0.6.0
277
381
  [0.5.1]: https://gitlab.com/abmach/mote/compare/v0.5.0...v0.5.1
278
382
  [0.5.0]: https://gitlab.com/abmach/mote/compare/v0.4.0...v0.5.0
279
383
  [0.4.0]: https://gitlab.com/abmach/mote/compare/v0.3.0...v0.4.0
package/README.md CHANGED
@@ -96,6 +96,12 @@ The consequence: theming is a handful of variable overrides, and an inline tweak
96
96
  | `--mote-radius` | `10px` | | Default corner softness |
97
97
  | `--mote-radius-sm` | `6px` | | Controls, inline code |
98
98
  | `--mote-radius-lg` | `16px` | | Dialogs |
99
+ | `--mote-container-max` | `70rem` | | Page wrapper max width (narrow for reading) |
100
+ | `--mote-display` | `clamp(2.75rem, 1.5rem + 4vw, 5rem)` | | Display text scale (opt-in) |
101
+ | `--mote-grid-min` | `16rem` | | Card grid minimum track width |
102
+ | `--mote-avatar` | `2.5rem` | | Avatar size |
103
+ | `--mote-control-pad` | `0.5rem` | | Button/control vertical padding |
104
+ | `--mote-cell-pad` | `0.6rem` | | Table cell vertical padding |
99
105
  | `--mote-shadow` / `--mote-shadow-sm` | warm diffuse | | Elevation |
100
106
  | `--mote-transition` | `150ms ease` | | Hover/focus timing |
101
107
 
@@ -144,11 +150,19 @@ mote is class-optional by design — elements are styled from tags alone; these
144
150
  | Sticky site header / nav pills | `body > header` — semantic; `.mote-header`/`.mote-nav` only for shells wrapped in a `<div>` |
145
151
  | Semantic buttons (gated) | `class="mote-success"` etc. on the button itself — themed sections never recolor actions |
146
152
  | Ghost buttons | `.mote-ghost` — transparent fill; status tints from the button's own class (gated like solids) |
153
+ | Solid button on any element | `.mote-btn` — the full solid recipe for anchors, spans; status classes gated |
154
+ | Media objects | `.mote-media` — avatar/icon beside body (flex, last child grows) |
147
155
  | Display text | `.mote-display` — opt-in hero/error-code scale via `--mote-display` |
148
156
  | Content rows | `.mote-row` + `.mote-push` — flex row, right-align helper |
157
+ | Stacked forms | `.mote-stack` — opt-in full-width fields, checkboxes stay 1em |
158
+ | Scrollable tables | `.mote-table-scroll` — sticky `thead`, no page scroll, no margin jump |
159
+ | Sparse grids | `.mote-grid-sparse` — auto-fit variant, collapses empty tracks |
149
160
  | Pagination | `nav[aria-label="pagination"]` — semantic, no class |
161
+ | Table of contents | `nav[aria-label="toc"]` — semantic, no class |
150
162
  | Footer baseline | `body > footer` — semantic top-border + padding |
151
163
  | Screen-reader helper | `.mote-sr-only` (doubles as a skip link when focused) |
164
+ | Print helpers | `.mote-no-print` / `.mote-print-only` |
165
+ | Theme-switch guard | `.mote-theme-switch` — prevents Chromium stale-color bug on `data-theme` flips |
152
166
  | Status inheritance control | `.mote-default` (force accent) · `.mote-reset` (clear inherited status) |
153
167
  | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
154
168
  | Card grids | `.mote-grid` — auto-fill columns from the container, no breakpoints |
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.5.1 · MIT
3
+ * v0.6.1 · MIT
4
4
  * https://gitlab.com/abmach/mote · mirror: https://github.com/abmach/mote
5
5
  *
6
6
  * Requires mote.css loaded first (provides the --mote-* tokens).
@@ -19,6 +19,7 @@
19
19
  flex-direction: column;
20
20
  padding: 1.25rem 1.5rem;
21
21
  border: var(--mote-border-width) solid var(--mote-border);
22
+ border-color: color-mix(in oklab, var(--mote-status, var(--mote-border)), var(--mote-border) 60%);
22
23
  border-radius: var(--mote-radius-lg);
23
24
  background: color-mix(in oklab, var(--mote-surface) 55%, var(--mote-bg));
24
25
  box-shadow: var(--mote-shadow-sm);
@@ -48,7 +49,11 @@
48
49
  line-height: 1.4;
49
50
  background: var(--mote-status-soft, var(--mote-accent-soft));
50
51
  color: var(--mote-status, var(--mote-accent-deep));
51
- user-select: none;
52
+ }
53
+
54
+ /* Badges as links (tag chips, version pills): kill the underline */
55
+ a:where(.mote-badge) {
56
+ text-decoration: none;
52
57
  }
53
58
 
54
59
  /* ---------------------------------------------------------------------------
@@ -95,9 +100,10 @@
95
100
  .mote-spinner {
96
101
  display: inline-block;
97
102
  vertical-align: -0.25em;
98
- width: 1.25rem;
99
- height: 1.25rem;
100
- border: 2px solid color-mix(in oklab, var(--mote-status, var(--mote-accent)), transparent 65%);
103
+ /* em sizing: scales with context (buttons, table rows, loading text) */
104
+ width: 1.25em;
105
+ height: 1.25em;
106
+ border: 0.15em solid color-mix(in oklab, var(--mote-status, var(--mote-accent)), transparent 65%);
101
107
  border-block-start-color: var(--mote-status, var(--mote-accent));
102
108
  border-radius: 999px;
103
109
  animation: mote-spin 0.7s linear infinite;
@@ -124,6 +130,7 @@
124
130
  background: color-mix(in oklab, var(--mote-bg) 82%, transparent);
125
131
  backdrop-filter: blur(10px);
126
132
  border-block-end: var(--mote-border-width) solid var(--mote-border);
133
+ padding-block: 0.7rem;
127
134
  }
128
135
 
129
136
  :where(body > header nav, .mote-nav) ul {
@@ -183,13 +190,14 @@ nav[aria-label="breadcrumb"] [aria-current="page"] {
183
190
  /* Pagination — semantic selector, no class (mirrors breadcrumbs) */
184
191
  nav[aria-label="pagination"] ul {
185
192
  display: flex;
193
+ flex-wrap: wrap;
186
194
  gap: 0.25rem;
187
195
  padding: 0;
188
196
  margin: 0;
189
197
  list-style: none;
190
198
  }
191
199
 
192
- nav[aria-label="pagination"] a {
200
+ nav[aria-label="pagination"] :is(a, [aria-current="page"]) {
193
201
  display: inline-block;
194
202
  min-width: 2.2rem;
195
203
  padding: 0.35rem 0.6rem;
@@ -215,6 +223,45 @@ nav[aria-label="pagination"] [aria-current="page"]:hover {
215
223
  color: var(--mote-on-accent);
216
224
  }
217
225
 
226
+ /* Table of contents — semantic selector (mirrors breadcrumbs/pagination) */
227
+ nav[aria-label="toc"] ul {
228
+ padding-inline-start: 0.8rem;
229
+ margin: 0;
230
+ list-style: none;
231
+ font-size: 0.9em;
232
+ }
233
+
234
+ nav[aria-label="toc"] li {
235
+ margin-block: 0.2rem;
236
+ }
237
+
238
+ nav[aria-label="toc"] a {
239
+ color: var(--mote-muted);
240
+ text-decoration: none;
241
+ transition: color var(--mote-transition);
242
+ }
243
+
244
+ nav[aria-label="toc"] a:hover {
245
+ color: var(--mote-accent-deep);
246
+ }
247
+
248
+ /* Scrollable table wrapper: sticky thead, no page scroll, no margin jump */
249
+ .mote-table-scroll {
250
+ overflow-x: auto;
251
+ max-height: 60vh;
252
+ overflow-y: auto;
253
+ }
254
+
255
+ .mote-table-scroll table {
256
+ margin-block: 0;
257
+ }
258
+
259
+ .mote-table-scroll thead th {
260
+ position: sticky;
261
+ top: 0;
262
+ background: var(--mote-bg);
263
+ }
264
+
218
265
  /* Page footer: the quiet sibling of the site header */
219
266
  :where(body > footer, .mote-footer) {
220
267
  margin-block-start: 3rem;
@@ -461,6 +508,7 @@ details[name] > :not(summary):last-child {
461
508
  when the header actually exists. */
462
509
  html:where(:has(body > header)) {
463
510
  scroll-padding-block-start: 5rem;
511
+ --mote-rail-top: 4.75rem;
464
512
  }
465
513
 
466
514
  .mote-doc > header { grid-row: 1; grid-column: 1 / -1; }
@@ -468,6 +516,12 @@ html:where(:has(body > header)) {
468
516
 
469
517
  .mote-doc > main { grid-row: 2; grid-column: 1; min-width: 0; }
470
518
 
519
+ /* Content stays inside the grid track — wide tables scroll here,
520
+ not the page. */
521
+ .mote-doc > main {
522
+ overflow-x: auto;
523
+ }
524
+
471
525
  /* nav = left rail */
472
526
  .mote-doc > nav {
473
527
  grid-row: 2;
@@ -476,7 +530,7 @@ html:where(:has(body > header)) {
476
530
  align-self: start;
477
531
  position: sticky;
478
532
  top: var(--mote-rail-top, 1rem);
479
- max-height: calc(100svh - 2rem);
533
+ max-height: calc(100svh - var(--mote-rail-top, 1rem) - 1rem);
480
534
  overflow: auto;
481
535
  }
482
536
 
@@ -512,6 +566,19 @@ html:where(:has(body > header)) {
512
566
  color: var(--mote-on-accent);
513
567
  }
514
568
 
569
+ /* Rail headings: small label-scale for grouped navigation */
570
+ .mote-doc > nav :is(h2, h3),
571
+ .mote-doc > aside :is(h2, h3),
572
+ nav[aria-label="toc"] :is(h2, h3) {
573
+ margin: 1rem 0 0.3rem;
574
+ padding-inline: 0.75rem;
575
+ font-size: 0.72rem;
576
+ font-weight: 600;
577
+ text-transform: uppercase;
578
+ letter-spacing: 0.05em;
579
+ color: var(--mote-muted);
580
+ }
581
+
515
582
  .mote-doc:where(:has(> nav)) {
516
583
  grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
517
584
  }
@@ -526,6 +593,10 @@ html:where(:has(body > header)) {
526
593
  grid-column: 2;
527
594
  min-width: 0;
528
595
  align-self: start;
596
+ position: sticky;
597
+ top: var(--mote-rail-top, 1rem);
598
+ max-height: calc(100svh - var(--mote-rail-top, 1rem) - 1rem);
599
+ overflow: auto;
529
600
  }
530
601
 
531
602
  .mote-doc:where(:has(> nav)) > aside {
@@ -568,3 +639,8 @@ html:where(:has(body > header)) {
568
639
  gives 3-wide pricing; 26rem gives 2-column content. */
569
640
  grid-template-columns: repeat(auto-fill, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
570
641
  }
642
+
643
+ /* Sparse grids: collapse empty tracks (footers, short card rows) */
644
+ .mote-grid-sparse {
645
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
646
+ }
package/mote.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.css — a mote of styling for plain HTML.
3
- * v0.5.1 · MIT
3
+ * v0.6.1 · MIT
4
4
  * https://gitlab.com/abmach/mote · mirror: https://github.com/abmach/mote
5
5
  *
6
6
  * The smallest thing that can carry style:
@@ -46,6 +46,9 @@
46
46
  --mote-tracking: -0.011em;
47
47
  /* Display scale — opt-in, for heroes and error pages; body scale untouched */
48
48
  --mote-display: clamp(2.75rem, 1.5rem + 4vw, 5rem);
49
+ /* Reading measure: the comfortable max width for long-form content.
50
+ Override per-page for narrow or wide layouts. */
51
+ --mote-container-max: 70rem;
49
52
 
50
53
  /* Shape & depth */
51
54
  --mote-radius: 10px;
@@ -216,7 +219,7 @@ a {
216
219
  transition: color var(--mote-transition), text-decoration-color var(--mote-transition);
217
220
  }
218
221
 
219
- a:hover {
222
+ a:hover:where(:not(.mote-btn, .mote-ghost)) {
220
223
  color: var(--mote-accent-deep);
221
224
  text-decoration-color: currentColor;
222
225
  }
@@ -369,7 +372,8 @@ caption {
369
372
  }
370
373
 
371
374
  th, td {
372
- padding: 0.6rem 0.8rem;
375
+ font-variant-numeric: tabular-nums;
376
+ padding: var(--mote-cell-pad, 0.6rem) var(--mote-cell-pad-inline, 0.8rem);
373
377
  border-block-end: var(--mote-border-width) solid var(--mote-border);
374
378
  text-align: start;
375
379
  vertical-align: top;
@@ -406,7 +410,7 @@ fieldset {
406
410
  border-radius: var(--mote-radius);
407
411
  }
408
412
 
409
- legend { padding-inline: 0.4rem; }
413
+ legend { padding-inline: 0.4rem; font-weight: 600; }
410
414
 
411
415
  input, select, textarea {
412
416
  font: inherit;
@@ -451,10 +455,16 @@ select[multiple] {
451
455
  width: 1em;
452
456
  height: 1em;
453
457
  padding: 0;
454
- accent-color: var(--mote-accent);
458
+ accent-color: var(--mote-status, var(--mote-accent));
455
459
  vertical-align: baseline;
456
460
  }
457
461
 
462
+ /* Harden checkbox/radio sizing against page-level width: 100% rules
463
+ (the documented stacked-form recipe). :where() keeps it overridable. */
464
+ :is([type="checkbox"], [type="radio"]):where(input) {
465
+ width: 1em;
466
+ }
467
+
458
468
  [type="color"] {
459
469
  padding: 0.2rem;
460
470
  height: 2.4rem;
@@ -484,7 +494,7 @@ select[multiple] {
484
494
  cursor: not-allowed;
485
495
  }
486
496
 
487
- :is(input, select, textarea):user-invalid {
497
+ :is(input, select, textarea):is(:user-invalid, [aria-invalid="true"]) {
488
498
  border-color: var(--mote-error);
489
499
  }
490
500
 
@@ -492,7 +502,7 @@ button, [type="button"], [type="submit"], [type="reset"] {
492
502
  font: inherit;
493
503
  font-weight: 500;
494
504
  color: var(--mote-on-accent);
495
- padding: 0.5rem 1.1rem;
505
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 1.1rem);
496
506
  background: var(--mote-accent);
497
507
  border: var(--mote-border-width) solid transparent;
498
508
  border-radius: var(--mote-radius-sm);
@@ -523,9 +533,16 @@ button:hover, [type="button"]:hover, [type="submit"]:hover, [type="reset"]:hover
523
533
  /* Ghost buttons: quiet, transparent fill, accent-tinted. Like solids,
524
534
  status colors come from the button's own class, never from context. */
525
535
  .mote-ghost {
536
+ display: inline-block;
537
+ font: inherit;
538
+ font-weight: 500;
526
539
  background: none;
527
540
  border: var(--mote-border-width) solid color-mix(in oklab, var(--mote-accent), transparent 55%);
528
541
  color: var(--mote-accent-deep);
542
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 1.1rem);
543
+ border-radius: var(--mote-radius-sm);
544
+ text-decoration: none;
545
+ cursor: pointer;
529
546
  }
530
547
 
531
548
  .mote-ghost:hover {
@@ -631,7 +648,7 @@ progress, meter {
631
648
 
632
649
  /* Centered page wrapper: <main class="mote-container"> */
633
650
  .mote-container {
634
- width: min(100% - 3rem, 70rem);
651
+ width: min(100% - 3rem, var(--mote-container-max));
635
652
  margin-inline: auto;
636
653
  }
637
654
 
@@ -649,6 +666,74 @@ progress, meter {
649
666
 
650
667
  .mote-push { margin-inline-start: auto; }
651
668
 
669
+ /* Stacked forms: stretch fields, keep checkboxes/radios compact.
670
+ An opt-in alternative to the page-global width: 100% recipe. */
671
+ .mote-stack :is(input, select, textarea) {
672
+ width: 100%;
673
+ }
674
+
675
+ .mote-stack :is([type="checkbox"], [type="radio"]) {
676
+ width: 1em;
677
+ }
678
+
679
+ .mote-stack :is(input, select, textarea):where(.mote-sr-only) {
680
+ width: 1px;
681
+ }
682
+
683
+ /* Solid button on any element — anchors, spans, divs.
684
+ Same recipe as the element-level button styles; status classes
685
+ gated identically (class on the element itself). */
686
+ .mote-btn {
687
+ display: inline-block;
688
+ font: inherit;
689
+ font-weight: 500;
690
+ color: var(--mote-on-accent);
691
+ padding: 0.5rem 1.1rem;
692
+ background: var(--mote-accent);
693
+ border: var(--mote-border-width) solid transparent;
694
+ border-radius: var(--mote-radius-sm);
695
+ text-decoration: none;
696
+ cursor: pointer;
697
+ transition: background var(--mote-transition), border-color var(--mote-transition),
698
+ transform var(--mote-transition);
699
+ }
700
+
701
+ .mote-btn:hover {
702
+ background: color-mix(in oklab, var(--mote-accent), var(--mote-fg) 14%);
703
+ box-shadow: var(--mote-shadow-sm);
704
+ }
705
+
706
+ .mote-btn:active {
707
+ transform: translateY(1px);
708
+ }
709
+
710
+ .mote-btn:where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted):where(:not(.mote-ghost)) {
711
+ background: var(--mote-status);
712
+ color: var(--mote-on-status, var(--mote-bg));
713
+ }
714
+
715
+ .mote-btn:where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted):where(:not(.mote-ghost)):hover {
716
+ background: color-mix(in oklab, var(--mote-status), var(--mote-fg) 14%);
717
+ }
718
+
719
+ /* Media object: avatar/icon beside body (forum posts, comments, chat) */
720
+ .mote-media {
721
+ display: flex;
722
+ align-items: flex-start;
723
+ gap: 1rem;
724
+ }
725
+
726
+ .mote-media > :last-child {
727
+ flex: 1;
728
+ min-width: 0;
729
+ }
730
+
731
+ /* Headings inside rows (version headers, card titles): default heading
732
+ margins balloon the row — zero them inside the flex context. */
733
+ :where(.mote-row) > :is(h1, h2, h3, h4, h5, h6) {
734
+ margin-block: 0;
735
+ }
736
+
652
737
  /* Visually hidden, screen-reader available */
653
738
  .mote-sr-only {
654
739
  position: absolute;
@@ -740,6 +825,7 @@ progress, meter {
740
825
  font-size: var(--mote-display);
741
826
  line-height: 1.1;
742
827
  letter-spacing: var(--mote-tracking);
828
+ color: var(--mote-status, inherit);
743
829
  text-wrap: balance;
744
830
  }
745
831
 
@@ -784,4 +870,21 @@ progress, meter {
784
870
  a { color: #000; }
785
871
  details { border: none; }
786
872
  dialog { box-shadow: none; }
873
+
874
+ /* Opt-in print helpers */
875
+ .mote-no-print { display: none !important; }
876
+ .mote-print-only { display: block; }
877
+ }
878
+
879
+ @media not print {
880
+ .mote-print-only { display: none; }
881
+ }
882
+
883
+ /* Theme switch: disable transitions during data-theme flips.
884
+ Chromium doesn't restart CSS transitions on custom-property changes,
885
+ so flipping data-theme leaves elements at the old theme's colors.
886
+ Apply this class before the flip, remove after one frame. */
887
+ .mote-theme-switch,
888
+ .mote-theme-switch * {
889
+ transition: none !important;
787
890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mote.css",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "A mote of styling for plain HTML — tiny, semantic, class-optional CSS with zero dependencies and no build step.",
5
5
  "style": "mote.css",
6
6
  "files": [