mote.css 0.8.0 → 0.9.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,6 +5,89 @@ 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.9.1] - 2026-10-23
9
+
10
+ ### Fixed
11
+
12
+ - The header hairline is now fully snippet-driven: it paints only
13
+ while the nav actually scrolls. With the snippet, an empty
14
+ `data-nav` (a single pill, nothing to scroll) leaves the line gone;
15
+ without the snippet there is no line at all — zero-JS headers keep
16
+ just the pills, and the cut pill is the overflow signal. Both states
17
+ fixture-pinned.
18
+
19
+ ## [0.9.0] - 2026-10-22
20
+
21
+ ### Added
22
+
23
+ - **`tests/fixture.html` — the regression fixture**: a self-checking page
24
+ loading the real committed stylesheets, with 54 assertions pinning the
25
+ principle invariants (two-tier gating, status inheritance, control
26
+ density, stack behavior, zebra-vs-`aria-current` order, skip-link states,
27
+ landmark styling, theme flip). Open it in any browser — the title shows
28
+ PASS or FAIL. Its first run caught two real bugs, both fixed in this
29
+ release.
30
+ - `tr[aria-current]` current-row treatment: accent-soft tint that reads
31
+ over zebra (same vocabulary as nav pills and pagination). The fixture
32
+ asserts it on both parities.
33
+ - `.mote-grid-sparse` is now standalone-complete (`display: grid` + gap) —
34
+ it was a columns-only modifier that silently rendered as a plain block
35
+ when used alone, as the README advertised it (fixture-caught).
36
+
37
+ ### Changed
38
+
39
+ - **Badges never grow past body scale**: `font-size: min(0.8em, 0.8rem)` —
40
+ a pill inside a heading stays readable (three round-6 builds hit the old
41
+ heading-scale default), small contexts still scale down with the em side.
42
+ - **`.mote-stack` is now a real stack**: flex column with
43
+ `--mote-stack-gap` (default 1.25rem) and zeroed child margins, so the
44
+ name is honest for any content — form rhythm is designed, not `<p>`
45
+ margin luck. Field stretching and the 1em checkbox/radio exception are
46
+ unchanged. The sr-only control rule inside stacks was strengthened to
47
+ beat the checkbox width rule on specificity.
48
+ - **Header navs scroll instead of wrapping**: `body > header nav` /
49
+ `.mote-nav` pill rows stay one line at any width — a long section list
50
+ (the docs' own ten pills) scrolls inside the bar instead of pushing the
51
+ brand or the theme toggle to a second row. The scrollbar is hidden;
52
+ a hairline under the pills carries directional accent fades pinned to
53
+ the nav edges — they never float with scroll position. Pure CSS cannot
54
+ distinguish "at the start" from "nothing to scroll", so the fades key
55
+ off a `data-nav` attribute kept current by a seven-line documented
56
+ snippet (the theme-toggle precedent, including a webfont-load re-sync);
57
+ without it, the hairline and the
58
+ cut pill are the whole hint. Direction-agnostic snippet math; the
59
+ gradients flip under `[dir="rtl"]`.
60
+ - Tokenized the hardcoded paddings: `--mote-grid-gap` (1.25rem) and
61
+ `--mote-band-pad` (4rem) — thin strips and matched split rhythms no
62
+ longer need hand-copied values.
63
+
64
+ ### Fixed (docs — round-6 build findings)
65
+
66
+ - Band placement warning: inside `main.mote-container` a band bleeds only
67
+ to the container edge — the docs now say so (the one round-6 trap that
68
+ actually broke a build mid-flight)
69
+ - `.mote-card` + `.mote-media` merge warning: the card's column direction
70
+ silently wins; nest instead.
71
+ - `aria-label` case-sensitivity warning on the semantic selectors.
72
+ - `.mote-num` widened: "table columns or any figure block".
73
+ - Media-object default alignment (`flex-start`) documented.
74
+ - Skip-link mechanism wording: clipped via `clip-path`, not `display: none`.
75
+ - Badge and stack paragraphs rewritten for the new behavior; note-width
76
+ recipe added; three new token rows.
77
+ - Six new pattern recipes: toolbar row, result rows, price line, post
78
+ actions, growing input, prominent search.
79
+ - **`demo.html` merged into the docs page as the Gallery section** (every
80
+ input type, footnoted table, sample output) and deleted — one canonical
81
+ page, no drift risk. README and in-page links updated.
82
+
83
+ ### Evaluation
84
+
85
+ - Sixth round: ten agents built ten NEW page types (forum, product,
86
+ status, checkout, inbox, gallery, profile, FAQ, poll, playlist) against
87
+ v0.8.0. 10/10 would-use-again, zero rendering failures; every v0.8.0
88
+ fix verified load-bearing. Asymmetric grid splits stay page CSS by
89
+ design (documented, not a gap).
90
+
8
91
  ## [0.8.0] - 2026-10-22
9
92
 
10
93
  ### Added
@@ -487,6 +570,8 @@ Components:
487
570
 
488
571
  [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
489
572
  [0.8.0]: https://gitlab.com/abmach/mote/compare/v0.7.0...v0.8.0
573
+ [0.9.0]: https://gitlab.com/abmach/mote/compare/v0.8.0...v0.9.0
574
+ [0.9.1]: https://gitlab.com/abmach/mote/compare/v0.9.0...v0.9.1
490
575
  [0.7.0]: https://gitlab.com/abmach/mote/compare/v0.6.1...v0.7.0
491
576
  [0.6.1]: https://gitlab.com/abmach/mote/compare/v0.6.0...v0.6.1
492
577
  [0.6.0]: https://gitlab.com/abmach/mote/compare/v0.5.1...v0.6.0
package/README.md CHANGED
@@ -158,12 +158,13 @@ mote is class-optional by design — elements are styled from tags alone; these
158
158
  | Media objects | `.mote-media` — avatar/icon beside body (flex, last child grows) |
159
159
  | Display text | `.mote-display` — opt-in hero/error-code scale via `--mote-display` |
160
160
  | Content rows | `.mote-row` + `.mote-push` — flex row, right-align helper |
161
- | Stacked forms | `.mote-stack` — opt-in full-width fields, checkboxes stay 1em |
161
+ | Stacks | `.mote-stack` — vertical rhythm with `--mote-stack-gap`; fields full width, checkboxes 1em, works on any content |
162
162
  | Scrollable tables | `.mote-table-scroll` — sticky `thead`, no page scroll, no margin jump |
163
163
  | Numeric columns | `.mote-num` — right-aligned tabular figures |
164
- | Sparse grids | `.mote-grid-sparse` — auto-fit variant, collapses empty tracks |
164
+ | Sparse grids | `.mote-grid-sparse` — standalone auto-fit variant, collapses empty tracks |
165
165
  | Pagination | `nav[aria-label="pagination"]` — semantic, no class |
166
166
  | Table of contents | `nav[aria-label="toc"]` — semantic, no class |
167
+ | Current table row | `tr[aria-current]` — accent-tinted row, reads over zebra |
167
168
  | Footer baseline | `body > footer` — semantic top-border + padding; its `nav` renders as a quiet wrapped row |
168
169
  | Screen-reader helper | `.mote-sr-only` — pure hiding, never appears |
169
170
  | Skip link | `.mote-skip-link` — sr-only until keyboard focus, then a fixed pill |
@@ -172,10 +173,13 @@ mote is class-optional by design — elements are styled from tags alone; these
172
173
  | Status inheritance control | `.mote-default` (force accent) · `.mote-reset` (clear inherited status) |
173
174
  | Hero offset | `--mote-hero-offset` (default 12rem) — space for header/footer in `.mote-hero` |
174
175
  | Table max height | `--mote-table-max` (default 60vh) — `.mote-table-scroll` panel cap |
176
+ | Stack gap | `--mote-stack-gap` (default 1.25rem) — `.mote-stack` vertical rhythm |
177
+ | Grid gap | `--mote-grid-gap` (default 1.25rem) — `.mote-grid` / `.mote-grid-sparse` |
178
+ | Band padding | `--mote-band-pad` (default 4rem) — `.mote-band`; 1rem for thin strips |
175
179
  | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
176
180
  | Card grids | `.mote-grid` — auto-fill columns from the container, no breakpoints |
177
181
  | Cards | `.mote-card` — normalizes content: alignment reads start even inside centered regions |
178
- | Badges | `.mote-badge` — gated: status from the badge's own class, never inherited |
182
+ | Badges | `.mote-badge` — gated: status from the badge's own class, never inherited; never grows past body scale |
179
183
  | Avatars | `.mote-avatar` |
180
184
  | Skeleton / spinner | `.mote-skeleton`, `.mote-spinner` |
181
185
  | Tooltips | `[popover].mote-tooltip` + `popovertarget` button |
@@ -197,7 +201,9 @@ animations become instant, on older engines.
197
201
 
198
202
  ## Demo
199
203
 
200
- 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.
204
+ Open [`tests/fixture.html`](tests/fixture.html) in any browser for the regression checks — the title shows PASS or FAIL.
205
+
206
+ Open [`docs/index.html`](docs/index.html) for the full live documentation — it ends with a gallery of every styled element.
201
207
 
202
208
  ## License
203
209
 
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.8.0 · MIT
3
+ * v0.9.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).
@@ -47,7 +47,7 @@
47
47
  </section>
48
48
  */
49
49
  .mote-band {
50
- padding-block: 4rem;
50
+ padding-block: var(--mote-band-pad, 4rem);
51
51
  background: color-mix(in oklab, var(--mote-status, var(--mote-accent)) 12%, var(--mote-bg));
52
52
  }
53
53
 
@@ -66,7 +66,9 @@
66
66
  display: inline-block;
67
67
  padding: 0.1rem 0.6rem;
68
68
  border-radius: 999px;
69
- font-size: 0.8em;
69
+ /* Never past body scale: 0.8em inside a heading balloons,
70
+ so clamp it — small contexts still scale down. */
71
+ font-size: min(0.8em, 0.8rem);
70
72
  font-weight: 500;
71
73
  line-height: 1.4;
72
74
  background: var(--mote-accent-soft);
@@ -164,13 +166,97 @@ a:where(.mote-badge):hover {
164
166
  padding-block: 0.7rem;
165
167
  }
166
168
 
169
+ /* Long navs scroll inside the bar — they never wrap to a second line
170
+ or push the rest of the row (brand, toggle) off it. min-width lets
171
+ the nav yield space before the surrounding row needs to. */
172
+ :where(body > header nav, .mote-nav) {
173
+ /* basis 0 keeps the nav on the row's first line no matter how long
174
+ (line-breaking sees the unshrunk basis, so a content-sized basis
175
+ would push trailing controls to a second line); it then grows
176
+ into the free space and the pill row scrolls inside it. */
177
+ flex: 1 1 0%;
178
+ min-width: 0;
179
+ }
180
+
181
+ /* Long navs: basis 0 keeps the nav on the row's first line no matter
182
+ how long (line-breaking sees the unshrunk basis, so a content-sized
183
+ basis would push trailing controls to a second line); it then grows
184
+ into the free space and the pill row scrolls inside it. The nav also
185
+ carries the hairline under the pills and hosts the fade pseudos. */
186
+ :where(body > header nav, .mote-nav) {
187
+ position: relative;
188
+ flex: 1 1 0%;
189
+ min-width: 0;
190
+ }
191
+
192
+ /* The hairline is snippet-driven: it paints only while the nav
193
+ actually scrolls. An empty data-nav means "nothing to scroll" —
194
+ no line under a single pill. No attribute at all means no snippet:
195
+ no line either; zero-JS headers keep just the pills, and the cut
196
+ pill is the overflow signal. */
197
+ :where(body > header nav[data-nav]:not([data-nav=""]), .mote-nav[data-nav]:not([data-nav=""])) {
198
+ background:
199
+ linear-gradient(var(--mote-border), var(--mote-border)) left bottom / 100% 2px no-repeat;
200
+ }
201
+
202
+ /* Scroll hints: the hairline under the pills carries directional fades
203
+ that bloom toward whichever side holds more content. Pure CSS cannot
204
+ distinguish "scrolled to the start" from "nothing to scroll", so the
205
+ fades key off a data-nav attribute kept current by the seven-line
206
+ documented snippet (same opt-in pattern as the theme toggle).
207
+ Without it: the hairline and the cut pill are the whole hint.
208
+ The pseudos pin to the nav edges — the accent never floats inward,
209
+ whatever the scroll position. */
210
+ :where(body > header nav, .mote-nav)::before,
211
+ :where(body > header nav, .mote-nav)::after {
212
+ content: "";
213
+ position: absolute;
214
+ bottom: 0;
215
+ width: 4rem;
216
+ height: 2px;
217
+ opacity: 0;
218
+ transition: opacity var(--mote-transition);
219
+ pointer-events: none;
220
+ }
221
+
222
+ :where(body > header nav, .mote-nav)::before {
223
+ inset-inline-start: 0;
224
+ background: linear-gradient(to left, transparent, color-mix(in oklab, var(--mote-accent) 55%, var(--mote-bg)) 40%);
225
+ }
226
+
227
+ :where(body > header nav, .mote-nav)::after {
228
+ inset-inline-end: 0;
229
+ background: linear-gradient(to right, transparent, color-mix(in oklab, var(--mote-accent) 55%, var(--mote-bg)) 40%);
230
+ }
231
+
232
+ :where(body > header nav[data-nav~="start"], nav.mote-nav[data-nav~="start"])::before,
233
+ :where(body > header nav[data-nav~="end"], nav.mote-nav[data-nav~="end"])::after {
234
+ opacity: 1;
235
+ }
236
+
237
+ /* RTL: the logical anchors flip on their own; the gradients flip with them. */
238
+ [dir="rtl"] :where(body > header nav, .mote-nav)::before {
239
+ background: linear-gradient(to right, transparent, color-mix(in oklab, var(--mote-accent) 55%, var(--mote-bg)) 40%);
240
+ }
241
+
242
+ [dir="rtl"] :where(body > header nav, .mote-nav)::after {
243
+ background: linear-gradient(to left, transparent, color-mix(in oklab, var(--mote-accent) 55%, var(--mote-bg)) 40%);
244
+ }
245
+
167
246
  :where(body > header nav, .mote-nav) ul {
168
247
  display: flex;
169
- flex-wrap: wrap;
248
+ flex-wrap: nowrap;
170
249
  gap: 0.25rem;
171
250
  padding: 0;
172
251
  margin: 0;
173
252
  list-style: none;
253
+ overflow-x: auto;
254
+ overflow-y: hidden;
255
+ scrollbar-width: none;
256
+ }
257
+
258
+ :where(body > header nav, .mote-nav) ul::-webkit-scrollbar {
259
+ display: none;
174
260
  }
175
261
 
176
262
  :where(body > header nav, .mote-nav) a {
@@ -703,7 +789,7 @@ nav[aria-label="toc"] :is(h2, h3) {
703
789
  Columns are computed from the container: as many as fit in 16rem. */
704
790
  .mote-grid {
705
791
  display: grid;
706
- gap: 1.25rem;
792
+ gap: var(--mote-grid-gap, 1.25rem);
707
793
  /* Density is a variable, not a class API: --mote-grid-min: 22rem
708
794
  gives 3-wide pricing; 26rem gives 2-column content. */
709
795
  grid-template-columns: repeat(auto-fill, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
@@ -711,5 +797,7 @@ nav[aria-label="toc"] :is(h2, h3) {
711
797
 
712
798
  /* Sparse grids: collapse empty tracks (footers, short card rows) */
713
799
  .mote-grid-sparse {
800
+ display: grid;
801
+ gap: var(--mote-grid-gap, 1.25rem);
714
802
  grid-template-columns: repeat(auto-fit, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
715
803
  }
package/mote.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.css — a mote of styling for plain HTML.
3
- * v0.8.0 · MIT
3
+ * v0.9.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:
@@ -396,6 +396,13 @@ tbody tr:nth-child(even) {
396
396
  background: color-mix(in oklab, var(--mote-surface) 55%, transparent);
397
397
  }
398
398
 
399
+ /* The current row — same aria-current vocabulary as nav pills and
400
+ pagination. Opt-in via the attribute; accent-soft reads over zebra
401
+ because this rule follows it in source order. */
402
+ tbody tr[aria-current] {
403
+ background: var(--mote-accent-soft);
404
+ }
405
+
399
406
  /* ---------------------------------------------------------------------------
400
407
  * 8. Forms & buttons
401
408
  * ------------------------------------------------------------------------- */
@@ -684,8 +691,20 @@ progress, meter {
684
691
  text-align: center;
685
692
  }
686
693
 
687
- /* Stacked forms: stretch fields, keep checkboxes/radios compact.
688
- An opt-in alternative to the page-global width: 100% recipe. */
694
+ /* Stack: a vertical rhythm for forms and lists — gap drives the
695
+ spacing, children's block margins are zeroed, fields stretch full
696
+ width while checkboxes/radios stay at 1em. Works on any content;
697
+ --mote-stack-gap tunes it. */
698
+ .mote-stack {
699
+ display: flex;
700
+ flex-direction: column;
701
+ gap: var(--mote-stack-gap, 1.25rem);
702
+ }
703
+
704
+ .mote-stack > * {
705
+ margin-block: 0;
706
+ }
707
+
689
708
  .mote-stack :is(input, select, textarea, progress, meter) {
690
709
  width: 100%;
691
710
  }
@@ -694,7 +713,7 @@ progress, meter {
694
713
  width: 1em;
695
714
  }
696
715
 
697
- .mote-stack :is(input, select, textarea):where(.mote-sr-only) {
716
+ .mote-stack .mote-sr-only:is(input, select, textarea) {
698
717
  width: 1px;
699
718
  }
700
719
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mote.css",
3
- "version": "0.8.0",
3
+ "version": "0.9.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": [