mote.css 0.6.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,96 @@ 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.6.0] - 2026-10-20
8
+ ## [0.7.0] - 2026-10-21
9
+
10
+ ### Changed
11
+
12
+ - **Badges are gated** (same as buttons): a plain `.mote-badge` is always
13
+ accent-tinted; a status class on the badge itself provides the tint —
14
+ never inherited from context. Closes the "tag chips silently re-tint
15
+ inside themed cards" surprise from four evaluation rounds.
16
+ Two-tier principle: **content surfaces follow context; content markers
17
+ and actions declare their own status.**
18
+
19
+ ### Fixed
20
+
21
+ - `.mote-table-scroll` sticky-header borders: Chromium paints collapsed
22
+ borders at the original flow position, not with the sticky cell —
23
+ switched to `border-collapse: separate; border-spacing: 0` inside the
24
+ wrapper.
25
+ - `.mote-table-scroll` clips rows in print: `max-height: none;
26
+ overflow: visible` added to the `@media print` block.
27
+ - Bare `<pre>` without `<code>` now gets the mono stack and 0.875rem size.
28
+ - `.mote-btn` now reads `var(--mote-control-pad, …)` instead of hardcoded
29
+ `0.5rem 1.1rem` — density overrides reach all buttons uniformly.
30
+ - Form controls (input/select/textarea) now read the same density tokens
31
+ as buttons: `var(--mote-control-pad, …) var(--mote-control-pad-inline, …)`.
32
+ - `.mote-stack` now stretches `progress` and `meter` alongside fields.
33
+ - `.mote-doc > aside` resets to `position: static` under 50rem (was
34
+ staying sticky while the nav went static — clipped scroll region on mobile).
35
+ - `.mote-table-scroll` max-height tokenized: `var(--mote-table-max, 60vh)`.
36
+ - Badge links get a hover state: `filter: brightness(0.92)`.
37
+ - Docs: theme-toggle snippet now 3-state (auto→light→dark) matching the
38
+ working demo; Patterns error-page uses `.mote-hero`; docs' own header
39
+ uses `.mote-row`/`.mote-push` instead of inline styles.
40
+
41
+ ### Added
42
+
43
+ - `.mote-hero`: vertically centers content in the remaining viewport
44
+ height — for error pages, login screens, empty states. Uses
45
+ `--mote-hero-offset` (default 12rem) to account for header/footer.
46
+ - `.mote-card-media`: flush-top cover images for cards — negative margins,
47
+ clipped overflow, radius matched to the card, all in one class. Replaces
48
+ the 5-line page recipe that broke card corners.
49
+ - `.mote-brand`: quiet wordmark link for site headers — `font-weight: 700`,
50
+ fg color, no underline.
51
+ - `.mote-num`: right-aligned tabular figures for data table columns.
52
+
53
+ ### Changed (evaluation)
54
+
55
+ - Fourth ten-app evaluation completed against v0.6.1; all round-3 fixes
56
+ verified holding. Round-4 findings addressed in this minor release.
57
+
58
+ ### Fixed
59
+
60
+ - `.mote-print-only` now actually displays in print (the unconditional
61
+ `display: none` was after the print block at equal specificity —
62
+ scoped to `@media not print` and moved before it).
63
+ - `.mote-stack` no longer stretches `.mote-sr-only` inputs to 100%
64
+ width (hidden file inputs were creating invisible horizontal scroll).
65
+ - `.mote-doc > main` now uses `overflow-x: auto` (was `clip`, which
66
+ made wide tables invisible rather than scrollable).
67
+ - `.mote-theme-switch` shipped in mote.css: disables transitions during
68
+ `data-theme` flips so Chromium doesn't wedge elements at stale colors
69
+ (custom-property changes don't restart CSS transitions). The docs'
70
+ toggle snippet now includes it; the demo uses it.
71
+ - `.mote-doc > aside` gets the same sticky treatment as the nav rail
72
+ (top, max-height, overflow) — TOCs and aside content no longer scroll
73
+ out of view.
74
+ - Nav rail `max-height` accounts for `--mote-rail-top` (was
75
+ `calc(100svh - 2rem)` regardless of header height).
76
+ - Rail heading treatment (small uppercase labels) now applies to
77
+ `.mote-doc > aside` and `nav[aria-label="toc"]` headings too.
78
+ - `legend` gets `font-weight: 600` so the group caption reads stronger
79
+ than the field labels inside it.
80
+
81
+ ### Added
82
+
83
+ - `tabular-nums` on all `td` — figures align in data tables without
84
+ any page CSS (invisible on non-numeric content).
85
+ - `.mote-table-scroll`: scrollable table wrapper with sticky `thead`,
86
+ no margin jump on scroll start, and no page-level horizontal scroll.
87
+ - `.mote-grid-sparse`: auto-fit variant that collapses empty grid
88
+ tracks (footers, short card rows).
89
+ - `--mote-control-pad` / `--mote-control-pad-inline` and
90
+ `--mote-cell-pad` / `--mote-cell-pad-inline` density tokens.
91
+ - Docs: toggle snippet includes the theme-switch workaround; Chromium
92
+ note references the shipped class.
93
+
94
+ ### Changed
95
+
96
+ - Third ten-app evaluation completed against v0.6.0; all round-2 fixes
97
+ verified holding. Round-3 findings addressed in this patch release.
9
98
 
10
99
  ### Fixed
11
100
 
@@ -335,6 +424,8 @@ Components:
335
424
  - Element gallery demo page (`demo.html`).
336
425
 
337
426
  [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
427
+ [0.7.0]: https://gitlab.com/abmach/mote/compare/v0.6.1...v0.7.0
428
+ [0.6.1]: https://gitlab.com/abmach/mote/compare/v0.6.0...v0.6.1
338
429
  [0.6.0]: https://gitlab.com/abmach/mote/compare/v0.5.1...v0.6.0
339
430
  [0.5.1]: https://gitlab.com/abmach/mote/compare/v0.5.0...v0.5.1
340
431
  [0.5.0]: https://gitlab.com/abmach/mote/compare/v0.4.0...v0.5.0
package/README.md CHANGED
@@ -100,6 +100,8 @@ The consequence: theming is a handful of variable overrides, and an inline tweak
100
100
  | `--mote-display` | `clamp(2.75rem, 1.5rem + 4vw, 5rem)` | | Display text scale (opt-in) |
101
101
  | `--mote-grid-min` | `16rem` | | Card grid minimum track width |
102
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 |
103
105
  | `--mote-shadow` / `--mote-shadow-sm` | warm diffuse | | Elevation |
104
106
  | `--mote-transition` | `150ms ease` | | Hover/focus timing |
105
107
 
@@ -149,19 +151,29 @@ mote is class-optional by design — elements are styled from tags alone; these
149
151
  | Semantic buttons (gated) | `class="mote-success"` etc. on the button itself — themed sections never recolor actions |
150
152
  | Ghost buttons | `.mote-ghost` — transparent fill; status tints from the button's own class (gated like solids) |
151
153
  | Solid button on any element | `.mote-btn` — the full solid recipe for anchors, spans; status classes gated |
154
+ | Hero centering | `.mote-hero` — vertically centers content for error/login/empty-state pages |
155
+ | Card cover media | `.mote-card-media` — flush-top cover images with matched radius |
156
+ | Brand/wordmark | `.mote-brand` — quiet non-nav link for site headers |
152
157
  | Media objects | `.mote-media` — avatar/icon beside body (flex, last child grows) |
153
158
  | Display text | `.mote-display` — opt-in hero/error-code scale via `--mote-display` |
154
159
  | Content rows | `.mote-row` + `.mote-push` — flex row, right-align helper |
160
+ | Stacked forms | `.mote-stack` — opt-in full-width fields, checkboxes stay 1em |
161
+ | Scrollable tables | `.mote-table-scroll` — sticky `thead`, no page scroll, no margin jump |
162
+ | Numeric columns | `.mote-num` — right-aligned tabular figures |
163
+ | Sparse grids | `.mote-grid-sparse` — auto-fit variant, collapses empty tracks |
155
164
  | Pagination | `nav[aria-label="pagination"]` — semantic, no class |
156
165
  | Table of contents | `nav[aria-label="toc"]` — semantic, no class |
157
166
  | Footer baseline | `body > footer` — semantic top-border + padding |
158
167
  | Screen-reader helper | `.mote-sr-only` (doubles as a skip link when focused) |
159
168
  | Print helpers | `.mote-no-print` / `.mote-print-only` |
169
+ | Theme-switch guard | `.mote-theme-switch` — prevents Chromium stale-color bug on `data-theme` flips |
160
170
  | Status inheritance control | `.mote-default` (force accent) · `.mote-reset` (clear inherited status) |
171
+ | Hero offset | `--mote-hero-offset` (default 12rem) — space for header/footer in `.mote-hero` |
172
+ | Table max height | `--mote-table-max` (default 60vh) — `.mote-table-scroll` panel cap |
161
173
  | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
162
174
  | Card grids | `.mote-grid` — auto-fill columns from the container, no breakpoints |
163
175
  | Cards | `.mote-card` |
164
- | Badges | `.mote-badge` + optional status class |
176
+ | Badges | `.mote-badge` — gated: status from the badge's own class, never inherited |
165
177
  | Avatars | `.mote-avatar` |
166
178
  | Skeleton / spinner | `.mote-skeleton`, `.mote-spinner` |
167
179
  | Tooltips | `[popover].mote-tooltip` + `popovertarget` button |
@@ -183,7 +195,7 @@ animations become instant, on older engines.
183
195
 
184
196
  ## Demo
185
197
 
186
- Open [`docs/index.html`](docs/index.html) for the full live documentation, or [`demo.html`](demo.html) for a quick gallery of every styled element.
198
+ Open [`docs/index.html`](docs/index.html) for the full live documentation, or [`docs/demo.html`](docs/demo.html) for a quick gallery of every styled element.
187
199
 
188
200
  ## License
189
201
 
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.6.0 · MIT
3
+ * v0.7.0 · 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).
@@ -40,6 +40,10 @@
40
40
  * <span class="mote-badge mote-success">success</span>
41
41
  * ------------------------------------------------------------------------- */
42
42
 
43
+ /* Badges are content markers, not surfaces — they declare their own
44
+ status, never inherit it from context (same gating as buttons).
45
+ A plain .mote-badge is always accent-tinted; a status class on the
46
+ badge itself provides the tint. */
43
47
  .mote-badge {
44
48
  display: inline-block;
45
49
  padding: 0.1rem 0.6rem;
@@ -47,8 +51,13 @@
47
51
  font-size: 0.8em;
48
52
  font-weight: 500;
49
53
  line-height: 1.4;
50
- background: var(--mote-status-soft, var(--mote-accent-soft));
51
- color: var(--mote-status, var(--mote-accent-deep));
54
+ background: var(--mote-accent-soft);
55
+ color: var(--mote-accent-deep);
56
+ }
57
+
58
+ .mote-badge:where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted) {
59
+ background: var(--mote-status-soft);
60
+ color: var(--mote-status);
52
61
  }
