@usableapp/cardds 0.7.94 → 0.7.96
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 +76 -0
- package/README.md +110 -11
- package/bin/cardds.mjs +1 -1
- package/css/actions.css +9 -12
- package/css/base.css +3 -6
- package/css/card.css +11 -17
- package/css/choice.css +14 -11
- package/css/finish.css +4 -2
- package/css/journey.css +7 -4
- package/css/layover.css +16 -11
- package/css/lists.css +4 -9
- package/css/media.css +20 -16
- package/css/numbers.css +16 -18
- package/css/palette.css +79 -97
- package/css/people.css +5 -4
- package/css/post.css +2 -1
- package/css/sheet.css +13 -10
- package/css/sticky.css +6 -5
- package/css/theme.template.css +16 -7
- package/css/tokens.css +69 -35
- package/dist/actions/Chip.d.ts +1 -1
- package/dist/actions/Chip.js +1 -1
- package/dist/actions/Dropdown.d.ts +1 -1
- package/dist/actions/Dropdown.js +1 -1
- package/dist/cardds.css +280 -260
- package/dist/cards/cardGrounds.d.ts +22 -0
- package/dist/cards/cardGrounds.js +84 -0
- package/dist/cards/inkGrounds.d.ts +34 -0
- package/dist/cards/inkGrounds.js +181 -0
- package/dist/catalog.json +1 -1
- package/dist/choice/Calendar.d.ts +1 -1
- package/dist/choice/Calendar.js +1 -1
- package/dist/choice/Check.d.ts +2 -2
- package/dist/choice/Check.js +2 -2
- package/dist/choice/DayStrip.d.ts +1 -1
- package/dist/choice/DayStrip.js +1 -1
- package/dist/choice/Mood.d.ts +1 -1
- package/dist/choice/Mood.js +1 -1
- package/dist/choice/Slider.d.ts +1 -1
- package/dist/choice/Slider.js +1 -1
- package/dist/forms/AddRow.d.ts +1 -1
- package/dist/forms/AddRow.js +1 -1
- package/dist/forms/Composer.d.ts +1 -1
- package/dist/forms/Composer.js +1 -1
- package/dist/forms/Field.d.ts +4 -2
- package/dist/forms/Field.js +4 -2
- package/dist/forms/FileBtn.d.ts +1 -1
- package/dist/forms/FileBtn.js +1 -1
- package/dist/forms/Pin.d.ts +1 -1
- package/dist/forms/Pin.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/media/MapPick.d.ts +1 -1
- package/dist/media/MapPick.js +1 -1
- package/dist/media/Tile.d.ts +1 -1
- package/dist/media/Tile.js +1 -1
- package/dist/numbers/Picker.d.ts +1 -1
- package/dist/numbers/Picker.js +1 -1
- package/dist/people/AvatarPick.d.ts +2 -2
- package/dist/people/AvatarPick.js +1 -1
- package/lib/guide.mjs +1 -1
- package/lint/rules.mjs +37 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.96 — 2026-10-18
|
|
6
|
+
|
|
7
|
+
From timebank's help sheet (Lh 2026-10-18): "the card's colour is no different from the background at all". A card is one boundary; a boundary the member cannot see is no boundary (WCAG 2.2 SC 1.4.11, 3:1). No prop changes. Two looks change on existing screens: every card in coloured mode and under the `lit` and `sticker` finishes now has a visible edge, and a `Tile stat` takes the ink of the card it stands on.
|
|
8
|
+
|
|
9
|
+
Then the ink (Lh 2026-10-18). On `Tile · StatOnEachTone` at tone 3: "Text on a dark background like this must be light or white". The model, ruled the same day: "there are text colours ONLIGHT and ONDARK. When the ground is light, use onlight; when the ground is dark, use ondark. In future, with a dark mode, text uses ondark too, without changing the way of thinking", and "the onlight and ondark principle applies to other glyphs too". **Ink follows its ground** (README): two ink sets per palette, every ground declares only its side, and every word and glyph on it follows. Measured by the new check on every gallery story × 7 palettes × (as written + tones 1–5): 15,137 pieces under AA at 0a36c7a → 0, and 0 in a dark mode made of sides alone. **A theme that set a per-tone ink token must move to the two sets** (Removed, below). A theme that sets only the five `--color-*` bases (timebank) changes nothing: the sets derive from `--color-ink` and `--color-on-brand`.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`cardGrounds()`: can the member see each card on your theme?** Before → after: nothing measured a card against its ground, and the README said cardds made no contrast check → a function the package exports builds a card of each tone (1–5) on the screen, in a sheet's body and in a `paper` sheet's body, where your theme is loaded, and returns per pair the fill's contrast, the edge's, `pass` (one of them 3:1) and a `problem` that names the tokens and the shortfall. It changes nothing; the theme's values stay yours. Run it on a dev page (`console.table(cardGrounds())`) or in a Playwright test (`await page.evaluate(cardGrounds)`). README "Can the member see the card?".
|
|
13
|
+
- **Story `Tile · StatOnEachTone`**: two stat tiles on each tone.
|
|
14
|
+
- **Two ink sets.** `--ink-onlight` and `--ink-onlight-muted` (words and glyphs on a light ground), `--ink-ondark` and `--ink-ondark-muted` (on a dark one). Each holds 4.5:1 on every ground of its side. Unset, they derive from `--color-ink` and `--color-on-brand`; the wireframe's are #141414 / #575757 and #ffffff / #a8a8a8.
|
|
15
|
+
- **Sides.** `--surface-dark`, `--card-1-dark` … `--card-5-dark`, `--accent-dark`, `--action-dark`, `--neg-dark`: `1` = a dark ground, `0` = a light one. The defaults: the screen, tones 1, 2, 4 and the paper light; tone 3, tone 5, the brand fill and a falling Delta dark. orchard and vers declare their tone 5 light. A surface sets `--dark` from its fill's side; the surface list in tokens.css gives it `--card-ink`, `--card-muted`, `--on-accent`, `--accent-ink` (the brand as a glyph: itself on a light ground, ondark on a dark one) and the four signals.
|
|
16
|
+
- **Signals by side.** `--signal-{ok,warn,error,info}-onlight` (the shade on a light ground, 4.5:1 on every one) and `--signal-…-ondark` (the ondark ink unless a palette gives its own: orchard does). `--signal-ok` etc. are now what a piece reads, derived on each surface.
|
|
17
|
+
- **Dark mode falls out.** Turn the light sides to 1 and darken their fills, in a block of your own (README "Ink follows its ground"); no rule per piece. `tests/ink.spec.js` holds the gallery to that block: 0 short.
|
|
18
|
+
- **`inkGrounds()`: can the member read every word and glyph on your theme?** A function the package exports walks every visible word, field value, `svg.icon` and glyph drawn as a fill (a signal dot, a day's dot, the pager's dots, a check's tick, a switch's knob, the strong calendar mark's ring) inside a root, finds the ground under it (every fill, through every opacity) and returns `{ piece, surface, side, set, kind, ink, ground, ratio, needs, pass, problem }`. `side` is what the ground declares, `set` the ink set the colour is; `problem` names both, e.g. `… on .card.card--3 (light side): #575757 (--card-muted = --ink-onlight-muted) on #141414 (--card-bg) is 2.55:1, words need 4.5:1`. Words 4.5:1; large words, icons and glyphs 3:1. It changes nothing. `console.table(inkGrounds().filter((r) => !r.pass))` or `await page.evaluate(inkGrounds)`. `tests/ink.spec.js` runs it over every story in every stock palette and in dark mode, in `npm run ci`.
|
|
19
|
+
- **Knobs.** `--sticky-dark` (a note's side, 0), `--bubble-meta-icon-ink`, plus the knobs #35 added for muted pieces: `--day-rest-ink`, `--segment-rest-ink`, `--float-bar-item-rest-ink`, `--picker-rest-ink`, `--tile-sub-ink`, `--tile-badge-label-ink`, `--callout-sub-ink`, `--snackbar-caption-ink`, `--modal-drawer-caption-ink`, `--route-via-ink`, `--sheet-step-summary-ink`, `--sticky-heading-caption-ink`, `--calendar-day-mark2-shadow`, `--sheet-stage-foot-bg` (docs/knobs.md).
|
|
20
|
+
|
|
21
|
+
### Removed (before → after, for a theme that set them)
|
|
22
|
+
- `--card-1-ink`, `--card-2-ink`, `--card-4-ink` (and `--color-ink` where it meant them) → `--ink-onlight`: one ink for every light ground.
|
|
23
|
+
- `--card-3-ink`, `--card-5-ink` → `--ink-ondark`.
|
|
24
|
+
- `--card-muted` (on `:root`, the older name), `--card-1-muted`, `--card-2-muted`, `--card-4-muted` → `--ink-onlight-muted`. `--card-muted` stays as what a piece reads on its surface; set on `:root` it no longer retunes anything.
|
|
25
|
+
- `--card-3-muted`, `--card-5-muted` → `--ink-ondark-muted`.
|
|
26
|
+
- A tone whose words were set the other way (orchard's and vers's light tone 5) → `--card-5-dark: 0`.
|
|
27
|
+
- `--on-surface`, `--on-surface-muted`, `--on-accent`, `--action-ink`, `--action-muted` set directly → the screen's, the brand fill's and the paper's side (`--surface-dark`, `--accent-dark`, `--action-dark`). The names stay readable; they now derive.
|
|
28
|
+
- `--signal-ok`, `--signal-warn`, `--signal-error`, `--signal-info` set on `:root` → `--signal-…-onlight` (and `--signal-…-ondark`).
|
|
29
|
+
- `--dim` and `--rest` (#35) → the muted shade of the side; `--faint` → a pager dot at rest is the muted ink, a solid shade.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **A card keeps an edge in coloured mode.** Before → after: `[data-palette]` set `--card-border: transparent`, so a card's only boundary was its fill and a 6–8% shadow: in every stock palette tones 1, 2 and 4 (and 5 in orchard and vers) sat under 3:1 on the screen, and a tone-1 card in a tone-1 sheet was 1:1 → `--card-border` is `--on-surface-muted` (the ground's muted words) under any `data-palette`, a 1.5px line (`--border-w`). Every tone on every ground now holds 3:1 in every stock palette and finish (525 pairs, `tests/card-grounds.spec.js`). The shadow stays. A project that wants its own edge sets `--card-border` in `theme.css` and runs `cardGrounds()`; the old look is `--card-border: transparent`, which the check will name.
|
|
33
|
+
- **The `lit` and `sticker` finishes keep the card's edge.** Before → after: every finish set `--card-border: transparent`; lit's 7% ring and sticker's white cut measured 1.15–1.41:1 on a light ground → only `--action-border` steps back under a finish; `print` and `ink` draw their own line as before.
|
|
34
|
+
- **`Tile stat` takes the ink of what it stands on** (timebank: in `Card tone={3}` the value and icon were rgb(42,36,28) on rgb(92,107,71)). Before → after: the stat tile drops the plain tile's fill (`--action-bg`) but kept its ink (`--action-ink`), dark on a dark card → its ink is inherited, as the card's heading's; `--tile-ink` still overrides. Its sub line is the card's muted words (`--card-muted`, which each tone holds to AA) instead of the full ink at `--dim`, which was 3.3:1 on clay's terracotta.
|
|
35
|
+
- **Every piece reads its ink from its ground's side.** Before → after (since 0a36c7a): `.card--3` set only its ink and the accent pair; a hand list in card.css faded ten named pieces by `opacity: var(--dim)`; six `.card--3 .x` patches in lists.css; per-tone ink tokens chosen one by one → a ground declares its side and the inks follow (the surface list in tokens.css: the screen, every card tone, a sheet, a tile, a chip, a switch, the drawer, a cover, each Postcard face, a note, the post paper, the float bar's pill, the snackbar, a Delta, the track and its steps, a tile badge, an avatar, a bubble, the map's hint). A piece with no fill that stands in for one takes its ground's side (`--dark: inherit`: a stat tile, an upload tile, a plain tile badge, an outline avatar, a light drawer).
|
|
36
|
+
- **A muted word or glyph is a shade, never an opacity.** Unpicked days, segments, picker values, float-bar items, a tile's sub line, captions: the side's muted ink. The pager's dots at rest were 30% of the ink (1.5:1) → the muted ink; the one where you are, the full ink (it was the accent, 1.6:1 on the wireframe's tone 5). A switch's knob at rest was 45% of the ink (2.4:1, and white on the paper switch on a dark card) → the paper side's muted ink.
|
|
37
|
+
- **One muted shade per side.** Wireframe: tone 1's #6b6b6b, tone 2's #5f5f5f, tone 4's #555555 and a note's 68% of the ink → #575757 (4.6:1 on the water note, the darkest light ground); tone 3's #b9b9b9 and tone 5's #c4c4c4 → #a8a8a8 (4.6:1 on tone 5). Each stock palette's sets are in palette.css with the ground that bounds them.
|
|
38
|
+
- **Each tone's words are its side's ink.** orchard's per-tone inks (sand #33291c, sage #2b3322, clay #3a2a1c) → its onlight umber #2a241c; clay's terracotta and mustard muted → #2a2723 (the terracotta is a middle ground: no lighter muted holds 4.5:1 on it). On the brand card the inverted primary button's words are onlight (they were the brand colour).
|
|
39
|
+
- **A chip is a ground of the paper's side.** A signal dot on a chip on a dark card took the card's ondark signal (1:1 on the white chip) → onlight. A picked chip or tile is a ground of the accent's side.
|
|
40
|
+
- **The track** lays down the accent fill of where it stands and is that fill's side; its steps re-point the accent (the `.card--3 .track` rule is gone).
|
|
41
|
+
- **Signals.** On a light ground, the onlight shade: wireframe ok #2b7a2f → #27692a, warn #7f6400 → #705903, error #c62828 → #af2525, info #2f5f9e → #2e5c98 (each 4.5:1 on tone 4's grey; tones 2 and 4 used to take the card's ink); orchard ok #497645 → #3e5a37, warn #8d641b → #6b4e1b, error #b04e39 → #82402f, info #4c6e95 → #40556c; vers ok #477242 → #3b5734, warn #856122 → #654b1e, error #a84a37 → #803c2d, info #49698f → #3c5169; clay ok #205622 → #1b2c18, warn #5d4a00 → #2f280d, error #931e1e → #471a17, info #264c7f → #1d2938 (4.5:1 on the terracotta: nearly the ink). On a dark ground, the ondark ink; orchard's own light shades (#f9ccc0, #f2d28b, #cfe6b8, #c5d7ef, 4.5:1 on the moss) now hold on every dark ground of orchard, not only tone 3.
|
|
42
|
+
- **Stock palette fills**, chosen so each side's ink holds 4.5:1 on the fill, on the fill's own hue (OKLCH, lightness only), and so a tone's ink holds on its 12% tint too (a banner, a step bar, today's day carry words there):
|
|
43
|
+
|
|
44
|
+
| palette | fill | before #35 | #35 | now | its ink | ink on it, before → now | on its 12% tint now |
|
|
45
|
+
|---|---|---|---|---|---|---|---|
|
|
46
|
+
| roast | tone 3 · --accent | #b97e3c | #805729 | #8c5402 | #fdf9f1 | 3.27 → 5.91 | 4.59 |
|
|
47
|
+
| hydro | tone 3 · --accent | #3d63e0 | #385bce | #365bd7 | #ffffff | 5.17 → 5.78 | 4.55 |
|
|
48
|
+
| orchard | tone 3 · --accent | #5c6b47 | #525f3f | #52613d | #f6f1e6 | 5.11 → 5.94 | 4.55 |
|
|
49
|
+
| vers | tone 3 · --accent | #678d58 | #4d6a42 | #496d3a | #ffffff | 3.8 → 5.95 | 4.55 |
|
|
50
|
+
| clay | tone 2 (light) | #e05c3a | #e36d4f | #eb6644 | #191713 | 4.91 → 5.55 | 4.56 |
|
|
51
|
+
| orchard | --highlight-1 | #d93a1f | #d1381e | #d03114 | #f6f1e6 | 4.07 → 4.51 | no words on a tint |
|
|
52
|
+
| orchard | --highlight-4 | #e0457b | #cd3f71 | #ca2e69 | #f6f1e6 | 3.51 → 4.54 | no words on a tint |
|
|
53
|
+
| orchard | --neg | #c4694f | #a25741 | #ad553b | #f6f1e6 | 3.4 → 4.5 | no words on a tint |
|
|
54
|
+
| vers | --neg | #dd9787 | #a84a37 | #a76557 | #ffffff | 2.37 → 4.52 | no words on a tint |
|
|
55
|
+
|
|
56
|
+
hydro's ondark ink #f2f6ff → #ffffff (#35). The highlight and Delta fills carry no tint, so they hold the fill alone. A palette's `--card-border` is still `--on-surface-muted`: now the onlight muted ink, a step darker.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- **`pager-forward-primary` reads a conditional forward slot as one** (reported by timebank 2026-10-18, poc/help-flow phone.tsx). Before → after: `<Btn>Back</Btn>{last ? <Btn primary>Send</Btn> : <Btn primary>When</Btn>}` was flagged, the first branch counted as a back button → the two branches of a conditional are one slot, and both are the forward Btn that must be `primary`.
|
|
60
|
+
|
|
61
|
+
### Known, not changed
|
|
62
|
+
- `inkGrounds()` reads ancestors only: words over a scrim drawn by `::after`, or over a sibling that is positioned under them, are measured against the ancestors' fills. A `url()` picture is skipped; a gradient is read as decoration unless it is one colour.
|
|
63
|
+
- A `Delta` pill (bg `--card-3-bg`) has no visible boundary on the dark card (its words pass); a mark, not words, so the ink check does not read it.
|
|
64
|
+
- A calendar `mark` is a 12% accent tint: the day's number reads on it, but the mark itself is not a 3:1 glyph (`mark2` adds a 3:1 ring). Decoration colours (`--dot-a`, `--dot-b`, the highlights) are not inks and are not measured: a brand-coloured legend dot vanishes on the brand card.
|
|
65
|
+
- The wireframe's card edge (#141414) and its brand fill are near-black: in a dark mode they need their own colours to keep a card's boundary (cardGrounds()); the ink holds without them.
|
|
66
|
+
|
|
67
|
+
## 0.7.95 — 2026-10-18
|
|
68
|
+
|
|
69
|
+
From timebank's help-ask prototype (Lh 2026-10-18): its Composer, Dropdown and map stood on white cards inside the help sheet. Ruled: the sheet is the paper the member fills in.
|
|
70
|
+
|
|
71
|
+
Nothing in the package's look or behaviour changes: no component, class or knob is added or removed. Two new checks may fail a project's `cardds check`: a control in a card, and a worded pager whose forward button is not the primary one.
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
- **`cardds check` rule `field-in-card` (error): the sheet is the paper the member fills in** (Lh 2026-10-18). Before → after: "Fields belong in a Sheet" was prose in `Field`'s doc, and nothing stopped a form on a card → a control the member fills in or picks, written inside a `<Card>`, is an error that names `Sheet`'s `SheetBody`, `SheetStep` and the stack sheet as its place. The controls: `Field`, `Composer`, `ComposerArea`, `Pin`, `Dropdown`, `Slider`, `Toggle`, `Check`, `ChipPick`, `Calendar`, `DayStrip`, `Day`, `Picker`, `Mood`, `AvatarPick`, `FileBtn`, `AddRow`, `MapPick`, a `Tile pick` and a `Tile upload`. A plain or `stat` Tile, a `Btn`, `IconBtn` or `Link`, and a raw `<input>` (the pickable row card's radio) are not flagged. A `Card` that is a sheet of a `SheetStack` is that stack's sheet and may hold them. To pass: move the controls onto the sheet's body (`<Sheet><CardHead …/><SheetBody>…controls…</SheetBody><CardFoot>…</CardFoot></Sheet>`), with no card round them. The ESLint plugin reports it under `cardds/check`.
|
|
75
|
+
- **`cardds check` rule `pager-forward-primary` (error).** Before → after: a worded pager could carry back and next with the same signal (Lh saw it on a timebank screen) → in an `ActionBar variant="pager"` or an `ActionBarTier pager` with two or more worded `Btn`s, the last (forward) one must be `primary` and the others not. The round pager (`IconBtn`s) is not checked.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
- **Every control's doc says where it lives: on a sheet, never in a Card.** `Field`'s doc comment now leads with it, so `cardds get Field` and `cardds find "where does a form field go"` answer with the rule. README's sheet rules gain "The sheet is the paper the member fills in".
|
|
79
|
+
- **The gallery's stories put their controls on sheets.** 60 stories in 19 files moved from a card onto a sheet: a `Step` with a `Page` and a `Sheet` where the story is a step of a flow, a bare `SheetStage` with a `Sheet` for a part shown on its own. Story ids are unchanged. Two samples that stood in two cards of one story now stand on one sheet.
|
|
80
|
+
|
|
5
81
|
## 0.7.94 — 2026-10-18
|
|
6
82
|
|
|
7
83
|
From timebank's help-ask sheet (Lh 2026-10-17). One look changes on existing screens: a row card's corner (41.6 → 13). Everything else is added, and a screen that does not use it is as it was.
|
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
> The other docs only point here: `CLAUDE.md` (AI working IN this repo — decisions and gotchas),
|
|
10
10
|
> `docs/guides/building-with-cardds.md` (AI composing screens in React; shipped to Claude Design),
|
|
11
11
|
> the `/cardds` skill (AI in another project — install + contract). To LOOK at the system: `npm run dev`.
|
|
12
|
+
>
|
|
13
|
+
> **Accessibility:** [Ink follows its ground](#ink-follows-its-ground). Two ink sets, onlight and ondark; every ground declares
|
|
14
|
+
> only its side, and every word and glyph on it follows. `inkGrounds()` checks your theme.
|
|
12
15
|
|
|
13
16
|
Card-first mobile design system, **React-first**: `src/` is the component
|
|
14
17
|
library — one thin component per pattern, emitting exactly the markup the CSS
|
|
@@ -555,8 +558,21 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
555
558
|
- **A bottom sheet holds what the member acts on** — controls, or small
|
|
556
559
|
content cards to pick from or read more — and it opens to the full height
|
|
557
560
|
of its box. **Cards are content the system offers**: the words, the
|
|
558
|
-
explanation, the thing to read. Do not put
|
|
559
|
-
|
|
561
|
+
explanation, the thing to read. Do not put the step's explanation in a
|
|
562
|
+
sheet.
|
|
563
|
+
- **The sheet is the paper the member fills in** (Lh 2026-10-18). Every
|
|
564
|
+
control the member fills in or picks stands on a sheet: a `Sheet`'s
|
|
565
|
+
`SheetBody`, a `SheetStep`, or a stack sheet. That is `Field`, `Composer`
|
|
566
|
+
and `ComposerArea`, `Pin`, `Dropdown`, `Slider`, `Toggle`, `Check`,
|
|
567
|
+
`ChipPick`, `Calendar`, `DayStrip` and `Day`, `Picker`, `Mood`,
|
|
568
|
+
`AvatarPick`, `FileBtn`, `AddRow`, `MapPick`, a `Tile pick` and a
|
|
569
|
+
`Tile upload`. A card is for reading and pressing buttons only; never put
|
|
570
|
+
a form, or one control of it, in a card. `cardds check` rule
|
|
571
|
+
`field-in-card` (error) flags a control written inside a `<Card>`. A `Card`
|
|
572
|
+
that is a sheet of a `SheetStack` is that stack's sheet, so it may hold
|
|
573
|
+
them. Seen in timebank's help-ask prototype: a Composer, a Dropdown and a
|
|
574
|
+
map on white cards inside the help sheet, the cards the colour of the
|
|
575
|
+
ground.
|
|
560
576
|
- **Two sheet systems, one material.** *Sheet single* (`.sheet`, `sheet.css`)
|
|
561
577
|
is ONE sheet the member acts in, whatever it holds: a short entry in front
|
|
562
578
|
of a step's card, a place on a map, a payment on a dim page — in a `.step`
|
|
@@ -916,7 +932,7 @@ That file is where a project's look lives. Bases and roles:
|
|
|
916
932
|
| the size **of** things | `--tap-base` (3rem = 48) | `--tap*`, `--btn-h`, `--chip-h`, `--bar-h`, `--header-h`, `--fab`, `--icon*`, `--avatar*`, `--dot*`, `--ring*`, `--tile-*`. `--tap*` never under `--tap-floor` (44px) |
|
|
917
933
|
| faces | `--font-ui`, `--font-display` | |
|
|
918
934
|
| colour | `--color-surface`, `--color-ink`, `--color-paper`, `--color-brand`, `--color-on-brand` — no default: unset = the wireframe | every role: surface / words, the three card tones (paper · the brand washed 14% into the surface · the brand), the muted inks (the ink washed into their ground), accent, neutral actions, `--dot-a` |
|
|
919
|
-
| colour roles (the escape hatch) |
|
|
935
|
+
| colour roles (the escape hatch) | the ink sets `--ink-{onlight,ondark}(-muted)`; the sides `--surface-dark`, `--card-{1,2,3,4,5}-dark`, `--accent-dark`, `--action-dark`, `--neg-dark`; `--surface`, `--card-{1,2,3,4,5}-bg`, `--card-border`, `--accent`, `--action-{bg,border}`, `--pos`, `--neg`, `--dot-{a,b}`, `--signal-{ok,warn,error,info}-{onlight,ondark}` | a role set directly wins over its derivation; the tints derive from the roles |
|
|
920
936
|
|
|
921
937
|
Rules:
|
|
922
938
|
|
|
@@ -936,9 +952,13 @@ Rules:
|
|
|
936
952
|
trades the wireframe's borders for the card's shadow and the controls' soft
|
|
937
953
|
edge (`palette.css`, `forms.css`). Colours of your own → `<html data-palette="own">`.
|
|
938
954
|
Without it the colours still apply, on wireframe borders.
|
|
939
|
-
- **cardds does not
|
|
940
|
-
|
|
941
|
-
|
|
955
|
+
- **cardds does not pick the colours; it measures two things about them.** A theme's
|
|
956
|
+
values are the project's call and the derivations are only starting points; any
|
|
957
|
+
role can be set outright. What cardds checks is that the member can see each card
|
|
958
|
+
([`cardGrounds()`](#can-the-member-see-the-card) names a tone whose fill and edge both
|
|
959
|
+
sit under 3:1 on its ground) and read what stands on it
|
|
960
|
+
([`inkGrounds()`](#ink-follows-its-ground) names a word or icon under 4.5:1 or 3:1).
|
|
961
|
+
Both name the tokens to move.
|
|
942
962
|
- The member's text-size setting is the same `--fs-base`, written on `<html>`
|
|
943
963
|
at run time by `setTextScale(percent)` (a share of the theme's base; 100 = the
|
|
944
964
|
theme's own): the words grow, and the things grow HALF as much (`--tap-base`; `controlsFor(percent)` — 130% text → 115% controls, 160% → 130%), so a larger text still fits what holds it. (`--text-scale`, which multiplied the root and zoomed the UI too, is gone.)
|
|
@@ -949,16 +969,95 @@ palette `own` shows a colour well per colour base.
|
|
|
949
969
|
template to the defaults; the step / centre geometry runs at two extreme
|
|
950
970
|
themes (`tight`, `loose`).
|
|
951
971
|
|
|
972
|
+
## Ink follows its ground
|
|
973
|
+
|
|
974
|
+
The accessibility rule for colour (Lh 2026-10-18): "there are text colours ONLIGHT and ONDARK. When the ground is light, use
|
|
975
|
+
onlight; when the ground is dark, use ondark." It holds for words and for glyphs: icons, marks and dots that carry meaning, a
|
|
976
|
+
check's tick, a switch's knob, the pager's dots.
|
|
977
|
+
|
|
978
|
+
1. **Two ink sets.** A palette (or your theme) sets `--ink-onlight` and `--ink-onlight-muted`, `--ink-ondark` and
|
|
979
|
+
`--ink-ondark-muted`. Each holds 4.5:1 on every ground of its side.
|
|
980
|
+
2. **Every ground says only its side.** `--surface-dark`, `--card-1-dark` … `--card-5-dark`, `--accent-dark` (the brand fill),
|
|
981
|
+
`--action-dark` (the paper of buttons, chips and fields), `--neg-dark`: `1` is dark, `0` light. A surface turns its side into
|
|
982
|
+
the inks every piece on it reads: `--card-ink`, `--card-muted`, `--on-accent`, `--accent-ink` and the four signals.
|
|
983
|
+
3. **A piece reads its ink only from the ground it stands on** (`currentColor`, `--card-ink`, `--card-muted`). It never names a
|
|
984
|
+
fixed colour, and a muted word or glyph is a shade, never an opacity.
|
|
985
|
+
4. **4.5:1 for words, 3:1 for large words (24px, or 18.66px bold), icons and glyphs** (WCAG 2.2 SC 1.4.3, 1.4.11), on the ground
|
|
986
|
+
the piece is really drawn on. A tint that carries words (a banner, a step bar, today's day) is a ground of its card's side, so
|
|
987
|
+
a dark fill holds its ondark ink on its 12% tint too.
|
|
988
|
+
5. **Signals** have an onlight and an ondark shade (`--signal-ok-onlight`, `--signal-ok-ondark`, …): no one shade of a hue holds
|
|
989
|
+
4.5:1 on both. The ondark one is the ondark ink unless a palette gives its own. The icon and the words carry the meaning.
|
|
990
|
+
|
|
991
|
+
**Dark mode** is the sides turned to `1` with their fills darkened, and no rule per piece:
|
|
992
|
+
|
|
993
|
+
```css
|
|
994
|
+
:root[data-mode="dark"] {
|
|
995
|
+
--surface-dark: 1; --card-1-dark: 1; --card-2-dark: 1; --card-4-dark: 1; --action-dark: 1;
|
|
996
|
+
--surface: #121212; --color-paper: #1e1e1e; --card-1-bg: #1e1e1e; --card-2-bg: #2a2a2a; --card-4-bg: #333333; --action-bg: #262626;
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
`tests/ink.spec.js` holds the gallery to exactly that block: every story, every tone, 0 pieces short.
|
|
1001
|
+
|
|
1002
|
+
Check your own theme where it is loaded. `inkGrounds()` walks every visible word, field value, icon and glyph inside a root and
|
|
1003
|
+
returns one row per piece, with the side its ground declares and the ink set its colour is:
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
import { inkGrounds } from '@usableapp/cardds';
|
|
1007
|
+
console.table(inkGrounds().filter((r) => !r.pass)); // a dev page
|
|
1008
|
+
|
|
1009
|
+
// a Playwright test on your app (Playwright sends the function to the page)
|
|
1010
|
+
const short = (await page.evaluate(inkGrounds)).filter((r) => !r.pass).map((r) => r.problem);
|
|
1011
|
+
expect(short).toEqual([]);
|
|
1012
|
+
// '.t-caption.t-muted "since May" on .card.card--3 (light side): #575757 (--card-muted = --ink-onlight-muted) on #141414 (--card-bg) is 2.55:1, words need 4.5:1'
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
A side and an ink set that disagree with the ground (`light side` on a dark fill) say the ground declared the wrong side. It
|
|
1016
|
+
reports and changes nothing. It does not measure a piece on a picture (a `url()` ground; a gradient counts as decoration unless it
|
|
1017
|
+
is one colour), a disabled control, a hidden one, words drawn by CSS `content`, decoration colours (`--dot-a`, `--dot-b`, the
|
|
1018
|
+
highlights), or what lies under a positioned element that is not its ancestor (a scrim drawn by `::after`). cardds holds itself to
|
|
1019
|
+
the same function: `tests/ink.spec.js` renders every gallery story in every stock palette and in the dark mode above, as written
|
|
1020
|
+
and with every card turned to each tone 1–5, about 27,000 pieces a pass, in `npm run ci`.
|
|
1021
|
+
|
|
1022
|
+
## Can the member see the card?
|
|
1023
|
+
|
|
1024
|
+
A card is one boundary, and a boundary the member cannot see is no boundary (WCAG 2.2 SC 1.4.11, non-text contrast: 3:1 against what is
|
|
1025
|
+
next to it). A card's boundary is its fill against the ground it stands on, or its edge (`--card-border`, or a finish's line).
|
|
1026
|
+
In coloured mode the edge is `--on-surface-muted`, the ground's muted words, which a palette holds to AA on its surface; the wireframe's
|
|
1027
|
+
is its black line. A light card on a light ground cannot reach 3:1 by its fill (a tone-1 card in a tone-1 sheet is 1:1), so the edge
|
|
1028
|
+
carries it.
|
|
1029
|
+
|
|
1030
|
+
Your theme decides the colours, so run the check on your theme. `cardGrounds()` builds a card of each tone (1–5) on each ground a card
|
|
1031
|
+
stands on (the screen, a sheet's body, a `paper` sheet's body) where your theme is loaded, reads what the browser draws and returns one
|
|
1032
|
+
row per pair:
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
import { cardGrounds } from '@usableapp/cardds';
|
|
1036
|
+
console.table(cardGrounds()); // a dev page: ground · tone · fill · edge · pass · problem
|
|
1037
|
+
|
|
1038
|
+
// a Playwright test on your app (Playwright sends the function to the page)
|
|
1039
|
+
const rows = await page.evaluate(cardGrounds);
|
|
1040
|
+
expect(rows.filter((r) => !r.pass).map((r) => r.problem)).toEqual([]);
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
A failing row names the tokens and the shortfall, for example
|
|
1044
|
+
`tone 1 card on the screen: fill 1:1 (--card-1-bg #ffffff on --surface #ffffff), edge 1:1 (--card-border); one of them needs 3:1 — the fill is short by 2, or give --card-border a colour of 3:1 on #ffffff`.
|
|
1045
|
+
It reports and changes nothing: which knob you move is your call. It runs in a browser on purpose: your theme's `var()` aliases,
|
|
1046
|
+
`[data-palette]` blocks, finish and `color-mix()` are resolved by the same engine that draws the screen. A stock palette is held to the
|
|
1047
|
+
same check in every finish (`tests/card-grounds.spec.js`).
|
|
1048
|
+
|
|
952
1049
|
## New palette
|
|
953
1050
|
|
|
954
1051
|
Copy any block in `palette.css`, rename, retune:
|
|
955
1052
|
|
|
956
1053
|
```css
|
|
957
1054
|
[data-palette="mine"] {
|
|
958
|
-
--surface: …;
|
|
959
|
-
--
|
|
960
|
-
--
|
|
961
|
-
--
|
|
1055
|
+
--surface: …;
|
|
1056
|
+
--ink-onlight: …; --ink-onlight-muted: …; /* words and glyphs on a light ground */
|
|
1057
|
+
--ink-ondark: …; --ink-ondark-muted: …; /* on a dark ground */
|
|
1058
|
+
--card-1-bg: …; /* + card-2 … card-5; a tone whose side is not the default's says so: --card-5-dark: 0; */
|
|
1059
|
+
--accent: …;
|
|
1060
|
+
--action-bg: …;
|
|
962
1061
|
}
|
|
963
1062
|
```
|
|
964
1063
|
|
|
@@ -979,7 +1078,7 @@ Four values, one attribute (`css/finish.css`; the header names each and its knob
|
|
|
979
1078
|
| `sticker` | a die-cut sticker: a white cut line, a short hard drop, a lone card leans (never one in a stack, a list or a feed), buttons with a bottom edge that press down | `--sticker-cut` (5px) · `--sticker-tilt` (0.8deg; `0deg` = straight) |
|
|
980
1079
|
| `ink` | a poster / comic print: a thick line in the surface's ink round every boundary and control, a hard offset in the same ink | `--ink-w` (2.5px) · `--ink-off` (5px) |
|
|
981
1080
|
|
|
982
|
-
Under every finish the
|
|
1081
|
+
Under every finish the controls' wireframe border steps back (`--action-border`; ink puts its own line there) and the card keeps its edge (`--card-border`: lit's ring and sticker's cut do not reach 3:1 on a light ground, see [Can the member see the card?](#can-the-member-see-the-card)), a card in a closed stack's base stays flat, and the information inside a card stays flat — only a container or a control gets an edge. Colours are mixes of the roles, so a finish sits in any palette. The gallery's nav has a finish switch (key `f`); `Card · Finishes` shows the four side by side, `Home · Finish` a home under `print`.
|
|
983
1082
|
|
|
984
1083
|
## Scale: everything is rem
|
|
985
1084
|
|
package/bin/cardds.mjs
CHANGED
|
@@ -84,7 +84,7 @@ import them, never copy their markup: the behaviour lives in the component. Gall
|
|
|
84
84
|
|
|
85
85
|
// the exports beside the components that are not components (docs/roles.md rows `a function, not a component`…): shape `—` and nothing to tell them from a structural part, so they are named.
|
|
86
86
|
// A new one joins here and in the list test of tests/catalog.spec.js, or it is listed as structure
|
|
87
|
-
const HELPERS = new Set(['applyMask', 'slideTo', 'slideScreen', 'installScreenSlide', 'useLayer', 'useLayers', 'useOptionalLayer', 'registerIcons', 'LayerHistory', 'setWords', 'word', 'setTextScale', 'controlsFor']);
|
|
87
|
+
const HELPERS = new Set(['applyMask', 'cardGrounds', 'inkGrounds', 'slideTo', 'slideScreen', 'installScreenSlide', 'useLayer', 'useLayers', 'useOptionalLayer', 'registerIcons', 'LayerHistory', 'setWords', 'word', 'setTextScale', 'controlsFor']);
|
|
88
88
|
const one = (text) => text.replace(/\s+/g, ' ').trim();
|
|
89
89
|
const indent = (text, by = ' ') => text.replace(/^(?=.)/gm, by);
|
|
90
90
|
|
package/css/actions.css
CHANGED
|
@@ -178,7 +178,8 @@
|
|
|
178
178
|
toggles, tiles and the dropdown already use. */
|
|
179
179
|
border: var(--border-w) solid var(--chip-border, var(--outline));
|
|
180
180
|
background: var(--chip-bg, var(--action-bg));
|
|
181
|
-
|
|
181
|
+
--dark: var(--action-dark); /* a chip is a surface of the paper's side: a signal dot on it reads onlight, on any card */
|
|
182
|
+
color: var(--chip-ink, var(--card-ink));
|
|
182
183
|
padding: var(--chip-pad-y, var(--sp-2)) var(--chip-pad-x, var(--sp-3));
|
|
183
184
|
font: var(--type-label);
|
|
184
185
|
font-size: var(--chip-font-size, var(--fs-sm));
|
|
@@ -197,10 +198,9 @@
|
|
|
197
198
|
.dot--info { background: var(--signal-info); }
|
|
198
199
|
/* --sm: a small LABEL (a name on a note, a tag in a tight head) — low and tight; never a tap target */
|
|
199
200
|
.chip--sm { min-height: 0; gap: var(--chip-sm-gap, var(--sp-1)); padding: var(--chip-sm-pad-y, var(--sp-0)) var(--chip-sm-pad-x, var(--sp-2)); }
|
|
200
|
-
.chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--
|
|
201
|
+
.chip--on { background: var(--chip-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--chip-on-ink, var(--card-ink)); border-color: transparent; }
|
|
201
202
|
/* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (README 13) */
|
|
202
|
-
.chip--muted { color: var(--chip-muted-ink, var(--card-muted)); }
|
|
203
|
-
/* dark-slot inversion lives in card.css (.card--3 :is(...)) */
|
|
203
|
+
.chip--muted { color: var(--chip-muted-ink, var(--card-muted)); } /* the chip's own fill's muted words: a muted chip on the dark card is still a light chip */
|
|
204
204
|
|
|
205
205
|
.chip-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
|
|
206
206
|
.chip-row.bleed { flex-wrap: nowrap; } /* actions.css loads after card.css — the strip stays one line */
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
text-align: left;
|
|
231
231
|
}
|
|
232
232
|
/* muted: the value is still the default (README 13) */
|
|
233
|
-
.dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--
|
|
233
|
+
.dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--action-muted)); }
|
|
234
234
|
.dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
235
235
|
.dropdown__btn > .icon { transition: rotate var(--motion-quick) ease; }
|
|
236
236
|
.dropdown:has(.menu:popover-open) .dropdown__btn > .icon { rotate: 180deg; }
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
/* ---- segment (day picker / tab strip) — items share width, shrink on small screens ---- */
|
|
243
243
|
.segment { display: flex; gap: var(--sp-1); }
|
|
244
244
|
.segment__item {
|
|
245
|
-
|
|
245
|
+
color: var(--segment-rest-ink, var(--card-muted)); /* unpicked: the ground's muted words */
|
|
246
246
|
border-radius: var(--r-chip);
|
|
247
247
|
padding: var(--sp-2) var(--sp-1);
|
|
248
248
|
font: var(--type-label);
|
|
@@ -254,7 +254,6 @@
|
|
|
254
254
|
.segment__item[aria-selected="true"] {
|
|
255
255
|
background: var(--accent);
|
|
256
256
|
color: var(--on-accent);
|
|
257
|
-
opacity: 1;
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
/* ---- bottom CTA pack: the screen's bottom (Screen `bottom`) ----
|
|
@@ -325,10 +324,9 @@
|
|
|
325
324
|
width: var(--dot);
|
|
326
325
|
height: var(--dot);
|
|
327
326
|
border-radius: 50%;
|
|
328
|
-
background:
|
|
329
|
-
opacity: var(--faint);
|
|
327
|
+
background: var(--card-muted); /* a glyph: the dots at rest are the ground's muted ink (an opacity of 0.3 was 1.5:1), the one where you are its full ink */
|
|
330
328
|
}
|
|
331
|
-
.pager__at > .is-at { background: var(--
|
|
329
|
+
.pager__at > .is-at { background: var(--card-ink); }
|
|
332
330
|
/* ---- tiers: a control row above the buttons ----
|
|
333
331
|
A screen whose one move comes with a control — the introduction's text-size
|
|
334
332
|
slider above its pager. Each tier is a row; the bar stacks them, and any
|
|
@@ -378,11 +376,10 @@
|
|
|
378
376
|
gap: var(--action-note-gap, var(--sp-1));
|
|
379
377
|
padding-inline: var(--sp-2);
|
|
380
378
|
font: var(--action-note-font, var(--type-body));
|
|
381
|
-
color: var(--action-note-ink, var(--
|
|
379
|
+
color: var(--action-note-ink, var(--card-muted));
|
|
382
380
|
text-align: center;
|
|
383
381
|
cursor: default;
|
|
384
382
|
}
|
|
385
|
-
.card .action-note { color: var(--action-note-ink, var(--card-muted)); }
|
|
386
383
|
.action-note__words { display: block; } /* ONE run of words: an icon inside sits in the line, the spaces around it kept */
|
|
387
384
|
.action-note__words > .icon { display: inline-block; vertical-align: -0.2em; width: 1.25em; height: 1.25em; margin-inline: 0.15em; color: var(--action-note-icon-ink, inherit); } /* a project may colour the icon apart from the words — e.g. the system blue of the control it points at (Lh 2026-09-24) */
|
|
388
385
|
|
package/css/base.css
CHANGED
|
@@ -27,7 +27,7 @@ html { font-size: 100%; }
|
|
|
27
27
|
body {
|
|
28
28
|
margin: 0;
|
|
29
29
|
background: var(--surface);
|
|
30
|
-
color: var(--
|
|
30
|
+
color: var(--card-ink);
|
|
31
31
|
font: var(--type-body);
|
|
32
32
|
-webkit-font-smoothing: antialiased;
|
|
33
33
|
}
|
|
@@ -103,7 +103,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
103
103
|
.screen > * { min-width: 0; } /* content never widens the column past the viewport */
|
|
104
104
|
/* a screen that carries its own palette (data-palette on the element, not on <html>)
|
|
105
105
|
paints its own surface, so a design tool can show one screen per palette */
|
|
106
|
-
.screen[data-palette] { background: var(--surface); color: var(--
|
|
106
|
+
.screen[data-palette] { background: var(--surface); color: var(--card-ink); }
|
|
107
107
|
|
|
108
108
|
/* app screen: exactly one phone height — a height, not a minimum, so that a
|
|
109
109
|
.step (step.css) has a bound to fill and its card and sheet scroll INSIDE
|
|
@@ -140,9 +140,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
140
140
|
.t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
|
|
141
141
|
.t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
|
|
142
142
|
|
|
143
|
-
.t-muted { color: var(--
|
|
144
|
-
.card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
|
|
145
|
-
/* .card--3 .t-muted (dim instead of --card-muted on the dark slot) is declared once in card.css, with the rest of that family */
|
|
143
|
+
.t-muted { color: var(--card-muted); } /* the muted words of whatever it stands on: the screen, a card of any tone, a drawer */
|
|
146
144
|
.t-caps { text-transform: uppercase; }
|
|
147
145
|
/* ---- mark: a highlighter stroke over a word INSIDE running text (a sender's name in a greeting) ----
|
|
148
146
|
<p class="t-body"><mark class="mark">ธนาคารเวลา</mark> ส่งแสตมป์ดวงแรกให้คุณ</p>
|
|
@@ -211,4 +209,3 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
211
209
|
.empty-state__action { margin-top: var(--sp-2); }
|
|
212
210
|
/* inside a card (a card's own "nothing yet"): the card's ink */
|
|
213
211
|
.card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
|
|
214
|
-
.card--3 .empty-state { color: inherit; }
|
package/css/card.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
.card {
|
|
8
8
|
--card-bg: var(--card-1-bg); /* the slot, readable by children (avatar rings, fold) */
|
|
9
|
-
--
|
|
9
|
+
--dark: var(--card-1-dark); /* its side: the inks follow (tokens.css) */
|
|
10
10
|
--card-pad: var(--card-pad-rest); /* published so .bleed can cancel it exactly */
|
|
11
11
|
--card-gap: var(--card-gap-rest); /* published so a sheet's peek strip (sheet.css) is exact */
|
|
12
12
|
background: var(--card-bg);
|
|
@@ -34,19 +34,13 @@
|
|
|
34
34
|
.card--away { translate: 0 var(--_y); }
|
|
35
35
|
@media (prefers-reduced-motion: reduce) { :is(.card--leaver, .post-desk__card) { transition: none; } }
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
check, toggle, ring, day…) inverts without per-component rules */
|
|
41
|
-
.card--3 { --card-bg: var(--card-3-bg); --
|
|
42
|
-
.card--4 { --card-bg: var(--card-4-bg); --
|
|
43
|
-
.card--5 { --card-bg: var(--card-5-bg); --
|
|
44
|
-
/* the dark slot's captions/hints/footnotes: --card-muted is tuned for a light card, so these dim the full ink
|
|
45
|
-
instead (one rule, matching specificity to what it replaces — .card--3 <space> <one class>) — merged from
|
|
46
|
-
base.css/choice.css/media.css/numbers.css/journey.css/people.css (2026-09-27 review). lists.css's .legend
|
|
47
|
-
differs (opacity 0.8, not --dim) and stays there. */
|
|
48
|
-
.card--3 :is(.t-muted, .calendar__wd, .composer__hint, .quote__by, .ring__label, .band__foot, .picker__unit,
|
|
49
|
-
.route__place, .note-row, .avatar-pick__role) { color: inherit; opacity: var(--dim); }
|
|
37
|
+
/* each tone is a fill and a side; its words and glyphs follow from the side (tokens.css) */
|
|
38
|
+
.card--2 { --card-bg: var(--card-2-bg); --dark: var(--card-2-dark); }
|
|
39
|
+
/* the brand slot: accent-toned controls would vanish on it — the slot re-points the accent to its own ink, and that fill's side is
|
|
40
|
+
the other one, so every accent consumer (btn--primary, chip--on, check, toggle, ring, day…) inverts without per-component rules */
|
|
41
|
+
.card--3 { --card-bg: var(--card-3-bg); --dark: var(--card-3-dark); --accent: var(--card-ink); --accent-dark: calc(1 - var(--dark)); }
|
|
42
|
+
.card--4 { --card-bg: var(--card-4-bg); --dark: var(--card-4-dark); }
|
|
43
|
+
.card--5 { --card-bg: var(--card-5-bg); --dark: var(--card-5-dark); }
|
|
50
44
|
|
|
51
45
|
/* header row: title left, floating action right — centered, since the
|
|
52
46
|
trailing control (chip/badge/icon-btn/link) is almost always taller than
|
|
@@ -317,7 +311,6 @@
|
|
|
317
311
|
display: grid;
|
|
318
312
|
place-items: center;
|
|
319
313
|
font: var(--card-thumb-font, var(--type-label));
|
|
320
|
-
opacity: 0.85;
|
|
321
314
|
}
|
|
322
315
|
|
|
323
316
|
/* ---- card feed: a queue of cards, all in sight — one big, the rest small (Lh 2026-10-09, the whole queue 2026-10-15) ----
|
|
@@ -364,9 +357,10 @@
|
|
|
364
357
|
padding: var(--delta-pad-y, var(--sp-1)) var(--delta-pad-x, var(--sp-2));
|
|
365
358
|
border-radius: var(--delta-radius, var(--r-chip));
|
|
366
359
|
background: var(--delta-bg, var(--card-3-bg));
|
|
367
|
-
|
|
360
|
+
--dark: var(--card-3-dark);
|
|
361
|
+
color: var(--delta-ink, var(--card-ink));
|
|
368
362
|
}
|
|
369
|
-
.delta--neg { background: var(--delta-neg-bg, var(--neg)); color: var(--delta-neg-ink, var(--card-
|
|
363
|
+
.delta--neg { background: var(--delta-neg-bg, var(--neg)); --dark: var(--neg-dark); color: var(--delta-neg-ink, var(--card-ink)); }
|
|
370
364
|
|
|
371
365
|
/* ---- panes: a few panes side by side inside a card or a sheet, one in sight (Lh 2026-10-02) ----
|
|
372
366
|
<div class="panes" role="group"><div class="panes__rail"><div class="panes__pane">…</div>…</div><span class="pager__at">…</span></div>
|
package/css/choice.css
CHANGED
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
border-radius: var(--toggle-radius, var(--r-chip));
|
|
50
50
|
border: var(--border-w) solid var(--toggle-border, var(--outline));
|
|
51
51
|
background: var(--toggle-bg, var(--action-bg));
|
|
52
|
+
--dark: var(--action-dark); /* the paper's side: its knob at rest is the paper's muted ink, on the accent the accent's */
|
|
53
|
+
color: var(--card-ink);
|
|
52
54
|
position: relative;
|
|
53
55
|
transition: background-color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
|
|
54
56
|
}
|
|
@@ -60,12 +62,11 @@
|
|
|
60
62
|
width: calc(var(--_h) - 2 * var(--_pad) - 2 * var(--border-w));
|
|
61
63
|
height: calc(var(--_h) - 2 * var(--_pad) - 2 * var(--border-w));
|
|
62
64
|
border-radius: 50%;
|
|
63
|
-
background:
|
|
64
|
-
|
|
65
|
-
transition: translate var(--motion-quick) ease, opacity var(--motion-quick) ease;
|
|
65
|
+
background: var(--card-muted); /* a glyph: a shade of its side, never an opacity (0.45 of the ink was 2.4:1 on the paper) */
|
|
66
|
+
transition: translate var(--motion-quick) ease;
|
|
66
67
|
}
|
|
67
68
|
.toggle:checked { background: var(--accent); border-color: transparent; }
|
|
68
|
-
.toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent);
|
|
69
|
+
.toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); }
|
|
69
70
|
|
|
70
71
|
/* ---- chip pick: a chip that is a choice — a native radio/checkbox inside the label, the picked one filled with accent.
|
|
71
72
|
Full-width in a .chip-grid (one slot / many options), or hugging its word on a .chip-row with --sign (an interest
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
a chip, a mood dot, a tile (media.css), a sticky's tone swatch (sticky.css), a place on a map (sheet.css), a card that is a label round a radio
|
|
88
89
|
(a pickable row card: the app gives the picked one its `tone`) — the native input lies over its whole box, unseen */
|
|
89
90
|
:is(.chip--pick, .mood__opt, .tile--pick, .sticky-pick__swatch, .map-pick) > input, label.card > input[type="radio"] { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
|
|
90
|
-
:is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); color: var(--
|
|
91
|
+
:is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
|
|
91
92
|
/* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
|
|
92
93
|
.chip--sign { padding-right: var(--sp-2); }
|
|
93
94
|
.chip--sign::after {
|
|
@@ -121,13 +122,13 @@
|
|
|
121
122
|
is the same shape on every day */
|
|
122
123
|
padding-inline: 0;
|
|
123
124
|
border-radius: var(--day-radius, var(--r-chip));
|
|
124
|
-
|
|
125
|
+
color: var(--day-rest-ink, var(--card-muted)); /* an unpicked day at rest: the muted words of the ground it stands on (an opacity faded it under AA) */
|
|
125
126
|
}
|
|
126
127
|
.day__name { font: var(--day-name-font, var(--type-caption)); }
|
|
127
128
|
.day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
128
129
|
/* today: a light tone, no louder than a rest; the selected pill below wins when it is both (it comes after and weighs more) */
|
|
129
|
-
.day--today { background: var(--day-today-bg, var(--ink-tint)); color: var(--day-today-ink,
|
|
130
|
-
.day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent));
|
|
130
|
+
.day--today { background: var(--day-today-bg, var(--ink-tint)); color: var(--day-today-ink, var(--card-ink)); }
|
|
131
|
+
.day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); }
|
|
131
132
|
.day__dot { grid-row: 3; align-self: center; width: var(--day-dot-size, var(--dot-xs)); height: var(--day-dot-size, var(--dot-xs)); border-radius: 50%; background: currentColor; }
|
|
132
133
|
|
|
133
134
|
/* ---- calendar: month grid, 44px cells, one accent day, optional marks ----
|
|
@@ -147,8 +148,10 @@
|
|
|
147
148
|
font-variant-numeric: tabular-nums;
|
|
148
149
|
grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
|
|
149
150
|
}
|
|
150
|
-
|
|
151
|
-
.
|
|
151
|
+
/* a marked day keeps the ground's ink, so its fill is a light tint (12%, as --ink-tint: every tone holds its ink 4.5:1 on that); the
|
|
152
|
+
strong mark adds a ring of the accent. A stronger fill sat at the middle, where neither the ink nor --on-accent holds 4.5:1 */
|
|
153
|
+
.calendar__day--mark { background: var(--calendar-day-mark-bg, color-mix(in srgb, var(--accent) 12%, transparent)); }
|
|
154
|
+
.calendar__day--mark2 { background: var(--calendar-day-mark2-bg, color-mix(in srgb, var(--accent) 12%, transparent)); color: var(--calendar-day-mark2-ink, inherit); box-shadow: var(--calendar-day-mark2-shadow, inset 0 0 0 var(--border-w) var(--accent)); }
|
|
152
155
|
.calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight: 700; }
|
|
153
156
|
.calendar__day:disabled { opacity: 0.3; }
|
|
154
157
|
|
|
@@ -204,7 +207,7 @@
|
|
|
204
207
|
edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
|
|
205
208
|
is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
|
|
206
209
|
}
|
|
207
|
-
.pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
|
|
210
|
+
.pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--action-accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
|
|
208
211
|
/* a PIN (secret): the cell DRAWS the dot, the character itself is not painted — except for a moment, when it has just been typed
|
|
209
212
|
(Lh 2026-10-01: "each digit must flash, then go at once"). It was `-webkit-text-security: disc`: a phone's browser then shows
|
|
210
213
|
the last typed character of a masked field for a second or more ("password echo", Android) — per FIELD, and a Pin is six
|
package/css/finish.css
CHANGED
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
so a finish sits in any palette; the one plain white is the sticker's backing paper and the lit edge's light.
|
|
21
21
|
============================================================ */
|
|
22
22
|
|
|
23
|
-
/* every finish: the finish IS the edge, so the wireframe's
|
|
24
|
-
|
|
23
|
+
/* every finish: the finish IS the controls' edge, so the wireframe's control border steps back (as [data-palette] in palette.css). The card
|
|
24
|
+
keeps its --card-border: lit's ring and sticker's white cut do not reach 3:1 on a light ground (palette.css, THE CARD KEEPS AN EDGE);
|
|
25
|
+
print and ink draw their own line below */
|
|
26
|
+
[data-finish] { --action-border: transparent; }
|
|
25
27
|
|
|
26
28
|
/* ============================================================
|
|
27
29
|
lit — the light comes from above:
|
package/css/journey.css
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
.route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
|
|
28
28
|
.route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
|
|
29
29
|
|
|
30
|
-
.route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center;
|
|
30
|
+
.route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center; color: var(--route-via-ink, var(--card-muted)); font-size: var(--route-via-font-size, var(--fs-body)); }
|
|
31
31
|
|
|
32
32
|
/* ---- tile badge: tiny label over a big value (gate, seat, zone) ---- */
|
|
33
33
|
.tile-badge {
|
|
@@ -37,13 +37,16 @@
|
|
|
37
37
|
padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
|
|
38
38
|
border-radius: var(--tile-badge-radius, var(--r-badge));
|
|
39
39
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
40
|
-
|
|
40
|
+
--dark: var(--card-3-dark);
|
|
41
|
+
color: var(--tile-badge-ink, var(--card-ink));
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
/* the label is the badge's own ink, smaller: a badge is a small fill of any colour (a palette paints it yellow), and only its own ink is
|
|
44
|
+
sure to hold 4.5:1 on it; --dim faded it to 2.4:1 */
|
|
45
|
+
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); color: var(--tile-badge-label-ink, inherit); }
|
|
43
46
|
.tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
44
47
|
|
|
45
48
|
/* plain: no fill — a date box beside a title (Jun / 18) */
|
|
46
|
-
.tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
49
|
+
.tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
47
50
|
.tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
|
|
48
51
|
|
|
49
52
|
/* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
|