@usableapp/cardds 0.1.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.
Files changed (170) hide show
  1. package/README.md +562 -0
  2. package/cardds.css +18 -0
  3. package/cardds.js +473 -0
  4. package/css/actions.css +292 -0
  5. package/css/base.css +128 -0
  6. package/css/card.css +283 -0
  7. package/css/choice.css +297 -0
  8. package/css/fonts.css +37 -0
  9. package/css/forms.css +64 -0
  10. package/css/journey.css +61 -0
  11. package/css/layover.css +243 -0
  12. package/css/lists.css +142 -0
  13. package/css/media.css +232 -0
  14. package/css/numbers.css +258 -0
  15. package/css/palette.css +112 -0
  16. package/css/people.css +79 -0
  17. package/css/sheet.css +295 -0
  18. package/css/stack.css +262 -0
  19. package/css/step.css +71 -0
  20. package/css/tokens.css +177 -0
  21. package/dist/actions/Btn.d.ts +311 -0
  22. package/dist/actions/Btn.js +14 -0
  23. package/dist/actions/BtnRow.d.ts +5 -0
  24. package/dist/actions/BtnRow.js +6 -0
  25. package/dist/actions/Chip.d.ts +328 -0
  26. package/dist/actions/Chip.js +25 -0
  27. package/dist/actions/Dot.d.ts +7 -0
  28. package/dist/actions/Dot.js +6 -0
  29. package/dist/actions/Dropdown.d.ts +34 -0
  30. package/dist/actions/Dropdown.js +19 -0
  31. package/dist/actions/Fab.d.ts +9 -0
  32. package/dist/actions/Fab.js +7 -0
  33. package/dist/actions/IconBtn.d.ts +308 -0
  34. package/dist/actions/IconBtn.js +8 -0
  35. package/dist/actions/Link.d.ts +301 -0
  36. package/dist/actions/Link.js +8 -0
  37. package/dist/actions/Segment.d.ts +11 -0
  38. package/dist/actions/Segment.js +10 -0
  39. package/dist/cardds.css +3068 -0
  40. package/dist/cards/Card.d.ts +310 -0
  41. package/dist/cards/Card.js +14 -0
  42. package/dist/cards/CardFoot.d.ts +5 -0
  43. package/dist/cards/CardFoot.js +6 -0
  44. package/dist/cards/CardHead.d.ts +21 -0
  45. package/dist/cards/CardHead.js +11 -0
  46. package/dist/cards/CardParts.d.ts +25 -0
  47. package/dist/cards/CardParts.js +26 -0
  48. package/dist/cards/Placeholder.d.ts +9 -0
  49. package/dist/cards/Placeholder.js +6 -0
  50. package/dist/cards/Stat.d.ts +17 -0
  51. package/dist/cards/Stat.js +14 -0
  52. package/dist/choice/Calendar.d.ts +23 -0
  53. package/dist/choice/Calendar.js +7 -0
  54. package/dist/choice/Check.d.ts +14 -0
  55. package/dist/choice/Check.js +10 -0
  56. package/dist/choice/DayStrip.d.ts +16 -0
  57. package/dist/choice/DayStrip.js +10 -0
  58. package/dist/choice/Mood.d.ts +11 -0
  59. package/dist/choice/Mood.js +6 -0
  60. package/dist/choice/Slider.d.ts +11 -0
  61. package/dist/choice/Slider.js +14 -0
  62. package/dist/cx.d.ts +2 -0
  63. package/dist/cx.js +2 -0
  64. package/dist/forms/AddRow.d.ts +5 -0
  65. package/dist/forms/AddRow.js +7 -0
  66. package/dist/forms/Composer.d.ts +13 -0
  67. package/dist/forms/Composer.js +6 -0
  68. package/dist/forms/Field.d.ts +17 -0
  69. package/dist/forms/Field.js +10 -0
  70. package/dist/forms/FileBtn.d.ts +18 -0
  71. package/dist/forms/FileBtn.js +10 -0
  72. package/dist/forms/Pin.d.ts +19 -0
  73. package/dist/forms/Pin.js +27 -0
  74. package/dist/index.d.ts +72 -0
  75. package/dist/index.js +75 -0
  76. package/dist/journey/NoteRow.d.ts +7 -0
  77. package/dist/journey/NoteRow.js +7 -0
  78. package/dist/journey/Route.d.ts +18 -0
  79. package/dist/journey/Route.js +8 -0
  80. package/dist/journey/TileBadge.d.ts +15 -0
  81. package/dist/journey/TileBadge.js +6 -0
  82. package/dist/layover/Banner.d.ts +9 -0
  83. package/dist/layover/Banner.js +6 -0
  84. package/dist/layover/Callout.d.ts +10 -0
  85. package/dist/layover/Callout.js +6 -0
  86. package/dist/layover/Deck.d.ts +9 -0
  87. package/dist/layover/Deck.js +10 -0
  88. package/dist/lists/Kv.d.ts +27 -0
  89. package/dist/lists/Kv.js +19 -0
  90. package/dist/lists/Legend.d.ts +11 -0
  91. package/dist/lists/Legend.js +11 -0
  92. package/dist/lists/Row.d.ts +319 -0
  93. package/dist/lists/Row.js +17 -0
  94. package/dist/lists/Timeline.d.ts +11 -0
  95. package/dist/lists/Timeline.js +10 -0
  96. package/dist/media/MapArea.d.ts +20 -0
  97. package/dist/media/MapArea.js +11 -0
  98. package/dist/media/Mosaic.d.ts +11 -0
  99. package/dist/media/Mosaic.js +10 -0
  100. package/dist/media/Postcard.d.ts +20 -0
  101. package/dist/media/Postcard.js +10 -0
  102. package/dist/media/Quote.d.ts +11 -0
  103. package/dist/media/Quote.js +6 -0
  104. package/dist/media/Tile.d.ts +320 -0
  105. package/dist/media/Tile.js +16 -0
  106. package/dist/numbers/Badge.d.ts +7 -0
  107. package/dist/numbers/Badge.js +6 -0
  108. package/dist/numbers/Band.d.ts +16 -0
  109. package/dist/numbers/Band.js +10 -0
  110. package/dist/numbers/Bars.d.ts +14 -0
  111. package/dist/numbers/Bars.js +10 -0
  112. package/dist/numbers/DotGrid.d.ts +12 -0
  113. package/dist/numbers/DotGrid.js +6 -0
  114. package/dist/numbers/Picker.d.ts +13 -0
  115. package/dist/numbers/Picker.js +18 -0
  116. package/dist/numbers/Ring.d.ts +15 -0
  117. package/dist/numbers/Ring.js +6 -0
  118. package/dist/numbers/Track.d.ts +12 -0
  119. package/dist/numbers/Track.js +10 -0
  120. package/dist/people/Avatar.d.ts +314 -0
  121. package/dist/people/Avatar.js +13 -0
  122. package/dist/people/AvatarPick.d.ts +18 -0
  123. package/dist/people/AvatarPick.js +10 -0
  124. package/dist/scaffold/ActionBar.d.ts +21 -0
  125. package/dist/scaffold/ActionBar.js +16 -0
  126. package/dist/scaffold/AppBar.d.ts +18 -0
  127. package/dist/scaffold/AppBar.js +10 -0
  128. package/dist/scaffold/Centre.d.ts +30 -0
  129. package/dist/scaffold/Centre.js +41 -0
  130. package/dist/scaffold/EmptyState.d.ts +14 -0
  131. package/dist/scaffold/EmptyState.js +8 -0
  132. package/dist/scaffold/FilterRow.d.ts +5 -0
  133. package/dist/scaffold/FilterRow.js +6 -0
  134. package/dist/scaffold/FloatBar.d.ts +310 -0
  135. package/dist/scaffold/FloatBar.js +15 -0
  136. package/dist/scaffold/PagerAt.d.ts +11 -0
  137. package/dist/scaffold/PagerAt.js +6 -0
  138. package/dist/scaffold/Screen.d.ts +299 -0
  139. package/dist/scaffold/Screen.js +11 -0
  140. package/dist/scaffold/Section.d.ts +12 -0
  141. package/dist/scaffold/Section.js +13 -0
  142. package/dist/scaffold/TopBar.d.ts +15 -0
  143. package/dist/scaffold/TopBar.js +9 -0
  144. package/dist/sheets/CardBack.d.ts +7 -0
  145. package/dist/sheets/CardBack.js +7 -0
  146. package/dist/sheets/Drawer.d.ts +17 -0
  147. package/dist/sheets/Drawer.js +16 -0
  148. package/dist/sheets/Sheet.d.ts +21 -0
  149. package/dist/sheets/Sheet.js +13 -0
  150. package/dist/sheets/SheetBody.d.ts +5 -0
  151. package/dist/sheets/SheetBody.js +6 -0
  152. package/dist/sheets/SheetHead.d.ts +13 -0
  153. package/dist/sheets/SheetHead.js +6 -0
  154. package/dist/sheets/SheetStack.d.ts +21 -0
  155. package/dist/sheets/SheetStack.js +14 -0
  156. package/dist/sheets/SheetStage.d.ts +20 -0
  157. package/dist/sheets/SheetStage.js +11 -0
  158. package/dist/sheets/Step.d.ts +15 -0
  159. package/dist/sheets/Step.js +15 -0
  160. package/dist/type/Icon.d.ts +16 -0
  161. package/dist/type/Icon.js +12 -0
  162. package/dist/type/Text.d.ts +302 -0
  163. package/dist/type/Text.js +16 -0
  164. package/dist/type/icons.d.ts +6 -0
  165. package/dist/type/icons.js +2 -0
  166. package/fonts/LICENSES.md +44 -0
  167. package/fonts/fc-pride-medium.otf +0 -0
  168. package/fonts/pk-nonthaburi-demo.ttf +0 -0
  169. package/icons.svg +61 -0
  170. package/package.json +62 -0