53
62
 
54
63
  /* Badges as links (tag chips, version pills): kill the underline */
@@ -56,6 +65,10 @@ a:where(.mote-badge) {
56
65
  text-decoration: none;
57
66
  }
58
67
 
68
+ a:where(.mote-badge):hover {
69
+ filter: brightness(0.92);
70
+ }
71
+
59
72
  /* ---------------------------------------------------------------------------
60
73
  * 3. Avatars
61
74
  * ------------------------------------------------------------------------- */
@@ -245,6 +258,44 @@ nav[aria-label="toc"] a:hover {
245
258
  color: var(--mote-accent-deep);
246
259
  }
247
260
 
261
+ /* Flush-top cover media for cards: negative margins derived from the
262
+ card's own padding, clipped overflow, radius matched to the card. */
263
+ .mote-card-media {
264
+ margin: -1.25rem -1.5rem 1rem;
265
+ overflow: clip;
266
+ border-radius: calc(var(--mote-radius-lg) - var(--mote-border-width))
267
+ calc(var(--mote-radius-lg) - var(--mote-border-width)) 0 0;
268
+ }
269
+
270
+ .mote-card-media img,
271
+ .mote-card-media video {
272
+ display: block;
273
+ width: 100%;
274
+ max-width: none;
275
+ border-radius: inherit;
276
+ }
277
+
278
+ /* Scrollable table wrapper: sticky thead, no page scroll, no margin jump */
279
+ .mote-table-scroll {
280
+ overflow-x: auto;
281
+ max-height: var(--mote-table-max, 60vh);
282
+ overflow-y: auto;
283
+ }
284
+
285
+ /* Chromium paints collapsed borders at the original flow position,
286
+ not with the sticky cell — separate borders belong to the cell. */
287
+ .mote-table-scroll table {
288
+ margin-block: 0;
289
+ border-collapse: separate;
290
+ border-spacing: 0;
291
+ }
292
+
293
+ .mote-table-scroll thead th {
294
+ position: sticky;
295
+ top: 0;
296
+ background: var(--mote-bg);
297
+ }
298
+
248
299
  /* Page footer: the quiet sibling of the site header */
