@usableapp/cardds 0.4.2 → 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 +35 -0
- package/README.md +14 -13
- package/css/actions.css +6 -1
- package/css/base.css +9 -11
- 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 +60 -55
- 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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
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.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
|
+
|
|
5
40
|
## 0.4.2 — 2026-09-22
|
|
6
41
|
|
|
7
42
|
### Words on the base: a picture and the dots
|
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,10 +115,10 @@ 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) */
|
|
121
119
|
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
122
120
|
the "where am I" dots under the words, centred — the same dots the centred card has under it */
|
|
123
|
-
.base-content > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; max-width: 100%; object-fit: contain; justify-self: start; }
|
|
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 */
|
|
124
122
|
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
125
123
|
.base-content > .pager__at { justify-self: center; }
|
|
126
124
|
.base-content .cta-pack {
|
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,10 +491,10 @@ 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
495
|
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
496
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; max-width: 100%; object-fit: contain; justify-self: start; }
|
|
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
498
|
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
499
499
|
.base-content > .pager__at { justify-self: center; }
|
|
500
500
|
.base-content .cta-pack {
|
|
@@ -561,19 +561,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
561
561
|
multi-line title can opt back out with .card__head--top. */
|
|
562
562
|
.card__head {
|
|
563
563
|
display: flex;
|
|
564
|
-
flex-wrap: wrap; /* the caption
|
|
564
|
+
flex-wrap: wrap; /* the caption takes a line of its own — no wrapper div */
|
|
565
565
|
align-items: center;
|
|
566
566
|
justify-content: space-between;
|
|
567
567
|
column-gap: var(--card-head-col-gap, var(--sp-3));
|
|
568
568
|
row-gap: var(--card-head-row-gap, var(--sp-0));
|
|
569
569
|
}
|
|
570
570
|
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
571
|
-
<div class="card__head"><
|
|
572
|
-
The heading and the far-right thing share the first row;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
.card__head > :is(.t-
|
|
576
|
-
.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; }
|
|
577
576
|
.card__head--top { align-items: flex-start; }
|
|
578
577
|
|
|
579
578
|
.card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
|
|
@@ -595,7 +594,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
595
594
|
/* Skeleton head — the heading in a card's OWN head, and only there.
|
|
596
595
|
The heading wraps freely: no line budget, no squeeze, no clamp (Lh
|
|
597
596
|
2026-09-16 — a height rule on words only distorts them; content that needs
|
|
598
|
-
more rows takes more rows). The group that holds
|
|
597
|
+
more rows takes more rows). The group that holds heading ·
|
|
599
598
|
caption is the head's own wrapping row (above) — no wrapper element, no
|
|
600
599
|
height, only the row gap. */
|
|
601
600
|
.card__head :is(h1, h2, h3) { overflow-wrap: anywhere; }
|
|
@@ -1174,7 +1173,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
1174
1173
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
1175
1174
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
1176
1175
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
1177
|
-
: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))); }
|
|
1178
1180
|
|
|
1179
1181
|
/* ---- called sheets (Lh, 2026-09-20): a .step whose .base-content has buttons that each CALL a sheet ----
|
|
1180
1182
|
.sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
|
|
@@ -1286,7 +1288,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1286
1288
|
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
1287
1289
|
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
1288
1290
|
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
1289
|
-
.sheet > .card__head { min-height: var(--
|
|
1291
|
+
.sheet > .card__head { min-height: var(--sheet-head-h); }
|
|
1290
1292
|
.sheet__head {
|
|
1291
1293
|
display: grid;
|
|
1292
1294
|
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
|
@@ -1568,7 +1570,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1568
1570
|
border: 0;
|
|
1569
1571
|
background: transparent;
|
|
1570
1572
|
color: inherit;
|
|
1571
|
-
font: var(--type-body);
|
|
1573
|
+
font: var(--field-font, var(--type-body));
|
|
1572
1574
|
width: 100%;
|
|
1573
1575
|
min-width: 0;
|
|
1574
1576
|
outline-offset: var(--sp-1);
|
|
@@ -1646,7 +1648,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1646
1648
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
1647
1649
|
color: var(--tile-badge-ink, var(--card-3-ink));
|
|
1648
1650
|
}
|
|
1649
|
-
.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; }
|
|
1650
1652
|
.tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
1651
1653
|
|
|
1652
1654
|
/* plain: no fill — a date box beside a title (Jun / 18) */
|
|
@@ -1707,6 +1709,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1707
1709
|
margin-block: -0.15em;
|
|
1708
1710
|
}
|
|
1709
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; }
|
|
1710
1715
|
|
|
1711
1716
|
/* a screen whose one big move sits at the top right (home's "ขอความช่วยเหลือ" — Lh 2026-09-09:
|
|
1712
1717
|
no bottom bar on the home screen) puts a primary .btn in the top bar's actions; it stands at
|
|
@@ -1810,7 +1815,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1810
1815
|
text-decoration: none;
|
|
1811
1816
|
justify-self: start; /* as a grid child it hugs its text, never stretches */
|
|
1812
1817
|
}
|
|
1813
|
-
.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: "ตัวหนังสือกับไฟเสมอกัน") */
|
|
1814
1821
|
.dot--b { background: var(--dot-b); }
|
|
1815
1822
|
.chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
|
|
1816
1823
|
/* dark-slot inversion lives in card.css (.card--3 :is(...)) */
|
|
@@ -2000,7 +2007,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2000
2007
|
.ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
|
|
2001
2008
|
.ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
|
|
2002
2009
|
.ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
|
|
2003
|
-
.ring--xs .ring__value { font: var(--type-
|
|
2010
|
+
.ring--xs .ring__value { font: var(--type-caption); }
|
|
2004
2011
|
|
|
2005
2012
|
/* ---- track: step progress in a pill — done · done · todo ----
|
|
2006
2013
|
<ol class="track"><li class="track__step is-done">icon</li> …</ol> */
|
|
@@ -2458,7 +2465,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2458
2465
|
.calendar { display: grid; gap: var(--calendar-gap, var(--sp-3)); }
|
|
2459
2466
|
.calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
|
|
2460
2467
|
.calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
|
|
2461
|
-
.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)); }
|
|
2462
2469
|
.card--3 .calendar__wd { color: inherit; opacity: 0.7; }
|
|
2463
2470
|
.calendar__day {
|
|
2464
2471
|
width: 100%;
|
|
@@ -2541,7 +2548,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2541
2548
|
padding: 0;
|
|
2542
2549
|
background: transparent;
|
|
2543
2550
|
color: inherit;
|
|
2544
|
-
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));
|
|
2545
2552
|
letter-spacing: var(--track-h2);
|
|
2546
2553
|
outline: none;
|
|
2547
2554
|
}
|
|
@@ -2696,8 +2703,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2696
2703
|
.kv { margin: 0; display: grid; }
|
|
2697
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)); }
|
|
2698
2705
|
.kv--lined > .kv__row + .kv__row { border-top: var(--border-w) solid var(--hairline); }
|
|
2699
|
-
.kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-
|
|
2700
|
-
.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" */
|
|
2701
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; }
|
|
2702
2708
|
|
|
2703
2709
|
/* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
|
|
@@ -2791,8 +2797,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2791
2797
|
}
|
|
2792
2798
|
.timeline__mark {
|
|
2793
2799
|
justify-self: start;
|
|
2794
|
-
font: var(--timeline-mark-font, var(--type-
|
|
2795
|
-
letter-spacing: var(--track-overline);
|
|
2800
|
+
font: var(--timeline-mark-font, var(--type-caption));
|
|
2796
2801
|
text-transform: uppercase;
|
|
2797
2802
|
padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
|
|
2798
2803
|
border-radius: var(--timeline-mark-radius, var(--r-chip));
|
|
@@ -2832,7 +2837,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2832
2837
|
|
|
2833
2838
|
/* ---- cover: the image IS the card; text over a scrim ----
|
|
2834
2839
|
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
2835
|
-
<div class="card__head"><
|
|
2840
|
+
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
2836
2841
|
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
2837
2842
|
<div class="row">…meta…</div></article> */
|
|
2838
2843
|
.card--cover {
|
|
@@ -2857,7 +2862,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2857
2862
|
border-radius: 0;
|
|
2858
2863
|
min-height: 0;
|
|
2859
2864
|
}
|
|
2860
|
-
/* scrim: dark at both ends so
|
|
2865
|
+
/* scrim: dark at both ends so the meta stays readable */
|
|
2861
2866
|
.card--cover::before {
|
|
2862
2867
|
content: "";
|
|
2863
2868
|
position: absolute;
|
|
@@ -2879,7 +2884,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2879
2884
|
}
|
|
2880
2885
|
.quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
|
|
2881
2886
|
.quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
|
|
2882
|
-
.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)); }
|
|
2883
2888
|
.card--3 .quote__by { color: inherit; opacity: 0.7; }
|
|
2884
2889
|
|
|
2885
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.",
|