@@ -0,0 +1,292 @@
1
+ /* ============================================================
2
+ cardds/actions.css — contextual commands around/inside cards:
3
+ topbar, chips, buttons, selects, bottom CTA pack
4
+ ============================================================ */
5
+
6
+ /* ---- the bars across the top of a screen: one height, whatever they carry ----
7
+ .topbar (screen title) and .appbar (app context) are the same bar with
8
+ different contents, so the height is declared here once rather than left to
9
+ whichever control happens to be tallest. Measured before this rule existed:
10
+ a title-only topbar 46px, one with an .icon-btn--sm 60px, with a full-size
11
+ .icon-btn 64px, with a .btn 70px. --header-h is a floor, not a cap — a bar
12
+ given a deliberately taller control still grows instead of clipping it. */
13
+ .topbar, .appbar {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--sp-3);
17
+ padding-block: var(--sp-2);
18
+ min-height: var(--header-h);
19
+ }
20
+
21
+ /* ---- top bar: back + screen title ---- */
22
+ .topbar .t-h1 {
23
+ text-transform: uppercase;
24
+ flex: 1;
25
+ min-width: 0;
26
+ overflow: hidden;
27
+ text-overflow: ellipsis;
28
+ white-space: nowrap;
29
+ padding-block: 0.15em; /* Thai marks survive the clip — see card.css, the head rule */
30
+ margin-block: -0.15em;
31
+ }
32
+ .topbar__actions { margin-left: auto; display: flex; gap: var(--sp-2); }
33
+
34
+ /* a screen whose one big move sits at the top right (home's "ขอความช่วยเหลือ" — Lh 2026-09-09:
35
+ no bottom bar on the home screen) puts a primary .btn in the top bar's actions; it stands at
36
+ the hero height so it is the easiest thing on the screen to hit */
37
+ .topbar__actions > .btn--primary { min-height: var(--tap-lg); }
38
+
39
+ /* ---- appbar: app context/brand switcher (logo · two-line label · chevron) ---- */
40
+ .appbar {
41
+ position: fixed;
42
+ inset-inline: 0;
43
+ top: 0;
44
+ max-width: var(--screen-w);
45
+ margin-inline: auto;
46
+ padding-inline: var(--screen-pad); /* fixed + full-bleed: the screen's own inset, so the bar's contents line up with the title bar under it (Lh 2026-09-15) */
47
+ z-index: 40; /* topmost — card animations never cover it */
48
+ background: var(--surface); /* solid: sliding cards never show through */
49
+ }
50
+ /* fixed, so it covers the top of the screen it sits in — reserve its height
51
+ there, from the same token, the way the bottom bars reserve theirs. The
52
+ breathing under it is published: a .step cancels it to touch the bar. */
53
+ .screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
54
+ .appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
55
+ .appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
56
+ .appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
57
+ .appbar__title { font: var(--type-label); }
58
+ .appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
59
+ /* the bar's trailing controls (one or two: a labelled switch chip, a wallet button — home,
60
+ Lh 2026-09-13) stand on the tap floor: a chip that is a switch grows to --tap here */
61
+ .appbar .chip--toggle { min-height: var(--tap); }
62
+
63
+ /* ---- icon button (round) ---- */
64
+ .icon-btn {
65
+ width: var(--tap); height: var(--tap);
66
+ flex: none;
67
+ border-radius: var(--r-chip);
68
+ border: var(--border-w) solid var(--outline); /* see .btn — same reason */
69
+ background: var(--action-bg);
70
+ color: var(--action-ink);
71
+ display: grid;
72
+ place-items: center;
73
+ font-size: var(--fs-lg);
74
+ line-height: 1;
75
+ }
76
+ .icon-btn--invert { background: var(--accent); color: var(--on-accent); border-color: transparent; }
77
+ .icon-btn--sm { width: var(--tap-sm); height: var(--tap-sm); font-size: var(--fs-title); }
78
+ /* fab: the square floating action, bottom-right of a stage or screen */
79
+ .fab { width: var(--fab); height: var(--fab); border-radius: var(--r-card); position: absolute; right: var(--sp-4); bottom: calc(var(--sp-5) + var(--bar-h) + var(--sp-3)); box-shadow: var(--shadow-float); z-index: 31; }
80
+
81
+ /* ---- buttons ---- */
82
+ .btn {
83
+ /* --outline, not --action-border: same reason as .chip — on a palette where
84
+ --card-1-bg equals --action-bg a transparent border makes a plain button
85
+ invisible on a white card. --primary and --quiet set their own edge. */
86
+ border: var(--border-w) solid var(--outline);
87
+ background: var(--action-bg);
88
+ color: var(--action-ink);
89
+ border-radius: var(--r-chip);
90
+ padding: var(--sp-4) var(--sp-6);
91
+ font: var(--type-label);
92
+ font-size: var(--fs-md);
93
+ display: inline-flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ gap: var(--sp-2);
97
+ min-height: var(--btn-h);
98
+ text-decoration: none; /* .btn works as <a> too */
99
+ }
100
+ .btn--primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
101
+ /* quiet keeps a faint outline so it still reads as a button on colored palettes;
102
+ it is ink on whatever it sits on — inherit, or on the dark slot (card--3) it
103
+ would take the light-card action ink and vanish */
104
+ .btn--quiet { background: transparent; color: inherit; border-color: color-mix(in srgb, currentColor 35%, transparent); }
105
+ .btn--block { width: 100%; }
106
+ /* xl: the one giant call to action in a tinted card ("Taxi!") */
107
+ .btn--xl { min-height: var(--btn-xl-h); font: 800 var(--fs-3xl)/1 var(--font-display); letter-spacing: var(--track-h1); border-radius: var(--r-card); padding-inline: var(--sp-8); }
108
+ /* disabled keeps the slot — a control that vanishes moves everything else,
109
+ and a pager whose "previous" disappeared on page one would shift the bar
110
+ under the thumb on every page */
111
+ :is(.btn, .icon-btn):disabled { opacity: 0.4; cursor: default; }
112
+ /* a row of buttons sharing the width (twin CTAs inside a card) */
113
+ .btn-row { display: flex; gap: var(--sp-2); }
114
+ .btn-row > .btn { flex: 1; min-width: 0; }
115
+
116
+ /* ---- chip: status/attribute tag, optional signal dot ---- */
117
+ .chip {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: var(--sp-2);
121
+ border-radius: var(--r-chip);
122
+ /* --outline, not --action-border: a chip is a control, and on a palette
123
+ where --card-1-bg == --action-bg (field, roast, hydro) a transparent
124
+ border makes it invisible on a white card. Same edge fields, checks,
125
+ toggles, tiles and the dropdown already use. */
126
+ border: var(--border-w) solid var(--outline);
127
+ background: var(--action-bg);
128
+ color: var(--action-ink);
129
+ padding: var(--sp-2) var(--sp-3);
130
+ font: var(--type-label);
131
+ font-size: var(--fs-sm);
132
+ min-height: var(--chip-h);
133
+ text-decoration: none;
134
+ justify-self: start; /* as a grid child it hugs its text, never stretches */
135
+ }
136
+ .dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); } /* signal dot: chips, legends */
137
+ .dot--b { background: var(--dot-b); }
138
+ .chip--on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
139
+ /* dark-slot inversion lives in card.css (.card--3 :is(...)) */
140
+
141
+ .chip-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
142
+
143
+ /* ---- dropdown: the standard for every select — a pill trigger + .menu popover ----
144
+ <div class="dropdown"><button class="dropdown__btn" popovertarget="f1"><span class="dropdown__label">this week</span><svg class="icon icon--xs"><use href="icons.svg#chevron-down"/></svg></button>
145
+ <div class="menu" id="f1" popover><button class="menu__item" aria-current="true" data-value="week">this week</button>…</div></div>
146
+ cardds.js copies the picked item into the label and fires "change" on .dropdown.
147
+ .menu (layover.css) draws the panel; anchor positioning pins it under the trigger. */
148
+ .dropdown { position: relative; min-width: 0; display: inline-grid; }
149
+ .dropdown__btn {
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: space-between;
153
+ gap: var(--sp-2);
154
+ width: 100%;
155
+ min-width: 0;
156
+ min-height: var(--tap-sm);
157
+ padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
158
+ border: var(--border-w) solid var(--outline); /* --outline: keeps a faint edge on colored palettes, like fields */
159
+ border-radius: var(--r-chip);
160
+ background: var(--action-bg);
161
+ color: var(--action-ink);
162
+ font: var(--type-label);
163
+ font-size: var(--fs-body);
164
+ text-align: left;
165
+ cursor: pointer;
166
+ }
167
+ .dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
168
+ .dropdown__btn > .icon { transition: rotate 0.2s ease; }
169
+ .dropdown:has(.menu:popover-open) .dropdown__btn > .icon { rotate: 180deg; }
170
+ .dropdown--end .menu[popover] { position-area: block-end span-inline-start; } /* right-aligned trigger: panel grows leftwards */
171
+ .filter-row { display: flex; gap: var(--sp-2); }
172
+ .filter-row > .dropdown { flex: 1; min-width: 0; }
173
+
174
+ /* ---- segment (day picker / tab strip) — items share width, shrink on small screens ---- */
175
+ .segment { display: flex; gap: var(--sp-1); }
176
+ .segment__item {
177
+ border: 0;
178
+ background: transparent;
179
+ color: inherit;
180
+ opacity: 0.6;
181
+ border-radius: var(--r-chip);
182
+ padding: var(--sp-2) var(--sp-1);
183
+ font: var(--type-label);
184
+ font-size: var(--fs-sm);
185
+ min-height: calc(var(--tap-sm) - var(--sp-0));
186
+ flex: 1;
187
+ min-width: 0;
188
+ }
189
+ .segment__item[aria-selected="true"] {
190
+ background: var(--accent);
191
+ color: var(--on-accent);
192
+ opacity: 1;
193
+ }
194
+
195
+ /* ---- bottom CTA pack: fixed action zone under the content ----
196
+ --bar-reserve is the room the bar needs — its own height plus a breathing
197
+ gap above it — published here so anything else that must stop clear of it
198
+ reads this instead of recomputing it. .screen's own reserve below reads
199
+ it; so does .step (step.css), the box a sheet slides in — one number, so
200
+ the two can't drift apart.
201
+ --kb is the on-screen keyboard's height, published on :root by cardds.js
202
+ from the visual viewport (0 without one, and on a platform that shrinks
203
+ the layout viewport itself): the bar rides above the keyboard on iOS the
204
+ way it already does on Android, and .step grows its room by the same. */
205
+ :root { --bar-reserve: calc(var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
206
+ .action-bar {
207
+ position: fixed;
208
+ inset-inline: 0;
209
+ bottom: var(--kb, 0);
210
+ z-index: 30; /* above .sheet-stack sheets, which paint at --level 1-5 */
211
+ max-width: var(--screen-w);
212
+ margin-inline: auto;
213
+ padding: var(--sp-3) var(--sp-4) var(--sp-5);
214
+ display: flex;
215
+ gap: var(--sp-2);
216
+ }
217
+ .action-bar .btn { flex: 1; box-shadow: var(--shadow-float); }
218
+ .action-bar .icon-btn { width: var(--btn-h); height: var(--btn-h); box-shadow: var(--shadow-float); }
219
+
220
+ /* ---- pager: previous / next at the two edges of the bar ----
221
+ For a sequence you can walk BOTH ways — a three-card feature intro, a step
222
+ flow. Use it instead of the default pack whenever going back is as ordinary
223
+ as going on: a wide CTA beside a small round icon reads as one action plus
224
+ an afterthought, which is wrong when the two are peers.
225
+
226
+ Both buttons are the same shape and the same height, so neither wins by
227
+ being physically bigger; the fill alone leans forward. The bar carries the
228
+ buttons only: where you are is said by the .pager__at dots UNDER the card
229
+ that changes (a .screen__centre group, card.css), never in the bar — the
230
+ dots move with what moves (Lh, 2026-09-12). Between the buttons goes a
231
+ "skip" link or nothing. Keep the last step of a flow on the plain
232
+ .action-bar: a step that COMMITS deserves one wide button, not a pair.
233
+
234
+ <div class="action-bar action-bar--pager">
235
+ <button class="btn" disabled><svg class="icon icon--xs"><use href="icons.svg#arrow-left"/></svg>Back</button>
236
+ <button class="btn btn--primary">Next<svg class="icon icon--xs"><use href="icons.svg#arrow-right"/></svg></button>
237
+ </div> */
238
+ .action-bar--pager { align-items: center; }
239
+ /* equal share of the bar, and tighter than a lone CTA so two labels plus the
240
+ indicator still fit a 320px screen */
241
+ .action-bar--pager > .btn {
242
+ flex: 1 1 0;
243
+ min-width: 0;
244
+ padding-inline: var(--sp-3);
245
+ gap: var(--sp-1);
246
+ white-space: nowrap;
247
+ overflow: hidden;
248
+ }
249
+ /* the page dots: under the card in a .screen__centre group; tight gaps */
250
+ .pager__at { flex: 0 1 auto; min-width: 0; overflow: hidden; display: flex; align-items: center; gap: var(--sp-1); }
251
+ .pager__at > * {
252
+ width: var(--dot);
253
+ height: var(--dot);
254
+ border-radius: 50%;
255
+ background: currentColor;
256
+ opacity: 0.3;
257
+ }
258
+ .pager__at > .is-at { background: var(--accent); opacity: 1; }
259
+ /* ---- tiers: a control row above the buttons ----
260
+ A screen whose one move comes with a control — the introduction's text-size
261
+ slider above its pager. Each tier is a row; the bar stacks them, and any
262
+ parent hosting the bar publishes the room for both, the way the plain
263
+ reserve is published above, so a .screen or a .card--centre stops clear.
264
+ <div class="action-bar action-bar--tiers">
265
+ <div class="action-bar__tier"><label class="slider">…</label></div>
266
+ <div class="action-bar__tier action-bar__tier--pager">…the round pager…</div>
267
+ </div> */
268
+ .action-bar--tiers { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
269
+ /* the tiers bar is as transparent as every other bar: the content scrolls under
270
+ it and shows through — the rail and the round buttons carry their own float
271
+ shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
272
+ .action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
273
+ .action-bar__tier > .btn { flex: 1; }
274
+ :has(> .action-bar--tiers) { --bar-reserve: calc(var(--tap) + var(--sp-3) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
275
+
276
+ /* ---- round pager: previous / next as two round buttons at the two edges ----
277
+ The pager with nothing to say between its buttons — no dots, no label — as
278
+ two equal round icon buttons, one at each edge, in thumb reach from either
279
+ side. Rule 3 still holds: the step that commits goes back to one wide button.
280
+ <div class="action-bar action-bar--pager">
281
+ <button class="icon-btn" aria-label="ย้อนกลับ">…</button>
282
+ <button class="icon-btn icon-btn--invert" aria-label="ถัดไป">…</button>
283
+ </div> — or the same two in an .action-bar__tier--pager under a control */
284
+ :is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
285
+
286
+ /* .screen's default bottom padding is a constant; derive the reserve from the
287
+ bar instead, so a taller button (a longer label, a script with more leading)
288
+ can never end up underneath it. Any parent that hosts the bar counts —
289
+ <body>, or an app shell (#app > .screen + .action-bar) — since the bar is
290
+ fixed to the viewport wherever it sits. (.screen--fill reserves nothing:
291
+ its .step carries the bar's room itself, step.css.) */
292
+ :has(> .action-bar) .screen:not(.screen--fill) { padding-bottom: var(--bar-reserve); }
package/css/base.css ADDED
@@ -0,0 +1,128 @@
1
+ /* ============================================================
2
+ cardds/base.css — reset, surface, typography levels, section
3
+ ============================================================ */
4
+
5
+ *, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
6
+
7
+ /* the scale: everything is rem, so this one number resizes the whole system.
8
+ 100% = the user's text size (16px by default); narrow phones step to 15/16 so
9
+ the 25rem column and its 48px taps keep their proportions on 320–360 screens.
10
+ --text-scale is an app's OWN text-size setting — a unitless number the app
11
+ writes on <html> (1 = as the browser has it; 1.1875 = larger). It multiplies
12
+ in here so it composes with the narrow-phone step instead of overwriting the
13
+ root, and so the app never writes a length from script. */
14
+ html { font-size: calc(100% * var(--text-scale, 1)); scrollbar-width: none; }
15
+ @media (max-width: 22.5em) { html { font-size: calc(93.75% * var(--text-scale, 1)); } }
16
+ html::-webkit-scrollbar { display: none; } /* Chromium/Safari */
17
+
18
+ body {
19
+ margin: 0;
20
+ background: var(--surface);
21
+ color: var(--on-surface);
22
+ font: var(--type-body);
23
+ -webkit-font-smoothing: antialiased;
24
+ }
25
+
26
+ /* components here set display (grid/flex), which outranks the UA's
27
+ [hidden] { display: none } — so the attribute silently does nothing on a
28
+ .btn, .icon-btn, .chip or .row. Restore it once, for all of them. */
29
+ [hidden] { display: none !important; }
30
+
31
+ img, svg { display: block; max-width: 100%; }
32
+ button { font: inherit; cursor: pointer; }
33
+
34
+ /* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
35
+ .icon { width: var(--icon); height: var(--icon); flex: none; }
36
+ .icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
37
+ .icon--xs { width: var(--icon-xs); height: var(--icon-xs); } /* inside captions, chips, links */
38
+ .icon--lg { width: var(--icon-lg); height: var(--icon-lg); } /* empty states */
39
+
40
+ /* mobile screen column — the standard horizontal inset, and nothing vertical.
41
+ Top and bottom are zero on purpose: vertical room is only ever a RESERVE for
42
+ something fixed parked over the screen, so each bar adds its own and a page
43
+ without bars needs no override. .appbar reserves the top (actions.css),
44
+ .action-bar (actions.css) and .float-bar (layover.css) the bottom. */
45
+ .screen {
46
+ --screen-gap: var(--sp-5); /* published: a .step cancels it to touch the bar it follows (step.css) */
47
+ max-width: var(--screen-w);
48
+ margin-inline: auto;
49
+ padding-inline: var(--screen-pad);
50
+ display: grid;
51
+ gap: var(--screen-gap);
52
+ }
53
+ .screen > * { min-width: 0; } /* content never widens the column past the viewport */
54
+ /* a screen that carries its own palette (data-palette on the element, not on <html>)
55
+ paints its own surface, so a design tool can show one screen per palette */
56
+ .screen[data-palette] { background: var(--surface); color: var(--on-surface); }
57
+
58
+ /* app screen: exactly one phone height — a height, not a minimum, so that a
59
+ .step (step.css) has a bound to fill and its card and sheet scroll INSIDE
60
+ it instead of growing the page; and clip, not hidden, because a clipped
61
+ box is not a scroll container: nothing — not even the browser revealing a
62
+ focused field — can shift the screen up. A trailing .sheet-stack (stack.css) stretches
63
+ and bleeds off the bottom edge. */
64
+ .screen--fill {
65
+ display: flex;
66
+ flex-direction: column;
67
+ height: 100dvh;
68
+ overflow: clip;
69
+ position: relative; /* the box a .card--centre or a .screen__centre floats in (card.css) */
70
+ }
71
+ /* min-height: 0 — a flex item's floor is its content, and the stack's content
72
+ is its own full-height sheets, so without it the stack grew to the whole
73
+ screen and its open sheet ran past the bottom edge by the bars' height,
74
+ unreachable by scrolling (timebank home, 2026-09-13). The stack IS the room
75
+ left under the bars; every sheet fills exactly that. */
76
+ .screen--fill > .sheet-stack { flex: 1; min-height: 0; }
77
+
78
+ /* ---- typography levels ---- */
79
+ /* --fs-self: each level publishes its own size, so a consumer that scales a
80
+ heading (card.css --squeeze) can multiply it without knowing the level */
81
+ .t-display { font: var(--type-display); letter-spacing: var(--track-display); margin: 0; text-wrap: balance; --fs-self: var(--fs-display); }
82
+ .t-h1 { font: var(--type-h1); letter-spacing: var(--track-h1); margin: 0; --fs-self: var(--fs-h1); }
83
+ .t-h2 { font: var(--type-h2); letter-spacing: var(--track-h2); margin: 0; --fs-self: var(--fs-h2); }
84
+ .t-title { font: var(--type-title); margin: 0; --fs-self: var(--fs-title); }
85
+ .t-body { font: var(--type-body); margin: 0; }
86
+ .t-label { font: var(--type-label); }
87
+ .t-caption { font: var(--type-caption); }
88
+ .t-overline { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; }
89
+ .t-stat { font: var(--type-stat); letter-spacing: var(--track-display); margin: 0; font-variant-numeric: tabular-nums; }
90
+ .t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
91
+ .t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
92
+
93
+ .t-muted { color: var(--on-surface-muted); }
94
+ .card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
95
+ .card--3 .t-muted { color: inherit; opacity: 0.7; } /* card-muted is tuned for light cards; dim instead on the dark slot */
96
+ .t-caps { text-transform: uppercase; }
97
+ .grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
98
+
99
+ /* ---- section: frameless on-surface content + CTA (never a card) ---- */
100
+ .section {
101
+ display: grid;
102
+ gap: var(--sp-3);
103
+ padding-block: var(--sp-4);
104
+ }
105
+ .section > .t-body { color: var(--on-surface-muted); max-width: 34ch; }
106
+ .section .cta-pack {
107
+ display: flex;
108
+ gap: var(--sp-2);
109
+ margin-top: var(--sp-2);
110
+ flex-wrap: wrap;
111
+ }
112
+
113
+ /* ---- empty state: on-surface, centered, optional CTA below ---- */
114
+ .empty-state {
115
+ display: grid;
116
+ justify-items: center;
117
+ text-align: center;
118
+ gap: var(--sp-2);
119
+ padding: var(--sp-12) var(--sp-6);
120
+ color: var(--on-surface-muted);
121
+ }
122
+ .empty-state > :is(h1, h2, h3) { color: var(--on-surface); } /* the heading in full ink, the words muted */
123
+ .empty-state__action { margin-top: var(--sp-2); }
124
+ /* inside a card (a section's "nothing yet"): the card's ink, the way .t-muted and .row__meta follow it */
125
+ .card .empty-state { color: var(--card-muted); padding-block: var(--sp-6); }
126
+ .card .empty-state > :is(h1, h2, h3) { color: var(--card-ink); }
127
+ .card--3 .empty-state { color: inherit; }
128
+ .card--3 .empty-state > :not(:is(h1, h2, h3)) { opacity: 0.7; }