@usableapp/cardds 0.4.1 → 0.5.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 +130 -0
- package/README.md +14 -13
- package/css/actions.css +6 -1
- package/css/base.css +13 -10
- package/css/card.css +7 -8
- package/css/choice.css +2 -2
- package/css/forms.css +1 -1
- package/css/journey.css +1 -1
- package/css/lists.css +2 -4
- package/css/media.css +3 -3
- package/css/numbers.css +1 -1
- package/css/sheet.css +5 -2
- package/css/theme.template.css +2 -2
- package/css/tokens.css +23 -21
- package/dist/cardds.css +64 -54
- package/dist/cards/CardHead.d.ts +3 -4
- package/dist/cards/CardHead.js +4 -4
- package/dist/cards/Stat.d.ts +1 -1
- package/dist/cards/Stat.js +1 -1
- package/dist/media/Quote.d.ts +1 -1
- package/dist/media/Quote.js +1 -1
- package/dist/scaffold/BaseContent.d.ts +1 -1
- package/dist/scaffold/BaseContent.js +1 -1
- package/dist/scaffold/TopBar.d.ts +3 -2
- package/dist/scaffold/TopBar.js +12 -1
- package/dist/sheets/Sheet.js +20 -0
- package/dist/type/Text.d.ts +2 -2
- package/dist/type/Text.js +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## 0.5.0 — 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Words are full ink; muted means inactive (Lh 2026-09-22)
|
|
8
|
+
`.base-content > .t-body` and `.kv__key` read `--on-surface` / `--card-ink` (were the muted grey). Knobs `--base-content-body-ink`, `--kv-key-ink` if a project wants them back. Muted stays on captions, `t-muted`, and inactive states (`.row--done`).
|
|
9
|
+
|
|
10
|
+
### Overline retired (Lh 2026-09-22) — BREAKING
|
|
11
|
+
A small caps line above a heading is a second message nobody asked for. Gone: `Text level="overline"` (TypeScript rejects it), `CardHead overline` (drop the prop — a walk's progress is a `Track` in the body, or nothing), `.t-overline`, `--type-overline`, `--track-overline`. The small name over a control ("รหัส OTP") is `Text level="label" muted`. Ring xs, tile-badge label, calendar weekday, timeline mark and quote attribution now read `--type-caption` with no tracking or uppercase (their font knobs are unchanged).
|
|
12
|
+
|
|
13
|
+
### h1 and h2 one size down (Lh 2026-09-22)
|
|
14
|
+
`--fs-h1` ×1.45 → ×1.2 (32 → 26: the screen title, so more of it fits the top bar) · `--fs-h2` ×1.2 → ×1 (26 → 22: a card's heading, told apart by the display face + weight). `--fs-3xl` (the xl button) stays 32; `--fs-lg`/`--fs-2xl` stay 26. Token names unchanged; a theme that sets `--fs-base` follows.
|
|
15
|
+
|
|
16
|
+
### TopBar: at most two trailing actions (Lh 2026-09-22)
|
|
17
|
+
A third control in `actions` is no longer drawn (`.topbar__actions > :nth-child(n+3)`), and `TopBar` warns once in the console. A bar that showed three icon buttons now shows two — move the rest into a `Menu` behind one `IconBtn`, or onto the base. Story `TopBar · TooManyActions`.
|
|
18
|
+
|
|
19
|
+
### `--text-scale` retired — the text-size setting is `--fs-base`
|
|
20
|
+
`--text-scale` multiplied the root font size, so a member's "larger text" zoomed every button, gap and icon too. Gone. An app's text-size setting now writes the type base on `<html>`: `document.documentElement.style.setProperty('--fs-base', '2.2rem')` (1.375rem = 100%, 2.2rem = 160%) — only the words and their holders (count badge, inline icon) grow. Remove `--text-scale` from your app; if a screen relied on the UI growing with the text, that is a gap to report.
|
|
21
|
+
|
|
22
|
+
### The type scale, rethought (Lh 2026-09-22) — BREAKING to the eye
|
|
23
|
+
The base IS the body: `--fs-base` 1.375rem (22px). Nothing that reads is under 17px. Sizes are a job's fit, not rungs — jobs share a size. Multipliers only, no clamp/vw. 16 tokens → 7 sizes:
|
|
24
|
+
| × | px | tokens |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| 0.775 | 17 | `--fs-xs` `--fs-caption` `--fs-label` `--fs-sm` (was 11 · 12 · 13 · 14) |
|
|
27
|
+
| 1 | 22 | `--fs-body` `--fs-md` `--fs-title` (was 15 · 16 · 17) |
|
|
28
|
+
| 1.2 | 26 | `--fs-h2` `--fs-lg` `--fs-2xl` (was 22 · 20 · 24) |
|
|
29
|
+
| 1.45 | 32 | `--fs-h1` `--fs-3xl` (was 28 · 32) |
|
|
30
|
+
| 1.8 | 40 | `--fs-display` `--fs-stat` (was clamp 34–42 · 30–38) |
|
|
31
|
+
| 2.5 | 55 | `--fs-hero` (was 56) |
|
|
32
|
+
| 2.9 | 64 | `--fs-giant` (was clamp 48–64) |
|
|
33
|
+
`--badge` = base × 1.2 (26), `--icon-xs` = base × 0.775 (17). A project's `theme.css` that set `--fs-base: 1rem` now gets 16px body — set it to `1.375rem` (the template's new default), or leave it unset. Every token name is unchanged.
|
|
34
|
+
- The sheet's peek strip follows the head's floor `--sheet-head-h` = max(a tap, an h2 line); when a title still wraps (a narrow phone at a large text size) the `Sheet` measures its head and publishes the real strip on its box, so the whole title row stays above the cut.
|
|
35
|
+
- The type page (`Text · Scale`) shows the 7 sizes with both faces and every job that shares each; the `/type` page is gone.
|
|
36
|
+
|
|
37
|
+
- `--field-font` (the words typed into a `Field`, default `--type-body`) and `--composer-input-font` (the Composer's textarea) are knobs now — a project that wants bigger type in its inputs sets them in `theme.css`. Every input already follows `--text-scale` on `<html>` (rem); if yours does not, look for a `px` `font-size` on `input` in your own CSS (the iOS no-zoom rule is the usual one).
|
|
38
|
+
- `.base-content > img` keeps its ratio inside `--base-content-media-h` (`height: auto`).
|
|
39
|
+
|
|
40
|
+
## 0.4.2 — 2026-09-22
|
|
41
|
+
|
|
42
|
+
### Words on the base: a picture and the dots
|
|
43
|
+
Screens that only tell (onboarding, a walk of pages) live on the base — `Step › BaseContent` — never in a `Card centre` on an empty base. Two things the base could not hold well are fixed:
|
|
44
|
+
- an `<img>` (or `Placeholder`) among the words is capped by `--base-content-media-h` (14rem) so a tall picture never pushes the words under the bar.
|
|
45
|
+
- `PagerAt` as the last child of a `BaseContent` is centred under the words (it used to sit left).
|
|
46
|
+
Story: `Step · WalksWithArt` — pages of words with a picture and the dots, a slider in the bar's upper tier, the round pager below.
|
|
47
|
+
|
|
48
|
+
Where things go on such a screen (answers given to timebank): a `Postcard` shown with the words is a child of `BaseContent` after them (the picture's slot; never wrapped in a Card) · a summary the system hands over (an address, a ticket) stays a `Card centre` · a walk's progress is the `CardHead` overline ("ขั้นที่ 2 จาก 5") or a `Track` as the first child of `BaseContent` — nothing sits between the TopBar and the Step.
|
|
49
|
+
|
|
50
|
+
## 0.4.1 — 2026-09-22
|
|
51
|
+
|
|
52
|
+
Breaking renames and removals. No aliases (nobody had installed 0.4.0).
|
|
53
|
+
|
|
54
|
+
### `CardBack` → `StackBack`
|
|
55
|
+
The back button a tapped stack sheet reveals belongs to the stack, not the card.
|
|
56
|
+
- import: `CardBack` → `StackBack`
|
|
57
|
+
- class (if you styled it): `.card__back` → `.sheet-stack__back`
|
|
58
|
+
- where it goes: unchanged — first child of the open stack sheet's `CardHead`.
|
|
59
|
+
|
|
60
|
+
### `SheetStage`: `ask` prop removed
|
|
61
|
+
The stage only does the ground; the thing in front places itself.
|
|
62
|
+
- `<SheetStage ask>` → `<SheetStage dim>` — the `Modal` inside centres itself now.
|
|
63
|
+
- A Modal on a clean ground (no scrim) is allowed: `<SheetStage>` with no `dim`.
|
|
64
|
+
- CSS: `.sheet-stage--ask` is gone; `--drawer-h` now lives on `.modal`.
|
|
65
|
+
|
|
66
|
+
### `Sheet`: `raised` prop removed
|
|
67
|
+
A sheet always stands on the floor and never has a dim behind it.
|
|
68
|
+
- `<SheetStage dim><Sheet raised …>` → that screen is a `Modal`: `<SheetStage dim><Modal><Lift>…</Lift><Drawer>…</Drawer></Modal>`
|
|
69
|
+
- `.sheet--raised`, `--sheet-raised-radius` are gone. `SheetStage foot` (the footnote strip) stays.
|
|
70
|
+
|
|
71
|
+
### `Postcard` reshaped
|
|
72
|
+
The shape of `Card cover`: the photo fills it, a band at the bottom names it. Not a 3:2 card inside a card any more.
|
|
73
|
+
- props kept: `photo` `alt` `name` `caption` `flipped` `defaultFlipped` `onFlip` `frontLabel` `backLabel`
|
|
74
|
+
- new: `line` (the words under the name, `t-body`) · `liked` + `onLike(liked)` (the member's own heart in the band; no heart without them) · `likeLabel` · `voice` (a voice note instead of words: whatever you put there stands at the middle of the back — the app plays it; see Progress)
|
|
75
|
+
- the band (`name` · `line` · heart) turns WITH the face. The back: `name` a fifth of the way down, `line`, then `caption` (or `voice`).
|
|
76
|
+
- a tap on the photo still turns it. The turn is `.postcard__turn` (a button filling the face); the faces are divs now.
|
|
77
|
+
- Don't wrap a Postcard in a `Card`. Its min-height is the cover's (`--postcard-min-h` 22.5rem); as a `SheetStage hero` it fills the band the sheet leaves open, with the screen's inset at both ends.
|
|
78
|
+
- Who liked it is YOUR data, shown outside the card — see `Row more`.
|
|
79
|
+
|
|
80
|
+
### `Row`: `more` (new)
|
|
81
|
+
A row that expands: it becomes the summary of a `<details class="row-expand">`, `more` comes out under it.
|
|
82
|
+
- `<Row lead={…} meta="4 คนถูกใจ" end={<Icon name="chevron-down" />} open={open} onOpenChange={setOpen} more={<Rows>…one Row per person…</Rows>} />`
|
|
83
|
+
- `open` + `onOpenChange`, or `defaultOpen`. An `end` chevron turns when open. You fetch the list; the row shows it.
|
|
84
|
+
|
|
85
|
+
### `Progress` (new)
|
|
86
|
+
One straight line, the done share solid, the rest faint.
|
|
87
|
+
- `<Progress at={0.4} label="0:05 of 0:12" />` — `at` 0–1; you keep the value (a timer, a download).
|
|
88
|
+
- knobs: `--progress-h` · `--progress-radius` · `--progress-done-ink` · `--progress-rest-bg`.
|
|
89
|
+
|
|
90
|
+
### Stage hero
|
|
91
|
+
`.sheet-stage__hero` is now as tall as the band a half sheet leaves open, less `--screen-pad` at both ends, and what it holds fills it (a child's `min-height` gives way). If your hero relied on its own height, check it.
|
|
92
|
+
|
|
93
|
+
### Gallery
|
|
94
|
+
One page per thing you name (Card, Sheet, Btn…); inner parts (CardHead, CardBody, SheetBody, MenuItem, BtnRow…) are variations on the parent's page. Nav groups by use. `/Parent/Part` URLs are gone.
|
|
95
|
+
|
|
96
|
+
## 0.4.0 — 2026-09-21
|
|
97
|
+
|
|
98
|
+
Knobs, open icons, theme bases, gallery. Nothing removed from the component API.
|
|
99
|
+
- **Knobs**: every component reads its look through `--<component>-<what>` custom properties with the default as fallback (363, listed in `docs/knobs.md`). Set one on `:root` (your `theme.css`) to retune everywhere, on any element to retune what is inside it. Declared nowhere: unset = nothing moved.
|
|
100
|
+
- **Icons are open**: `registerIcons({ name: '<path…/>' })` → usable by name on every component that takes an icon; `<Icon>` draws a one-off from children; an unknown name warns once instead of throwing.
|
|
101
|
+
- **Theme bases** (`css/theme.template.css`): four size bases (`--sp-base` `--fs-base` `--r-base` `--tap-base`) and five colour bases (`--color-surface` `--color-ink` `--color-paper` `--color-brand` `--color-on-brand`) on `:root`. A project sets a base, never a multiplier. `<html data-palette="own">` for own colours.
|
|
102
|
+
- `Stat centre`; the pager places what sits between its two buttons in the middle; every component types its `ref`.
|
|
103
|
+
|
|
104
|
+
## 0.3.0 — 2026-09-21
|
|
105
|
+
|
|
106
|
+
**Components only — the app wires them.** The biggest change since 0.1: every component with a state is controlled like an input (`x` + `onXChange`, or `defaultX`), keeps its own behaviour inside, and none knows another. `cardds.js` no longer does anything between components — it is `--kb` (the keyboard's height) and `.bleed`'s scrollbar, 35 lines. If your screen relied on cardds.js to connect two things, the app connects them now.
|
|
107
|
+
|
|
108
|
+
### Gone
|
|
109
|
+
- `aria-controls` on a button calling a sheet → the app: `useState` of the sheet's state, the button sets it, `<Sheet state onStateChange>`.
|
|
110
|
+
- events `cardds:sheet` · `cardds:pin` · `cardds:modal` · `cardds:flip`, and the Dropdown's native `change` → the components' own `onXChange` callbacks.
|
|
111
|
+
- `window.carddsSheetSet` / `carddsSheetReveal` / `carddsStepSync`, `--sheet-room`, the bar guard → the Sheet does all of it itself.
|
|
112
|
+
- `data-answer` on Modal buttons → plain `onClick`s; `<Modal open onClosed>`.
|
|
113
|
+
- `IconBtn menu` / `menuId` / `menuStart` → any button opens a popover `Menu` with the platform's `popoverTarget={id}`.
|
|
114
|
+
- `SheetStack mode="tap"` + hand-set `is-open` / `is-before` / `is-after` → `open` (index | null) + `onOpenChange`, or `defaultOpen`; the component paints the classes.
|
|
115
|
+
|
|
116
|
+
### Each component, before → after
|
|
117
|
+
- **Sheet**: `state="half"` alone used to be a starting point the sheet then moved from; now a fixed `state` PINS it (the sheet asks through `onStateChange` and stays if you don't answer). A sheet the member drags or taps the handle of: `defaultState="half"` (it keeps its own), or `state` + `onStateChange` (you keep it). Own behaviour, inside: handle tap ↔ peek, drag-snap to `states`, the raise when a field in a peeked sheet takes focus, the climb when the keyboard comes up. The `away` state (a called sheet, off-screen) is set-only.
|
|
118
|
+
- **SheetStack**: `open` + `onOpenChange` / `defaultOpen`. A peeked head's tap and the back button only ask.
|
|
119
|
+
- **Modal**: `open` + `onClosed` (it plays its opening backwards, then says so; you unmount it). Buttons are yours.
|
|
120
|
+
- **Menu**: `actions` (plain onClicks) or `value` + `onChange`; picking closes it. **Dropdown**: one component like `<select>`, `value` + `onChange`, the pill named by the chosen item.
|
|
121
|
+
- **Pin**: `value` + `onChange(code, complete)` or `defaultValue`; auto-advance, Backspace, arrows, paste inside. Tests that listened for `cardds:pin` type into the cells instead.
|
|
122
|
+
- **Slider**: publishes its own `--slider-at`. **Postcard**: `flipped` + `onFlip` or `defaultFlipped`.
|
|
123
|
+
- **Calendar**: `selected` + `onSelect` or `defaultSelected`; `onPrev` / `onNext`; label props (its buttons had no handlers before).
|
|
124
|
+
- **Picker**: `value` + `onChange` or `defaultValue` — the value IS the item at the rail's centre (it had no way to report one).
|
|
125
|
+
|
|
126
|
+
Guidance, no longer enforced: "pager or sheet", "a sheet finishes on its foot", "a form lives whole in one sheet".
|
|
127
|
+
|
|
128
|
+
## 0.2.2 — 2026-09-21
|
|
129
|
+
|
|
130
|
+
One sheet shape: the head stays pinned, the body scrolls without limit, the foot's button stands at the bottom above the keyboard — at every state, on a step and on a stage. Stage sheets ride `--kb` too. (`carddsSheetSet` / `carddsSheetReveal` on `window` — removed again in 0.3.0.)
|
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
141
141
|
<!-- reads: the words only -->
|
|
142
142
|
<section class="step">
|
|
143
143
|
<section class="base-content">
|
|
144
|
-
<div class="card__head"><
|
|
144
|
+
<div class="card__head"><h2 class="t-h2">…</h2></div>
|
|
145
145
|
<p class="t-body">…</p>
|
|
146
146
|
</section>
|
|
147
147
|
</section>
|
|
@@ -222,7 +222,7 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
222
222
|
<div class="screen screen--fill">
|
|
223
223
|
<header class="topbar"><h1 class="t-h1">ธนาคารเวลายินดีต้อนรับ</h1></header>
|
|
224
224
|
<article class="card card--centre">
|
|
225
|
-
<div class="card__head"><div><
|
|
225
|
+
<div class="card__head"><div><h2 class="t-h2">ปรับขนาดตัวหนังสือ</h2></div></div>
|
|
226
226
|
<p class="t-body">…</p>
|
|
227
227
|
</article>
|
|
228
228
|
</div>
|
|
@@ -283,10 +283,9 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
283
283
|
the far right it runs all the way across. The heading wraps freely —
|
|
284
284
|
no line budget, no squeeze, no clamp (Lh 2026-09-16): a height rule on
|
|
285
285
|
words only distorts them. The head is FLAT markup — heading, then an
|
|
286
|
-
optional
|
|
287
|
-
wrapper element, no `<p>`: the head wraps, the
|
|
288
|
-
|
|
289
|
-
rest (Lh 2026-09-16).
|
|
286
|
+
optional caption as a span, then the far-right thing; no
|
|
287
|
+
wrapper element, no `<p>`: the head wraps, the caption takes the row
|
|
288
|
+
below (`order`), the gap tokens do the rest (Lh 2026-09-16).
|
|
290
289
|
- **Content**: whatever the card frames — rows, key-values, a gauge, a
|
|
291
290
|
paragraph. Frameless, per rule 9.
|
|
292
291
|
- **Foot** (`.card__foot`): the card's action buttons on one line. One
|
|
@@ -500,8 +499,9 @@ Rules:
|
|
|
500
499
|
- **cardds does not judge the colours.** No contrast check, no warning — a
|
|
501
500
|
theme's values are the project's call. The derivations are only starting points;
|
|
502
501
|
any role can be set outright.
|
|
503
|
-
-
|
|
504
|
-
|
|
502
|
+
- The member's text-size setting is the same `--fs-base`, written on `<html>`
|
|
503
|
+
by the app at run time (2.2rem = 160%): the words grow, the things keep their
|
|
504
|
+
size. (`--text-scale`, which multiplied the root and zoomed the UI too, is gone.)
|
|
505
505
|
|
|
506
506
|
Try it live: the gallery's nav (`npm run dev`) has a slider per size base, and
|
|
507
507
|
palette `own` shows a colour well per colour base.
|
|
@@ -531,10 +531,9 @@ whole system still resizes from one number: the root font size. `base.css` keeps
|
|
|
531
531
|
default) and steps narrow phones (≤360px) to `93.75%`, which keeps the 25rem
|
|
532
532
|
column, its 48px taps and its type in proportion on 320–360 screens. Only
|
|
533
533
|
`--border-w`, `--rule-w` and the focus ring stay in px, so edges stay crisp.
|
|
534
|
-
An app with its own text-size setting
|
|
535
|
-
|
|
536
|
-
the
|
|
537
|
-
than overwriting it, and the app never writes a length from script.
|
|
534
|
+
An app with its own text-size setting writes `--fs-base` on `<html>` (the type
|
|
535
|
+
base, 1.375rem = 100%; 2.2rem = 160%): only the type and its holders (the count
|
|
536
|
+
badge, the inline icon) grow; taps, spacing and radii keep their size.
|
|
538
537
|
Components never write a raw length: spacing from `--sp-*`, control sizes from
|
|
539
538
|
`--tap`, `--tap-lg`, `--btn-h`, `--bar-h`, `--header-h`, `--fab`, `--badge`, container
|
|
540
539
|
padding from `--screen-pad` / `--card-pad`, radii from
|
|
@@ -567,10 +566,12 @@ The two bars across the top of a screen — `.topbar` (screen title) and
|
|
|
567
566
|
`.appbar` (app context) — share one rule and one floor, `--header-h` (3.75rem /
|
|
568
567
|
60px), so a title-only bar stands as tall as one carrying an icon button, a
|
|
569
568
|
dropdown or a `.btn`. `.screen` reserves its top padding from the same token.
|
|
569
|
+
`.topbar__actions` takes at most TWO controls (Lh 2026-09-22): a third and beyond
|
|
570
|
+
is not drawn (`TopBar` warns once) — more moves go in a `Menu` behind one button.
|
|
570
571
|
|
|
571
572
|
## Typography levels
|
|
572
573
|
|
|
573
574
|
`t-display` 34–42/800 · `t-h1` 28 · `t-h2` 22 · `t-title` 17 · `t-body` 15 ·
|
|
574
|
-
`t-label` 13 · `t-caption` 12 · `t-
|
|
575
|
+
`t-label` 13 · `t-caption` 12 · `t-stat` big tabular
|
|
575
576
|
numbers (`t-stat--mono`, `t-stat--xl`). Pixel values are at the default root;
|
|
576
577
|
each is an `--fs-*` rem token. Swap `--font-display` per app for character.
|
package/css/actions.css
CHANGED
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
margin-block: -0.15em;
|
|
39
39
|
}
|
|
40
40
|
.topbar__actions { margin-left: auto; display: flex; gap: var(--sp-2); }
|
|
41
|
+
/* at most TWO trailing controls (Lh 2026-09-22): the title must keep its line; a third and
|
|
42
|
+
beyond is not drawn — more moves belong in a Menu behind one button, or on the base */
|
|
43
|
+
.topbar__actions > :nth-child(n + 3) { display: none; }
|
|
41
44
|
|
|
42
45
|
/* a screen whose one big move sits at the top right (home's "ขอความช่วยเหลือ" — Lh 2026-09-09:
|
|
43
46
|
no bottom bar on the home screen) puts a primary .btn in the top bar's actions; it stands at
|
|
@@ -141,7 +144,9 @@
|
|
|
141
144
|
text-decoration: none;
|
|
142
145
|
justify-self: start; /* as a grid child it hugs its text, never stretches */
|
|
143
146
|
}
|
|
144
|
-
.dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); } /* signal dot: chips, legends
|
|
147
|
+
.dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); translate: 0 var(--dot-drop, 0.09em); } /* signal dot: chips, legends.
|
|
148
|
+
The drop: a line box is centred on its ascender room (Thai marks), the letters beside the dot sit lower — the dot rides down to the
|
|
149
|
+
letters' own middle, in em so it follows the text size (Lh 2026-09-22: "ตัวหนังสือกับไฟเสมอกัน") */
|
|
145
150
|
.dot--b { background: var(--dot-b); }
|
|
146
151
|
.chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
|
|
147
152
|
/* dark-slot inversion lives in card.css (.card--3 :is(...)) */
|
package/css/base.css
CHANGED
|
@@ -16,14 +16,13 @@ button, [role="button"], [role="tab"], [role="option"], [role="menuitem"], a.btn
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/* the scale: everything is rem, so this one number resizes the whole system.
|
|
19
|
-
100% = the
|
|
19
|
+
100% = the browser's text size (16px by default); narrow phones step to 15/16 so
|
|
20
20
|
the 25rem column and its 48px taps keep their proportions on 320–360 screens.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@media (max-width: 22.5em) { html { font-size: calc(93.75% * var(--text-scale, 1)); } }
|
|
21
|
+
An app's OWN text-size setting is NOT here (Lh 2026-09-22: --text-scale, which
|
|
22
|
+
multiplied the root, zoomed the UI too — retired): it is --fs-base on <html>,
|
|
23
|
+
the type base, so the words grow and the things keep their size (tokens.css). */
|
|
24
|
+
html { font-size: 100%; scrollbar-width: none; }
|
|
25
|
+
@media (max-width: 22.5em) { html { font-size: 93.75%; } }
|
|
27
26
|
html::-webkit-scrollbar { display: none; } /* Chromium/Safari */
|
|
28
27
|
|
|
29
28
|
body {
|
|
@@ -96,7 +95,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
96
95
|
.t-body { font: var(--type-body); margin: 0; }
|
|
97
96
|
.t-label { font: var(--type-label); }
|
|
98
97
|
.t-caption { font: var(--type-caption); }
|
|
99
|
-
.t-overline { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; }
|
|
100
98
|
.t-stat { font: var(--type-stat); letter-spacing: var(--track-display); margin: 0; font-variant-numeric: tabular-nums; }
|
|
101
99
|
.t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
|
|
102
100
|
.t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
|
|
@@ -108,7 +106,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
108
106
|
.grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
|
|
109
107
|
|
|
110
108
|
/* ---- base content: words straight on the base, no card around them — an
|
|
111
|
-
|
|
109
|
+
a heading, a paragraph, maybe a .cta-pack. Was .section until
|
|
112
110
|
2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
|
|
113
111
|
In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
|
|
114
112
|
.base-content {
|
|
@@ -117,7 +115,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
117
115
|
align-content: start; /* a tall column never stretches the rows — the words hug the top */
|
|
118
116
|
padding-block: var(--base-content-pad-y, var(--sp-4));
|
|
119
117
|
}
|
|
120
|
-
.base-content > .t-body { color: var(--on-surface
|
|
118
|
+
.base-content > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
|
|
119
|
+
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
120
|
+
the "where am I" dots under the words, centred — the same dots the centred card has under it */
|
|
121
|
+
.base-content > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
122
|
+
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
123
|
+
.base-content > .pager__at { justify-self: center; }
|
|
121
124
|
.base-content .cta-pack {
|
|
122
125
|
display: flex;
|
|
123
126
|
align-items: start; /* buttons keep their own height, never the row's */
|
package/css/card.css
CHANGED
|
@@ -39,19 +39,18 @@
|
|
|
39
39
|
multi-line title can opt back out with .card__head--top. */
|
|
40
40
|
.card__head {
|
|
41
41
|
display: flex;
|
|
42
|
-
flex-wrap: wrap; /* the caption
|
|
42
|
+
flex-wrap: wrap; /* the caption takes a line of its own — no wrapper div */
|
|
43
43
|
align-items: center;
|
|
44
44
|
justify-content: space-between;
|
|
45
45
|
column-gap: var(--card-head-col-gap, var(--sp-3));
|
|
46
46
|
row-gap: var(--card-head-row-gap, var(--sp-0));
|
|
47
47
|
}
|
|
48
48
|
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
49
|
-
<div class="card__head"><
|
|
50
|
-
The heading and the far-right thing share the first row;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.card__head > :is(.t-
|
|
54
|
-
.card__head > .t-overline { order: -1; }
|
|
49
|
+
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
|
|
50
|
+
The heading and the far-right thing share the first row; a caption sits on
|
|
51
|
+
a row below (order 1), whatever the DOM order — so an author writes the
|
|
52
|
+
words in reading order. */
|
|
53
|
+
.card__head > :is(.t-caption, .t-body) { flex: 1 1 100%; order: 1; margin: 0; }
|
|
55
54
|
.card__head--top { align-items: flex-start; }
|
|
56
55
|
|
|
57
56
|
.card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
|
|
@@ -73,7 +72,7 @@
|
|
|
73
72
|
/* Skeleton head — the heading in a card's OWN head, and only there.
|
|
74
73
|
The heading wraps freely: no line budget, no squeeze, no clamp (Lh
|
|
75
74
|
2026-09-16 — a height rule on words only distorts them; content that needs
|
|
76
|
-
more rows takes more rows). The group that holds
|
|
75
|
+
more rows takes more rows). The group that holds heading ·
|
|
77
76
|
caption is the head's own wrapping row (above) — no wrapper element, no
|
|
78
77
|
height, only the row gap. */
|
|
79
78
|
.card__head :is(h1, h2, h3) { overflow-wrap: anywhere; }
|
package/css/choice.css
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
.calendar { display: grid; gap: var(--calendar-gap, var(--sp-3)); }
|
|
139
139
|
.calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
|
|
140
140
|
.calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
|
|
141
|
-
.calendar__wd { font: var(--calendar-wd-font, var(--type-
|
|
141
|
+
.calendar__wd { font: var(--calendar-wd-font, var(--type-caption)); color: var(--calendar-wd-ink, var(--card-muted)); padding-block: var(--calendar-wd-pad-y, var(--sp-2)); }
|
|
142
142
|
.card--3 .calendar__wd { color: inherit; opacity: 0.7; }
|
|
143
143
|
.calendar__day {
|
|
144
144
|
width: 100%;
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
padding: 0;
|
|
222
222
|
background: transparent;
|
|
223
223
|
color: inherit;
|
|
224
|
-
font: 700 var(--fs-2xl)/1.25 var(--font-display);
|
|
224
|
+
font: var(--composer-input-font, 700 var(--fs-2xl)/1.25 var(--font-display));
|
|
225
225
|
letter-spacing: var(--track-h2);
|
|
226
226
|
outline: none;
|
|
227
227
|
}
|
package/css/forms.css
CHANGED
package/css/journey.css
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
35
35
|
color: var(--tile-badge-ink, var(--card-3-ink));
|
|
36
36
|
}
|
|
37
|
-
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-
|
|
37
|
+
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); opacity: 0.7; }
|
|
38
38
|
.tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
39
39
|
|
|
40
40
|
/* plain: no fill — a date box beside a title (Jun / 18) */
|
package/css/lists.css
CHANGED
|
@@ -82,8 +82,7 @@
|
|
|
82
82
|
.kv { margin: 0; display: grid; }
|
|
83
83
|
.kv__row { display: flex; align-items: center; justify-content: space-between; gap: var(--kv-row-gap, var(--sp-3)); min-height: var(--kv-row-min-h, var(--tap)); }
|
|
84
84
|
.kv--lined > .kv__row + .kv__row { border-top: var(--border-w) solid var(--hairline); }
|
|
85
|
-
.kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-
|
|
86
|
-
.card--3 .kv__key { color: inherit; opacity: 0.7; }
|
|
85
|
+
.kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-ink)); } /* full ink (Lh 2026-09-22): a key is read like the value; muted = inactive, not "secondary" */
|
|
87
86
|
.kv__val { margin: 0; font: var(--kv-val-font, var(--type-title)); display: inline-flex; align-items: center; gap: var(--kv-val-gap, var(--sp-2)); text-align: right; min-width: 0; }
|
|
88
87
|
|
|
89
88
|
/* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
|
|
@@ -177,8 +176,7 @@
|
|
|
177
176
|
}
|
|
178
177
|
.timeline__mark {
|
|
179
178
|
justify-self: start;
|
|
180
|
-
font: var(--timeline-mark-font, var(--type-
|
|
181
|
-
letter-spacing: var(--track-overline);
|
|
179
|
+
font: var(--timeline-mark-font, var(--type-caption));
|
|
182
180
|
text-transform: uppercase;
|
|
183
181
|
padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
|
|
184
182
|
border-radius: var(--timeline-mark-radius, var(--r-chip));
|
package/css/media.css
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
/* ---- cover: the image IS the card; text over a scrim ----
|
|
7
7
|
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
8
|
-
<div class="card__head"><
|
|
8
|
+
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
9
9
|
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
10
10
|
<div class="row">…meta…</div></article> */
|
|
11
11
|
.card--cover {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
border-radius: 0;
|
|
31
31
|
min-height: 0;
|
|
32
32
|
}
|
|
33
|
-
/* scrim: dark at both ends so
|
|
33
|
+
/* scrim: dark at both ends so the meta stays readable */
|
|
34
34
|
.card--cover::before {
|
|
35
35
|
content: "";
|
|
36
36
|
position: absolute;
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
.quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
|
|
54
54
|
.quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
|
|
55
|
-
.quote__by { font: var(--quote-by-font, var(--type-
|
|
55
|
+
.quote__by { font: var(--quote-by-font, var(--type-caption)); color: var(--quote-by-ink, var(--card-muted)); }
|
|
56
56
|
.card--3 .quote__by { color: inherit; opacity: 0.7; }
|
|
57
57
|
|
|
58
58
|
/* ---- tile grid: big square tiles — icon top, label bottom ----
|
package/css/numbers.css
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
.ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
|
|
38
38
|
.ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
|
|
39
39
|
.ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
|
|
40
|
-
.ring--xs .ring__value { font: var(--type-
|
|
40
|
+
.ring--xs .ring__value { font: var(--type-caption); }
|
|
41
41
|
|
|
42
42
|
/* ---- track: step progress in a pill — done · done · todo ----
|
|
43
43
|
<ol class="track"><li class="track__step is-done">icon</li> …</ol> */
|
package/css/sheet.css
CHANGED
|
@@ -89,7 +89,10 @@
|
|
|
89
89
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
90
90
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
91
91
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
92
|
-
:is(.step, .sheet-stage) { --_handle: var(--sp-1); --sheet-peek: calc(var(--card-pad-rest) + var(--_handle) + 2 * var(--card-gap-rest) + var(--
|
|
92
|
+
:is(.step, .sheet-stage) { --_handle: var(--sp-1); --sheet-peek: calc(var(--card-pad-rest) + var(--_handle) + 2 * var(--card-gap-rest) + var(--sheet-head-h)); }
|
|
93
|
+
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
94
|
+
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
95
|
+
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
93
96
|
|
|
94
97
|
/* ---- called sheets (Lh, 2026-09-20): a .step whose .base-content has buttons that each CALL a sheet ----
|
|
95
98
|
.sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
|
|
@@ -201,7 +204,7 @@
|
|
|
201
204
|
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
202
205
|
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
203
206
|
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
204
|
-
.sheet > .card__head { min-height: var(--
|
|
207
|
+
.sheet > .card__head { min-height: var(--sheet-head-h); }
|
|
205
208
|
.sheet__head {
|
|
206
209
|
display: grid;
|
|
207
210
|
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
package/css/theme.template.css
CHANGED
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
:root {
|
|
24
24
|
/* ---- size: four bases, the multipliers stay the system's ---- */
|
|
25
25
|
--sp-base: 0.25rem; /* 4 the room BETWEEN things: every --sp-N = base × N, so paddings and gaps */
|
|
26
|
-
--fs-base:
|
|
26
|
+
--fs-base: 1.375rem; /* 22 the type — the body size; every --fs-* is a multiplier of it (17 · 22 · 26 · 32 · 40 · 55 · 64), the count badge, the inline icon */
|
|
27
27
|
--r-base: 1rem; /* 16 the corners: every --r-* (0 = square; chips and buttons stay fully round) */
|
|
28
28
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets, control heights, icons, avatars, ring, tiles
|
|
29
29
|
(a touch target never goes under 44px, whatever this says) */
|
|
30
|
-
/*
|
|
30
|
+
/* a member's text-size setting is this same --fs-base, written on <html> by the app at run time (1.375rem = 100%, 2.2rem = 160%) — the words grow, the things keep their size */
|
|
31
31
|
|
|
32
32
|
/* ---- faces ---- */
|
|
33
33
|
--font-ui: "PK Nonthaburi", -apple-system, system-ui, sans-serif;
|
package/css/tokens.css
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
and never a multiplier, so the proportions of the system hold at any size.
|
|
21
21
|
The px in the comments are at these defaults (1rem = 16px). */
|
|
22
22
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
23
|
-
--fs-base:
|
|
23
|
+
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.775 (17).
|
|
24
|
+
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
24
25
|
--r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
|
|
25
26
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
26
27
|
--header-h, --fab), icons, avatars, dots, the ring and the tiles. --sp-base is the room BETWEEN things.
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
--screen-pad: var(--sp-4); /* 16 the screen's inset — .sheet-stack cancels it */
|
|
55
56
|
--map-ring: calc(var(--tap-base) * 10 / 3); /* the rough-area ring on a map (sheet.css .map__ring) — a neighbourhood, not a house */
|
|
56
57
|
--fab: calc(var(--tap-base) * 3 / 2); /* 72 square floating action */
|
|
57
|
-
--badge: calc(var(--fs-base) * 1.
|
|
58
|
+
--badge: calc(var(--fs-base) * 1.2); /* 26 count badge — holds a 17px number */
|
|
58
59
|
--dot: calc(var(--tap-base) * 5 / 24); /* 10 signal dot */
|
|
59
60
|
--dot-xs: calc(var(--tap-base) / 8); /* 6 marker dot */
|
|
60
61
|
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
--icon: calc(var(--tap-base) * 7 / 12); /* 28 default */
|
|
63
64
|
--icon-sm: calc(var(--tap-base) * 7 / 12); /* one size everywhere; retune if small contexts appear */
|
|
64
65
|
--icon-lg: calc(var(--tap-base) * 2 / 3); /* 32 empty states */
|
|
65
|
-
--icon-xs: var(--fs-base);
|
|
66
|
+
--icon-xs: calc(var(--fs-base) * 0.775); /* 17 inline with captions/chips only (row meta, link arrows) — the caption's size */
|
|
66
67
|
|
|
67
68
|
/* ---- people & big elements ---- */
|
|
68
69
|
--avatar-sm: calc(var(--tap-base) * 5 / 6); /* 40 row lead */
|
|
@@ -100,22 +101,24 @@
|
|
|
100
101
|
--font-display: "FC Pride", var(--font-ui); /* the character face for headings and big numbers (fonts.css) */
|
|
101
102
|
--font-mono: "SF Mono", "JetBrains Mono", ui-monospace, monospace;
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
--fs-
|
|
106
|
-
--fs-
|
|
107
|
-
--fs-
|
|
108
|
-
--fs-
|
|
109
|
-
--fs-
|
|
110
|
-
--fs-
|
|
111
|
-
--fs-
|
|
112
|
-
--fs-
|
|
113
|
-
--fs-
|
|
114
|
-
--fs-
|
|
115
|
-
--fs-
|
|
116
|
-
--fs-
|
|
117
|
-
--fs-
|
|
118
|
-
--fs-
|
|
104
|
+
/* THE SCALE (Lh 2026-09-22): 7 sizes for 16 jobs — a size is a job's fit, not a rung; jobs share sizes.
|
|
105
|
+
Nothing that reads is under 17 (the smallest ui: chips). No clamp/vw: a size is one number. */
|
|
106
|
+
--fs-xs: calc(var(--fs-base) * 0.775); /* 17 the smallest ui words: ring xs, a tile-badge label */
|
|
107
|
+
--fs-caption: calc(var(--fs-base) * 0.775); /* 17 */
|
|
108
|
+
--fs-label: calc(var(--fs-base) * 0.775); /* 17 chips, buttons' small words */
|
|
109
|
+
--fs-sm: calc(var(--fs-base) * 0.775); /* 17 chips, segments — the floor */
|
|
110
|
+
--fs-body: var(--fs-base); /* 22 body, the words in a field */
|
|
111
|
+
--fs-md: var(--fs-base); /* 22 buttons, fields */
|
|
112
|
+
--fs-title: var(--fs-base); /* 22 row title (weight tells it apart) */
|
|
113
|
+
--fs-h2: var(--fs-base); /* 22 a card's heading (Lh 2026-09-22: one size down — the display face + weight tell it) */
|
|
114
|
+
--fs-lg: calc(var(--fs-base) * 1.2); /* 26 day numbers, tile values */
|
|
115
|
+
--fs-2xl: calc(var(--fs-base) * 1.2); /* 26 composer, small ring */
|
|
116
|
+
--fs-h1: calc(var(--fs-base) * 1.2); /* 26 screen title (Lh 2026-09-22: was 1.45 — a smaller title = more of it fits the bar) */
|
|
117
|
+
--fs-3xl: calc(var(--fs-base) * 1.45); /* 32 xl button */
|
|
118
|
+
--fs-display: calc(var(--fs-base) * 1.8); /* 40 hero words */
|
|
119
|
+
--fs-stat: calc(var(--fs-base) * 1.8); /* 40 big numbers */
|
|
120
|
+
--fs-hero: calc(var(--fs-base) * 2.5); /* 55 the one number on a sheet */
|
|
121
|
+
--fs-giant: calc(var(--fs-base) * 2.9); /* 64 data bands */
|
|
119
122
|
|
|
120
123
|
/* level weight size / line-height family */
|
|
121
124
|
--type-display: 800 var(--fs-display) / 1.15 var(--font-display); /* leading: Thai marks stack to 1.1em up and 0.3em down in FC Pride — 1.02 clipped them (2026-09-10) */
|
|
@@ -128,8 +131,6 @@
|
|
|
128
131
|
--type-body: 400 var(--fs-body) / 1.5 var(--font-ui);
|
|
129
132
|
--type-label: 600 var(--fs-label) / 1.2 var(--font-ui); /* chips, buttons */
|
|
130
133
|
--type-caption: 500 var(--fs-caption) / 1.35 var(--font-ui);
|
|
131
|
-
--type-overline:700 var(--fs-xs) / 1.2 var(--font-mono); /* uppercase micro-labels */
|
|
132
|
-
--track-overline: 0.08em;
|
|
133
134
|
--type-stat: 800 var(--fs-stat) / 1.05 var(--font-display); /* big numbers */
|
|
134
135
|
|
|
135
136
|
/* ============================================================
|
|
@@ -208,5 +209,6 @@
|
|
|
208
209
|
falls back to when its own declaration cannot resolve.
|
|
209
210
|
============================================================ */
|
|
210
211
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
212
|
+
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
|
|
211
213
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
212
214
|
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
package/dist/cardds.css
CHANGED
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
and never a multiplier, so the proportions of the system hold at any size.
|
|
62
62
|
The px in the comments are at these defaults (1rem = 16px). */
|
|
63
63
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
64
|
-
--fs-base:
|
|
64
|
+
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.775 (17).
|
|
65
|
+
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
65
66
|
--r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
|
|
66
67
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
67
68
|
--header-h, --fab), icons, avatars, dots, the ring and the tiles. --sp-base is the room BETWEEN things.
|
|
@@ -95,7 +96,7 @@
|
|
|
95
96
|
--screen-pad: var(--sp-4); /* 16 the screen's inset — .sheet-stack cancels it */
|
|
96
97
|
--map-ring: calc(var(--tap-base) * 10 / 3); /* the rough-area ring on a map (sheet.css .map__ring) — a neighbourhood, not a house */
|
|
97
98
|
--fab: calc(var(--tap-base) * 3 / 2); /* 72 square floating action */
|
|
98
|
-
--badge: calc(var(--fs-base) * 1.
|
|
99
|
+
--badge: calc(var(--fs-base) * 1.2); /* 26 count badge — holds a 17px number */
|
|
99
100
|
--dot: calc(var(--tap-base) * 5 / 24); /* 10 signal dot */
|
|
100
101
|
--dot-xs: calc(var(--tap-base) / 8); /* 6 marker dot */
|
|
101
102
|
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
--icon: calc(var(--tap-base) * 7 / 12); /* 28 default */
|
|
104
105
|
--icon-sm: calc(var(--tap-base) * 7 / 12); /* one size everywhere; retune if small contexts appear */
|
|
105
106
|
--icon-lg: calc(var(--tap-base) * 2 / 3); /* 32 empty states */
|
|
106
|
-
--icon-xs: var(--fs-base);
|
|
107
|
+
--icon-xs: calc(var(--fs-base) * 0.775); /* 17 inline with captions/chips only (row meta, link arrows) — the caption's size */
|
|
107
108
|
|
|
108
109
|
/* ---- people & big elements ---- */
|
|
109
110
|
--avatar-sm: calc(var(--tap-base) * 5 / 6); /* 40 row lead */
|
|
@@ -141,22 +142,24 @@
|
|
|
141
142
|
--font-display: "FC Pride", var(--font-ui); /* the character face for headings and big numbers (fonts.css) */
|
|
142
143
|
--font-mono: "SF Mono", "JetBrains Mono", ui-monospace, monospace;
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
--fs-
|
|
147
|
-
--fs-
|
|
148
|
-
--fs-
|
|
149
|
-
--fs-
|
|
150
|
-
--fs-
|
|
151
|
-
--fs-
|
|
152
|
-
--fs-
|
|
153
|
-
--fs-
|
|
154
|
-
--fs-
|
|
155
|
-
--fs-
|
|
156
|
-
--fs-
|
|
157
|
-
--fs-
|
|
158
|
-
--fs-
|
|
159
|
-
--fs-
|
|
145
|
+
/* THE SCALE (Lh 2026-09-22): 7 sizes for 16 jobs — a size is a job's fit, not a rung; jobs share sizes.
|
|
146
|
+
Nothing that reads is under 17 (the smallest ui: chips). No clamp/vw: a size is one number. */
|
|
147
|
+
--fs-xs: calc(var(--fs-base) * 0.775); /* 17 the smallest ui words: ring xs, a tile-badge label */
|
|
148
|
+
--fs-caption: calc(var(--fs-base) * 0.775); /* 17 */
|
|
149
|
+
--fs-label: calc(var(--fs-base) * 0.775); /* 17 chips, buttons' small words */
|
|
150
|
+
--fs-sm: calc(var(--fs-base) * 0.775); /* 17 chips, segments — the floor */
|
|
151
|
+
--fs-body: var(--fs-base); /* 22 body, the words in a field */
|
|
152
|
+
--fs-md: var(--fs-base); /* 22 buttons, fields */
|
|
153
|
+
--fs-title: var(--fs-base); /* 22 row title (weight tells it apart) */
|
|
154
|
+
--fs-h2: var(--fs-base); /* 22 a card's heading (Lh 2026-09-22: one size down — the display face + weight tell it) */
|
|
155
|
+
--fs-lg: calc(var(--fs-base) * 1.2); /* 26 day numbers, tile values */
|
|
156
|
+
--fs-2xl: calc(var(--fs-base) * 1.2); /* 26 composer, small ring */
|
|
157
|
+
--fs-h1: calc(var(--fs-base) * 1.2); /* 26 screen title (Lh 2026-09-22: was 1.45 — a smaller title = more of it fits the bar) */
|
|
158
|
+
--fs-3xl: calc(var(--fs-base) * 1.45); /* 32 xl button */
|
|
159
|
+
--fs-display: calc(var(--fs-base) * 1.8); /* 40 hero words */
|
|
160
|
+
--fs-stat: calc(var(--fs-base) * 1.8); /* 40 big numbers */
|
|
161
|
+
--fs-hero: calc(var(--fs-base) * 2.5); /* 55 the one number on a sheet */
|
|
162
|
+
--fs-giant: calc(var(--fs-base) * 2.9); /* 64 data bands */
|
|
160
163
|
|
|
161
164
|
/* level weight size / line-height family */
|
|
162
165
|
--type-display: 800 var(--fs-display) / 1.15 var(--font-display); /* leading: Thai marks stack to 1.1em up and 0.3em down in FC Pride — 1.02 clipped them (2026-09-10) */
|
|
@@ -169,8 +172,6 @@
|
|
|
169
172
|
--type-body: 400 var(--fs-body) / 1.5 var(--font-ui);
|
|
170
173
|
--type-label: 600 var(--fs-label) / 1.2 var(--font-ui); /* chips, buttons */
|
|
171
174
|
--type-caption: 500 var(--fs-caption) / 1.35 var(--font-ui);
|
|
172
|
-
--type-overline:700 var(--fs-xs) / 1.2 var(--font-mono); /* uppercase micro-labels */
|
|
173
|
-
--track-overline: 0.08em;
|
|
174
175
|
--type-stat: 800 var(--fs-stat) / 1.05 var(--font-display); /* big numbers */
|
|
175
176
|
|
|
176
177
|
/* ============================================================
|
|
@@ -249,6 +250,7 @@
|
|
|
249
250
|
falls back to when its own declaration cannot resolve.
|
|
250
251
|
============================================================ */
|
|
251
252
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
253
|
+
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
|
|
252
254
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
253
255
|
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
|
254
256
|
|
|
@@ -390,14 +392,13 @@ button, [role="button"], [role="tab"], [role="option"], [role="menuitem"], a.btn
|
|
|
390
392
|
}
|
|
391
393
|
|
|
392
394
|
/* the scale: everything is rem, so this one number resizes the whole system.
|
|
393
|
-
100% = the
|
|
395
|
+
100% = the browser's text size (16px by default); narrow phones step to 15/16 so
|
|
394
396
|
the 25rem column and its 48px taps keep their proportions on 320–360 screens.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
@media (max-width: 22.5em) { html { font-size: calc(93.75% * var(--text-scale, 1)); } }
|
|
397
|
+
An app's OWN text-size setting is NOT here (Lh 2026-09-22: --text-scale, which
|
|
398
|
+
multiplied the root, zoomed the UI too — retired): it is --fs-base on <html>,
|
|
399
|
+
the type base, so the words grow and the things keep their size (tokens.css). */
|
|
400
|
+
html { font-size: 100%; scrollbar-width: none; }
|
|
401
|
+
@media (max-width: 22.5em) { html { font-size: 93.75%; } }
|
|
401
402
|
html::-webkit-scrollbar { display: none; } /* Chromium/Safari */
|
|
402
403
|
|
|
403
404
|
body {
|
|
@@ -470,7 +471,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
470
471
|
.t-body { font: var(--type-body); margin: 0; }
|
|
471
472
|
.t-label { font: var(--type-label); }
|
|
472
473
|
.t-caption { font: var(--type-caption); }
|
|
473
|
-
.t-overline { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; }
|
|
474
474
|
.t-stat { font: var(--type-stat); letter-spacing: var(--track-display); margin: 0; font-variant-numeric: tabular-nums; }
|
|
475
475
|
.t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
|
|
476
476
|
.t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
|
|
@@ -482,7 +482,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
482
482
|
.grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
|
|
483
483
|
|
|
484
484
|
/* ---- base content: words straight on the base, no card around them — an
|
|
485
|
-
|
|
485
|
+
a heading, a paragraph, maybe a .cta-pack. Was .section until
|
|
486
486
|
2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
|
|
487
487
|
In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
|
|
488
488
|
.base-content {
|
|
@@ -491,7 +491,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
491
491
|
align-content: start; /* a tall column never stretches the rows — the words hug the top */
|
|
492
492
|
padding-block: var(--base-content-pad-y, var(--sp-4));
|
|
493
493
|
}
|
|
494
|
-
.base-content > .t-body { color: var(--on-surface
|
|
494
|
+
.base-content > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
|
|
495
|
+
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
496
|
+
the "where am I" dots under the words, centred — the same dots the centred card has under it */
|
|
497
|
+
.base-content > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
498
|
+
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
499
|
+
.base-content > .pager__at { justify-self: center; }
|
|
495
500
|
.base-content .cta-pack {
|
|
496
501
|
display: flex;
|
|
497
502
|
align-items: start; /* buttons keep their own height, never the row's */
|
|
@@ -556,19 +561,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
556
561
|
multi-line title can opt back out with .card__head--top. */
|
|
557
562
|
.card__head {
|
|
558
563
|
display: flex;
|
|
559
|
-
flex-wrap: wrap; /* the caption
|
|
564
|
+
flex-wrap: wrap; /* the caption takes a line of its own — no wrapper div */
|
|
560
565
|
align-items: center;
|
|
561
566
|
justify-content: space-between;
|
|
562
567
|
column-gap: var(--card-head-col-gap, var(--sp-3));
|
|
563
568
|
row-gap: var(--card-head-row-gap, var(--sp-0));
|
|
564
569
|
}
|
|
565
570
|
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
566
|
-
<div class="card__head"><
|
|
567
|
-
The heading and the far-right thing share the first row;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
.card__head > :is(.t-
|
|
571
|
-
.card__head > .t-overline { order: -1; }
|
|
571
|
+
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
|
|
572
|
+
The heading and the far-right thing share the first row; a caption sits on
|
|
573
|
+
a row below (order 1), whatever the DOM order — so an author writes the
|
|
574
|
+
words in reading order. */
|
|
575
|
+
.card__head > :is(.t-caption, .t-body) { flex: 1 1 100%; order: 1; margin: 0; }
|
|
572
576
|
.card__head--top { align-items: flex-start; }
|
|
573
577
|
|
|
574
578
|
.card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
|
|
@@ -590,7 +594,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
590
594
|
/* Skeleton head — the heading in a card's OWN head, and only there.
|
|
591
595
|
The heading wraps freely: no line budget, no squeeze, no clamp (Lh
|
|
592
596
|
2026-09-16 — a height rule on words only distorts them; content that needs
|
|
593
|
-
more rows takes more rows). The group that holds
|
|
597
|
+
more rows takes more rows). The group that holds heading ·
|
|
594
598
|
caption is the head's own wrapping row (above) — no wrapper element, no
|
|
595
599
|
height, only the row gap. */
|
|
596
600
|
.card__head :is(h1, h2, h3) { overflow-wrap: anywhere; }
|
|
@@ -1169,7 +1173,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
1169
1173
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
1170
1174
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
1171
1175
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
1172
|
-
:is(.step, .sheet-stage) { --_handle: var(--sp-1); --sheet-peek: calc(var(--card-pad-rest) + var(--_handle) + 2 * var(--card-gap-rest) + var(--
|
|
1176
|
+
:is(.step, .sheet-stage) { --_handle: var(--sp-1); --sheet-peek: calc(var(--card-pad-rest) + var(--_handle) + 2 * var(--card-gap-rest) + var(--sheet-head-h)); }
|
|
1177
|
+
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
1178
|
+
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
1179
|
+
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
1173
1180
|
|
|
1174
1181
|
/* ---- called sheets (Lh, 2026-09-20): a .step whose .base-content has buttons that each CALL a sheet ----
|
|
1175
1182
|
.sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
|
|
@@ -1281,7 +1288,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1281
1288
|
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
1282
1289
|
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
1283
1290
|
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
1284
|
-
.sheet > .card__head { min-height: var(--
|
|
1291
|
+
.sheet > .card__head { min-height: var(--sheet-head-h); }
|
|
1285
1292
|
.sheet__head {
|
|
1286
1293
|
display: grid;
|
|
1287
1294
|
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
|
@@ -1563,7 +1570,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1563
1570
|
border: 0;
|
|
1564
1571
|
background: transparent;
|
|
1565
1572
|
color: inherit;
|
|
1566
|
-
font: var(--type-body);
|
|
1573
|
+
font: var(--field-font, var(--type-body));
|
|
1567
1574
|
width: 100%;
|
|
1568
1575
|
min-width: 0;
|
|
1569
1576
|
outline-offset: var(--sp-1);
|
|
@@ -1641,7 +1648,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1641
1648
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
1642
1649
|
color: var(--tile-badge-ink, var(--card-3-ink));
|
|
1643
1650
|
}
|
|
1644
|
-
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-
|
|
1651
|
+
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); opacity: 0.7; }
|
|
1645
1652
|
.tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
1646
1653
|
|
|
1647
1654
|
/* plain: no fill — a date box beside a title (Jun / 18) */
|
|
@@ -1702,6 +1709,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1702
1709
|
margin-block: -0.15em;
|
|
1703
1710
|
}
|
|
1704
1711
|
.topbar__actions { margin-left: auto; display: flex; gap: var(--sp-2); }
|
|
1712
|
+
/* at most TWO trailing controls (Lh 2026-09-22): the title must keep its line; a third and
|
|
1713
|
+
beyond is not drawn — more moves belong in a Menu behind one button, or on the base */
|
|
1714
|
+
.topbar__actions > :nth-child(n + 3) { display: none; }
|
|
1705
1715
|
|
|
1706
1716
|
/* a screen whose one big move sits at the top right (home's "ขอความช่วยเหลือ" — Lh 2026-09-09:
|
|
1707
1717
|
no bottom bar on the home screen) puts a primary .btn in the top bar's actions; it stands at
|
|
@@ -1805,7 +1815,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1805
1815
|
text-decoration: none;
|
|
1806
1816
|
justify-self: start; /* as a grid child it hugs its text, never stretches */
|
|
1807
1817
|
}
|
|
1808
|
-
.dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); } /* signal dot: chips, legends
|
|
1818
|
+
.dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); translate: 0 var(--dot-drop, 0.09em); } /* signal dot: chips, legends.
|
|
1819
|
+
The drop: a line box is centred on its ascender room (Thai marks), the letters beside the dot sit lower — the dot rides down to the
|
|
1820
|
+
letters' own middle, in em so it follows the text size (Lh 2026-09-22: "ตัวหนังสือกับไฟเสมอกัน") */
|
|
1809
1821
|
.dot--b { background: var(--dot-b); }
|
|
1810
1822
|
.chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
|
|
1811
1823
|
/* dark-slot inversion lives in card.css (.card--3 :is(...)) */
|
|
@@ -1995,7 +2007,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1995
2007
|
.ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
|
|
1996
2008
|
.ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
|
|
1997
2009
|
.ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
|
|
1998
|
-
.ring--xs .ring__value { font: var(--type-
|
|
2010
|
+
.ring--xs .ring__value { font: var(--type-caption); }
|
|
1999
2011
|
|
|
2000
2012
|
/* ---- track: step progress in a pill — done · done · todo ----
|
|
2001
2013
|
<ol class="track"><li class="track__step is-done">icon</li> …</ol> */
|
|
@@ -2453,7 +2465,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2453
2465
|
.calendar { display: grid; gap: var(--calendar-gap, var(--sp-3)); }
|
|
2454
2466
|
.calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
|
|
2455
2467
|
.calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
|
|
2456
|
-
.calendar__wd { font: var(--calendar-wd-font, var(--type-
|
|
2468
|
+
.calendar__wd { font: var(--calendar-wd-font, var(--type-caption)); color: var(--calendar-wd-ink, var(--card-muted)); padding-block: var(--calendar-wd-pad-y, var(--sp-2)); }
|
|
2457
2469
|
.card--3 .calendar__wd { color: inherit; opacity: 0.7; }
|
|
2458
2470
|
.calendar__day {
|
|
2459
2471
|
width: 100%;
|
|
@@ -2536,7 +2548,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2536
2548
|
padding: 0;
|
|
2537
2549
|
background: transparent;
|
|
2538
2550
|
color: inherit;
|
|
2539
|
-
font: 700 var(--fs-2xl)/1.25 var(--font-display);
|
|
2551
|
+
font: var(--composer-input-font, 700 var(--fs-2xl)/1.25 var(--font-display));
|
|
2540
2552
|
letter-spacing: var(--track-h2);
|
|
2541
2553
|
outline: none;
|
|
2542
2554
|
}
|
|
@@ -2691,8 +2703,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2691
2703
|
.kv { margin: 0; display: grid; }
|
|
2692
2704
|
.kv__row { display: flex; align-items: center; justify-content: space-between; gap: var(--kv-row-gap, var(--sp-3)); min-height: var(--kv-row-min-h, var(--tap)); }
|
|
2693
2705
|
.kv--lined > .kv__row + .kv__row { border-top: var(--border-w) solid var(--hairline); }
|
|
2694
|
-
.kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-
|
|
2695
|
-
.card--3 .kv__key { color: inherit; opacity: 0.7; }
|
|
2706
|
+
.kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-ink)); } /* full ink (Lh 2026-09-22): a key is read like the value; muted = inactive, not "secondary" */
|
|
2696
2707
|
.kv__val { margin: 0; font: var(--kv-val-font, var(--type-title)); display: inline-flex; align-items: center; gap: var(--kv-val-gap, var(--sp-2)); text-align: right; min-width: 0; }
|
|
2697
2708
|
|
|
2698
2709
|
/* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
|
|
@@ -2786,8 +2797,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2786
2797
|
}
|
|
2787
2798
|
.timeline__mark {
|
|
2788
2799
|
justify-self: start;
|
|
2789
|
-
font: var(--timeline-mark-font, var(--type-
|
|
2790
|
-
letter-spacing: var(--track-overline);
|
|
2800
|
+
font: var(--timeline-mark-font, var(--type-caption));
|
|
2791
2801
|
text-transform: uppercase;
|
|
2792
2802
|
padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
|
|
2793
2803
|
border-radius: var(--timeline-mark-radius, var(--r-chip));
|
|
@@ -2827,7 +2837,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2827
2837
|
|
|
2828
2838
|
/* ---- cover: the image IS the card; text over a scrim ----
|
|
2829
2839
|
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
2830
|
-
<div class="card__head"><
|
|
2840
|
+
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
2831
2841
|
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
2832
2842
|
<div class="row">…meta…</div></article> */
|
|
2833
2843
|
.card--cover {
|
|
@@ -2852,7 +2862,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2852
2862
|
border-radius: 0;
|
|
2853
2863
|
min-height: 0;
|
|
2854
2864
|
}
|
|
2855
|
-
/* scrim: dark at both ends so
|
|
2865
|
+
/* scrim: dark at both ends so the meta stays readable */
|
|
2856
2866
|
.card--cover::before {
|
|
2857
2867
|
content: "";
|
|
2858
2868
|
position: absolute;
|
|
@@ -2874,7 +2884,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2874
2884
|
}
|
|
2875
2885
|
.quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
|
|
2876
2886
|
.quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
|
|
2877
|
-
.quote__by { font: var(--quote-by-font, var(--type-
|
|
2887
|
+
.quote__by { font: var(--quote-by-font, var(--type-caption)); color: var(--quote-by-ink, var(--card-muted)); }
|
|
2878
2888
|
.card--3 .quote__by { color: inherit; opacity: 0.7; }
|
|
2879
2889
|
|
|
2880
2890
|
/* ---- tile grid: big square tiles — icon top, label bottom ----
|
package/dist/cards/CardHead.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export interface CardHeadProps extends ComponentProps<'div'> {
|
|
|
3
3
|
/** the heading (a string becomes an h2 at level `t-h2`) — wraps freely, as many lines as the words need */
|
|
4
4
|
heading?: ReactNode;
|
|
5
5
|
/** small caps above the heading ("Step 1 of 4", "Time bank") */
|
|
6
|
-
overline?: ReactNode;
|
|
7
6
|
/** the muted line under the heading (a date, a subtitle) */
|
|
8
7
|
caption?: ReactNode;
|
|
9
8
|
/** the contextual thing at the far right: a chip, an IconBtn (more), a Link, a small Ring, an Avatar, a Badge (a count in a stack head) */
|
|
@@ -15,7 +14,7 @@ export interface CardHeadProps extends ComponentProps<'div'> {
|
|
|
15
14
|
}
|
|
16
15
|
/**
|
|
17
16
|
* CardHead — the card's head: the heading on the left takes the room, the contextual info hugs the far right.
|
|
18
|
-
* Use `heading` (+ `
|
|
19
|
-
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the
|
|
17
|
+
* Use `heading` (+ `caption`) and `end` for the standard head, or pass children for a custom one
|
|
18
|
+
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the caption is a span on a row of its own (card.css). Never put buttons here — they go in the CardFoot.
|
|
20
19
|
*/
|
|
21
|
-
export declare function CardHead({ heading,
|
|
20
|
+
export declare function CardHead({ heading, caption, end, top, level, className, children, ...rest }: CardHeadProps): import("react").JSX.Element;
|
package/dist/cards/CardHead.js
CHANGED
|
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
/**
|
|
4
4
|
* CardHead — the card's head: the heading on the left takes the room, the contextual info hugs the far right.
|
|
5
|
-
* Use `heading` (+ `
|
|
6
|
-
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the
|
|
5
|
+
* Use `heading` (+ `caption`) and `end` for the standard head, or pass children for a custom one
|
|
6
|
+
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the caption is a span on a row of its own (card.css). Never put buttons here — they go in the CardFoot.
|
|
7
7
|
*/
|
|
8
|
-
export function CardHead({ heading,
|
|
8
|
+
export function CardHead({ heading, caption, end, top, level = 'h2', className, children, ...rest }) {
|
|
9
9
|
const H = level === 'h1' ? 'h1' : 'h2';
|
|
10
|
-
return (_jsxs("div", { className: cx('card__head', top && 'card__head--top', className), ...rest, children: [
|
|
10
|
+
return (_jsxs("div", { className: cx('card__head', top && 'card__head--top', className), ...rest, children: [heading != null && _jsx(H, { className: level === 'display' ? 't-display' : `t-${level}`, children: heading }), caption != null && _jsx("span", { className: "t-caption t-muted", children: caption }), children, end] }));
|
|
11
11
|
}
|
package/dist/cards/Stat.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface StatProps extends ComponentProps<'div'> {
|
|
|
5
5
|
/** centred: the one number of a sheet or a stage, under a centred Avatar */
|
|
6
6
|
centre?: boolean;
|
|
7
7
|
}
|
|
8
|
-
/** Stat — an inline stat block:
|
|
8
|
+
/** Stat — an inline stat block: a label, a big `Text level="stat"` number, a caption; StatRows put a Delta beside the label. */
|
|
9
9
|
export declare function Stat({ grow, centre, className, ...rest }: StatProps): import("react").JSX.Element;
|
|
10
10
|
export interface StatRowProps extends ComponentProps<'div'> {
|
|
11
11
|
}
|
package/dist/cards/Stat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
-
/** Stat — an inline stat block:
|
|
3
|
+
/** Stat — an inline stat block: a label, a big `Text level="stat"` number, a caption; StatRows put a Delta beside the label. */
|
|
4
4
|
export function Stat({ grow, centre, className, ...rest }) {
|
|
5
5
|
return _jsx("div", { className: cx('stat', grow && 'grow', centre && 'stat--centre', className), ...rest });
|
|
6
6
|
}
|
package/dist/media/Quote.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export interface QuoteProps extends ComponentProps<'blockquote'> {
|
|
|
7
7
|
/** h2-size text instead of h1 */
|
|
8
8
|
sm?: boolean;
|
|
9
9
|
}
|
|
10
|
-
/** Quote — a giant opening mark, big text,
|
|
10
|
+
/** Quote — a giant opening mark, big text, a small attribution. */
|
|
11
11
|
export declare function Quote({ text, by, sm, className, ...rest }: QuoteProps): import("react").JSX.Element;
|
package/dist/media/Quote.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
-
/** Quote — a giant opening mark, big text,
|
|
3
|
+
/** Quote — a giant opening mark, big text, a small attribution. */
|
|
4
4
|
export function Quote({ text, by, sm, className, ...rest }) {
|
|
5
5
|
return (_jsxs("blockquote", { className: cx('quote', sm && 'quote--sm', className), ...rest, children: [_jsx("p", { className: "quote__text", children: text }), by != null && _jsx("footer", { className: "quote__by", children: by })] }));
|
|
6
6
|
}
|
|
@@ -2,7 +2,7 @@ import type { ComponentProps } from 'react';
|
|
|
2
2
|
export interface BaseContentProps extends ComponentProps<'section'> {
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
* BaseContent — words straight on the base, no card around them:
|
|
5
|
+
* BaseContent — words straight on the base, no card around them: a heading, a paragraph,
|
|
6
6
|
* maybe a CtaPack. Content has three homes (Card · Sheet · the base); this is the third one, named.
|
|
7
7
|
* Was Section (and, inside a Step, StepCard) until 2026-09-16 — one block, one name.
|
|
8
8
|
* In a Step it is the reading block (first child; the step's MOVE goes to the ActionBar, never here). It can move two ways
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
/**
|
|
4
|
-
* BaseContent — words straight on the base, no card around them:
|
|
4
|
+
* BaseContent — words straight on the base, no card around them: a heading, a paragraph,
|
|
5
5
|
* maybe a CtaPack. Content has three homes (Card · Sheet · the base); this is the third one, named.
|
|
6
6
|
* Was Section (and, inside a Step, StepCard) until 2026-09-16 — one block, one name.
|
|
7
7
|
* In a Step it is the reading block (first child; the step's MOVE goes to the ActionBar, never here). It can move two ways
|
|
@@ -4,8 +4,9 @@ export interface TopBarProps extends Omit<ComponentProps<'header'>, 'title'> {
|
|
|
4
4
|
title: ReactNode;
|
|
5
5
|
/** a leading control — usually `<IconBtn sm icon="arrow-left" label="Back" />` */
|
|
6
6
|
back?: ReactNode;
|
|
7
|
-
/** trailing controls, right-aligned
|
|
8
|
-
*
|
|
7
|
+
/** trailing controls, right-aligned, AT MOST TWO (Lh 2026-09-22 — a third is not drawn and warns once): chips,
|
|
8
|
+
* small icon buttons, a Dropdown — or the screen's one big move as a `Btn primary` (home's ขอความช่วยเหลือ,
|
|
9
|
+
* --tap-lg tall there; Lh 2026-09-09: no bottom bar on home). More moves go in a Menu behind one button. */
|
|
9
10
|
actions?: ReactNode;
|
|
10
11
|
}
|
|
11
12
|
/**
|
package/dist/scaffold/TopBar.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
2
3
|
import { cx } from '../cx.js';
|
|
3
4
|
/**
|
|
4
5
|
* TopBar — the screen's title bar: back · TITLE · actions. Same 60px floor as the AppBar.
|
|
5
6
|
* One per screen, first child of the Screen; a Step that follows it touches it.
|
|
6
7
|
*/
|
|
7
8
|
export function TopBar({ title, back, actions, className, children, ...rest }) {
|
|
8
|
-
|
|
9
|
+
const acts = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const n = acts.current?.childElementCount ?? 0;
|
|
12
|
+
if (n > MAX_ACTIONS && !warned) {
|
|
13
|
+
warned = true;
|
|
14
|
+
console.warn(`cardds TopBar: ${n} actions — at most ${MAX_ACTIONS} are drawn; put the rest in a Menu behind one button`);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return (_jsxs("header", { className: cx('topbar', className), ...rest, children: [back, _jsx("h1", { className: "t-h1", children: title }), children, actions != null && _jsx("div", { className: "topbar__actions", ref: acts, children: actions })] }));
|
|
9
18
|
}
|
|
19
|
+
const MAX_ACTIONS = 2;
|
|
20
|
+
let warned = false;
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -105,6 +105,26 @@ export function Sheet({ state, defaultState = 'half', onStateChange, states, han
|
|
|
105
105
|
ro.observe(el.parentElement);
|
|
106
106
|
return () => ro.disconnect();
|
|
107
107
|
}, [reveal]);
|
|
108
|
+
/* the peek strip holds the handle and the WHOLE title row. CSS gives the box the strip for a one-line head (--sheet-peek in
|
|
109
|
+
sheet.css); when the head is taller than that floor — a title wrapped on a narrow phone at a large text size — the sheet
|
|
110
|
+
measures its head and publishes the real strip on its box, so nothing of the title is under the cut (2026-09-22, with the
|
|
111
|
+
22-base type scale). Removed when the head fits the floor again, so the CSS number is the one in force. */
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const el = ref.current, box = el?.parentElement, head = el?.querySelector(':scope > .card__head, :scope > .sheet__head');
|
|
114
|
+
if (!el || !box || !head || typeof ResizeObserver === 'undefined')
|
|
115
|
+
return;
|
|
116
|
+
const publish = () => {
|
|
117
|
+
box.style.removeProperty('--sheet-peek'); // read the CSS strip, not our own
|
|
118
|
+
const cs = getComputedStyle(el);
|
|
119
|
+
const strip = parseFloat(cs.getPropertyValue('--sheet-peek')) || 0, floor = parseFloat(cs.getPropertyValue('--sheet-head-h')) || 0;
|
|
120
|
+
const over = head.getBoundingClientRect().height - floor;
|
|
121
|
+
if (over > 0.5)
|
|
122
|
+
box.style.setProperty('--sheet-peek', `${strip + over}px`);
|
|
123
|
+
};
|
|
124
|
+
const ro = new ResizeObserver(publish);
|
|
125
|
+
ro.observe(head);
|
|
126
|
+
return () => { ro.disconnect(); box.style.removeProperty('--sheet-peek'); };
|
|
127
|
+
}, []);
|
|
108
128
|
const focus = (e) => {
|
|
109
129
|
onFocus?.(e);
|
|
110
130
|
if (current !== 'peek' || e.target.closest('.sheet__handle'))
|
package/dist/type/Text.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type TextLevel = 'display' | 'h1' | 'h2' | 'title' | 'body' | 'label' | 'caption' | '
|
|
1
|
+
export type TextLevel = 'display' | 'h1' | 'h2' | 'title' | 'body' | 'label' | 'caption' | 'stat';
|
|
2
2
|
export interface TextProps extends React.HTMLAttributes<HTMLElement> {
|
|
3
3
|
/** the element it renders (React 19 passes `ref` as a prop) */
|
|
4
4
|
ref?: React.Ref<HTMLElement>;
|
|
5
|
-
/** the type level: display (hero words) · h1 · h2 (a card's heading) · title · body · label · caption ·
|
|
5
|
+
/** the type level: display (hero words) · h1 · h2 (a card's heading) · title · body · label · caption · stat (a big number) */
|
|
6
6
|
level: TextLevel;
|
|
7
7
|
/** stat only: the one giant number on a sheet (`.t-stat--xl`) */
|
|
8
8
|
xl?: boolean;
|
package/dist/type/Text.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
const DEFAULT_AS = {
|
|
4
|
-
display: 'h2', h1: 'h2', h2: 'h2', title: 'p', body: 'p', label: 'span', caption: 'span',
|
|
4
|
+
display: 'h2', h1: 'h2', h2: 'h2', title: 'p', body: 'p', label: 'span', caption: 'span', stat: 'p',
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
7
|
* Text — one of cardds's type levels (`.t-display` … `.t-stat`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"cardds.css",
|
|
49
49
|
"cardds.js",
|
|
50
50
|
"icons.svg",
|
|
51
|
-
"README.md"
|
|
51
|
+
"README.md",
|
|
52
|
+
"CHANGELOG.md"
|
|
52
53
|
],
|
|
53
54
|
"peerDependencies": {
|
|
54
55
|
"react": ">=18",
|