249
300
  :where(body > footer, .mote-footer) {
250
301
  margin-block-start: 3rem;
@@ -502,7 +553,7 @@ html:where(:has(body > header)) {
502
553
  /* Content stays inside the grid track — wide tables scroll here,
503
554
  not the page. */
504
555
  .mote-doc > main {
505
- overflow-x: clip;
556
+ overflow-x: auto;
506
557
  }
507
558
 
508
559
  /* nav = left rail */
@@ -513,7 +564,7 @@ html:where(:has(body > header)) {
513
564
  align-self: start;
514
565
  position: sticky;
515
566
  top: var(--mote-rail-top, 1rem);
516
- max-height: calc(100svh - 2rem);
567
+ max-height: calc(100svh - var(--mote-rail-top, 1rem) - 1rem);
517
568
  overflow: auto;
518
569
  }
519
570
 
@@ -550,7 +601,9 @@ html:where(:has(body > header)) {
550
601
  }
551
602
 
552
603
  /* Rail headings: small label-scale for grouped navigation */
553
- .mote-doc > nav :is(h2, h3) {
604
+ .mote-doc > nav :is(h2, h3),
605
+ .mote-doc > aside :is(h2, h3),
606
+ nav[aria-label="toc"] :is(h2, h3) {
554
607
  margin: 1rem 0 0.3rem;
555
608
  padding-inline: 0.75rem;
556
609
  font-size: 0.72rem;
@@ -574,6 +627,10 @@ html:where(:has(body > header)) {
574
627
  grid-column: 2;
575
628
  min-width: 0;
576
629
  align-self: start;
630
+ position: sticky;
631
+ top: var(--mote-rail-top, 1rem);
632
+ max-height: calc(100svh - var(--mote-rail-top, 1rem) - 1rem);
633
+ overflow: auto;
577
634
  }
578
635
 
579
636
  .mote-doc:where(:has(> nav)) > aside {
@@ -600,6 +657,12 @@ html:where(:has(body > header)) {
600
657
  grid-row: auto;
601
658
  }
602
659
 
660
+ .mote-doc > aside {
661
+ position: static;
662
+ max-height: none;
663
+ overflow: visible;
664
+ }
665
+
603
666
  .mote-doc > nav {
604
667
  position: static;
605
668
  max-height: none;
@@ -616,3 +679,8 @@ html:where(:has(body > header)) {
616
679
  gives 3-wide pricing; 26rem gives 2-column content. */
617
680
  grid-template-columns: repeat(auto-fill, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
618
681
  }
682
+
683
+ /* Sparse grids: collapse empty tracks (footers, short card rows) */
684
+ .mote-grid-sparse {
685
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
686
+ }
package/mote.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.css — a mote of styling for plain HTML.
3
- * v0.6.0 · MIT
3
+ * v0.7.0 · 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:
@@ -219,7 +219,7 @@ a {
219
219
  transition: color var(--mote-transition), text-decoration-color var(--mote-transition);
220
220
  }
221
221
 
222
- a:hover {
222
+ a:hover:where(:not(.mote-btn, .mote-ghost)) {
223
223
  color: var(--mote-accent-deep);
224
224
  text-decoration-color: currentColor;
225
225
  }
@@ -335,6 +335,8 @@ kbd {
335
335
  pre {
336
336
  padding: 1rem 1.2rem;
337
337
  overflow-x: auto;
338
+ font-family: var(--mote-font-mono);
339
+ font-size: 0.875rem;
338
340
  border-radius: var(--mote-radius);
339
341
  background: var(--mote-surface);
340
342
  border: var(--mote-border-width) solid var(--mote-border);
@@ -372,7 +374,8 @@ caption {
372
374
  }
373
375
 
374
376
  th, td {
375
- padding: 0.6rem 0.8rem;
377
+ font-variant-numeric: tabular-nums;
378
+ padding: var(--mote-cell-pad, 0.6rem) var(--mote-cell-pad-inline, 0.8rem);
376
379
  border-block-end: var(--mote-border-width) solid var(--mote-border);
377
380
  text-align: start;
378
381
  vertical-align: top;
@@ -409,12 +412,12 @@ fieldset {
409
412
  border-radius: var(--mote-radius);
410
413
  }
411
414
 
412
- legend { padding-inline: 0.4rem; }
415
+ legend { padding-inline: 0.4rem; font-weight: 600; }
413
416
 
414
417
  input, select, textarea {
415
418
  font: inherit;
416
419
  color: inherit;
417
- padding: 0.5rem 0.75rem;
420
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 0.75rem);
418
421
  background: var(--mote-surface);
419
422
  border: var(--mote-border-width) solid var(--mote-border);
420
423
  border-radius: var(--mote-radius-sm);
@@ -501,7 +504,7 @@ button, [type="button"], [type="submit"], [type="reset"] {
501
504
  font: inherit;
502
505
  font-weight: 500;
503
506
  color: var(--mote-on-accent);
504
- padding: 0.5rem 1.1rem;
507
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 1.1rem);
505
508
  background: var(--mote-accent);
506
509
  border: var(--mote-border-width) solid transparent;
507
510
  border-radius: var(--mote-radius-sm);
@@ -532,10 +535,13 @@ button:hover, [type="button"]:hover, [type="submit"]:hover, [type="reset"]:hover
532
535
  /* Ghost buttons: quiet, transparent fill, accent-tinted. Like solids,
533
536
  status colors come from the button's own class, never from context. */
534
537
  .mote-ghost {
538
+ display: inline-block;
539
+ font: inherit;
540
+ font-weight: 500;
535
541
  background: none;
536
542
  border: var(--mote-border-width) solid color-mix(in oklab, var(--mote-accent), transparent 55%);
537
543
  color: var(--mote-accent-deep);
538
- padding: 0.5rem 1.1rem;
544
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 1.1rem);
539
545
  border-radius: var(--mote-radius-sm);
540
546
  text-decoration: none;
541
547
  cursor: pointer;
@@ -662,9 +668,20 @@ progress, meter {
662
668
 
663
669
  .mote-push { margin-inline-start: auto; }
664
670
 
671
+ /* Hero: vertically center content in the remaining viewport height.
672
+ For error pages, login screens, empty states. */
673
+ .mote-hero {
674
+ min-height: calc(100svh - var(--mote-hero-offset, 12rem));
675
+ display: flex;
676
+ flex-direction: column;
677
+ justify-content: center;
678
+ align-items: center;
679
+ text-align: center;
680
+ }
681
+
665
682
  /* Stacked forms: stretch fields, keep checkboxes/radios compact.
666
683
  An opt-in alternative to the page-global width: 100% recipe. */
667
- .mote-stack :is(input, select, textarea) {
684
+ .mote-stack :is(input, select, textarea, progress, meter) {
668
685
  width: 100%;
669
686
  }
670
687
 
@@ -672,6 +689,10 @@ progress, meter {
672
689
  width: 1em;
673
690
  }
674
691
 
692
+ .mote-stack :is(input, select, textarea):where(.mote-sr-only) {
693
+ width: 1px;
694
+ }
695
+
675
696
  /* Solid button on any element — anchors, spans, divs.
676
697
  Same recipe as the element-level button styles; status classes
677
698
  gated identically (class on the element itself). */
@@ -680,7 +701,7 @@ progress, meter {
680
701
  font: inherit;
681
702
  font-weight: 500;
682
703
  color: var(--mote-on-accent);
683
- padding: 0.5rem 1.1rem;
704
+ padding: var(--mote-control-pad, 0.5rem) var(--mote-control-pad-inline, 1.1rem);
684
705
  background: var(--mote-accent);
685
706
  border: var(--mote-border-width) solid transparent;
686
707
  border-radius: var(--mote-radius-sm);
@@ -727,6 +748,19 @@ progress, meter {
727
748
  }
728
749
 
729
750
  /* Visually hidden, screen-reader available */
751
+ /* Brand/wordmark: quiet non-nav link for site headers */
752
+ .mote-brand {
753
+ font-weight: 700;
754
+ color: var(--mote-fg);
755
+ text-decoration: none;
756
+ }
757
+
758
+ /* Numeric table column: right-align tabular figures */
759
+ .mote-num {
760
+ text-align: end;
761
+ font-variant-numeric: tabular-nums;
762
+ }
763
+
730
764
  .mote-sr-only {
731
765
  position: absolute;
732
766
  width: 1px;
@@ -866,6 +900,18 @@ progress, meter {
866
900
  /* Opt-in print helpers */
867
901
  .mote-no-print { display: none !important; }
868
902
  .mote-print-only { display: block; }
903
+ .mote-table-scroll { max-height: none; overflow: visible; }
869
904
  }
870
905
 
871
- .mote-print-only { display: none; }
906
+ @media not print {
907
+ .mote-print-only { display: none; }
908
+ }
909
+
910
+ /* Theme switch: disable transitions during data-theme flips.
911
+ Chromium doesn't restart CSS transitions on custom-property changes,
912
+ so flipping data-theme leaves elements at the old theme's colors.
913
+ Apply this class before the flip, remove after one frame. */
914
+ .mote-theme-switch,
915
+ .mote-theme-switch * {
916
+ transition: none !important;
917
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mote.css",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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": [