@usableapp/cardds 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ /* ============================================================
2
+ cardds/theme.template.css — A PROJECT'S THEME. Copy, rename, retune.
3
+
4
+ Not part of the bundle: copy this file into the project as its own
5
+ `theme.css` and load it AFTER cardds.css. It is the whole public
6
+ surface of the system — the four size bases and the colour roles.
7
+ Everything else in tokens.css is derived from these; a project never
8
+ sets a derived token (--sp-4, --fs-h1, --r-card, --tap, --sheet-*…)
9
+ and never a multiplier.
10
+
11
+ The values below are the defaults (the wireframe): as shipped, this
12
+ file changes nothing (tests/tokens.spec.js holds it to that).
13
+
14
+ On :root only. The size tokens are computed where tokens.css declares
15
+ them (:root), so a base set on a subtree does not reach them.
16
+ A stock palette's colours and this file's have the same weight — the
17
+ later file wins, so: after cardds.css.
18
+ ============================================================ */
19
+
20
+ :root {
21
+ /* ---- size: four bases, the multipliers stay the system's ---- */
22
+ --sp-base: 0.25rem; /* 4 the room BETWEEN things: every --sp-N = base × N, so paddings and gaps */
23
+ --fs-base: 1rem; /* 16 the type: every --fs-*, the count badge, the inline icon */
24
+ --r-base: 1rem; /* 16 the corners: every --r-* (0 = square; chips and buttons stay fully round) */
25
+ --tap-base: 3rem; /* 48 the size of THINGS: touch targets, control heights, icons, avatars, ring, tiles
26
+ (a touch target never goes under 44px, whatever this says) */
27
+ /* the whole system at once (a member's text-size setting) is --text-scale, a unitless number — not a theme's job */
28
+
29
+ /* ---- faces ---- */
30
+ --font-ui: "PK Nonthaburi", -apple-system, system-ui, sans-serif;
31
+ --font-display: "FC Pride", var(--font-ui);
32
+
33
+ /* ---- colour: five bases, the roles derive (tone 2, the muted inks, the tints) ----
34
+ No default — unset = the wireframe. With colours of your own, put the page in coloured mode:
35
+ <html data-palette="own"> (any value no stock palette uses): cards take a shadow for their
36
+ wireframe border, controls a soft edge. */
37
+ /* --color-surface: #f9e8dc; */ /* the app's ground */
38
+ /* --color-ink: #241f1b; */ /* the words */
39
+ /* --color-paper: #ffffff; */ /* the plain card, neutral buttons and fields */
40
+ /* --color-brand: #ef4e0b; */ /* the primary action, the strong card */
41
+ /* --color-on-brand: #fff4ec; */ /* the words on the brand */
42
+
43
+ /* ---- colour roles: only where a derivation is not what you want — a role set here wins over it ----
44
+ --surface --on-surface --on-surface-muted
45
+ --card-1-bg --card-1-ink --card-2-bg --card-2-ink --card-3-bg --card-3-ink (three card tones)
46
+ --card-border --card-muted --card-2-muted
47
+ --accent --on-accent --action-bg --action-ink --action-border
48
+ --pos --neg --dot-a --dot-b */
49
+ }
package/css/tokens.css CHANGED
@@ -15,56 +15,69 @@
15
15
  --screen-w: 25rem; /* 400: mobile column — .screen, .action-bar, .sheet-stage */
16
16
  --screen-centre: 40%; /* the human centre of a screen: the middle of its top four fifths — the bottom fifth is the thumb's (Lh, 2026-09-10). .card--centre sits here */
17
17
 
18
- /* ---- spacing (4pt grid) ---- */
19
- --sp-0: 0.125rem; /* 2 hairline gaps (title→meta) */
20
- --sp-1: 0.25rem; /* 4 */
21
- --sp-2: 0.5rem; /* 8 */
22
- --sp-3: 0.75rem; /* 12 */
23
- --sp-4: 1rem; /* 16 */
24
- --sp-5: 1.25rem; /* 20 */
25
- --sp-6: 1.5rem; /* 24 */
26
- --sp-8: 2rem; /* 32 */
27
- --sp-10: 2.5rem; /* 40 */
28
- --sp-12: 3rem; /* 48 */
18
+ /* ---- the four bases: what a project retunes (Lh, 2026-09-20) ----
19
+ Every size token below is multiplier × its family's base. A project sets a BASE
20
+ and never a multiplier, so the proportions of the system hold at any size.
21
+ The px in the comments are at these defaults (1rem = 16px). */
22
+ --sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
23
+ --fs-base: 1rem; /* 16 type: --fs-* */
24
+ --r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
25
+ --tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
26
+ --header-h, --fab), icons, avatars, dots, the ring and the tiles. --sp-base is the room BETWEEN things.
27
+ Text-holders follow the type instead: --badge and --icon-xs (inline with captions) are on --fs-base.
28
+ Not on a base: --screen-w (the column), borders / rules / focus (px, crisp), shadows, motion. */
29
+
30
+ /* ---- spacing (4pt grid): the number in the name is the multiplier ---- */
31
+ --sp-0: calc(var(--sp-base) * 0.5); /* 2 hairline gaps (title→meta) */
32
+ --sp-1: var(--sp-base); /* 4 */
33
+ --sp-2: calc(var(--sp-base) * 2); /* 8 */
34
+ --sp-3: calc(var(--sp-base) * 3); /* 12 */
35
+ --sp-4: calc(var(--sp-base) * 4); /* 16 */
36
+ --sp-5: calc(var(--sp-base) * 5); /* 20 */
37
+ --sp-6: calc(var(--sp-base) * 6); /* 24 */
38
+ --sp-8: calc(var(--sp-base) * 8); /* 32 */
39
+ --sp-10: calc(var(--sp-base) * 10); /* 40 */
40
+ --sp-12: calc(var(--sp-base) * 12); /* 48 */
29
41
 
30
42
  /* ---- touch & control sizes ---- */
31
- --tap: 3rem; /* 48 primary touch target */
32
- --tap-sm: 2.75rem; /* 44 minimum touch target (Apple HIG floor) */
33
- --tap-lg: 3.5rem; /* 56 hero controls: track steps, send, field with action */
34
- --btn-h: 3.25rem; /* 52 button height */
35
- --btn-xl-h: 5.5rem; /* 88 the one giant CTA */
36
- --chip-h: 2.375rem; /* 38 chip height */
37
- --bar-h: 4rem; /* 64 float bar, day cell, deck actions */
38
- --header-h: 3.75rem;/* 60 top bar — .topbar and .appbar alike */
43
+ --tap-floor: 2.75rem; /* 44 the HIG floor — no --tap-base takes a touch target under it */
44
+ --tap: max(var(--tap-floor), var(--tap-base)); /* 48 primary touch target */
45
+ --tap-sm: max(var(--tap-floor), calc(var(--tap-base) * 11 / 12)); /* 44 minimum touch target */
46
+ --tap-lg: max(var(--tap-floor), calc(var(--tap-base) * 7 / 6)); /* 56 hero controls: track steps, send, field with action */
47
+ --btn-h: calc(var(--tap-base) * 13 / 12); /* 52 button height */
48
+ --btn-xl-h: calc(var(--tap-base) * 11 / 6); /* 88 the one giant CTA */
49
+ --chip-h: calc(var(--tap-base) * 19 / 24); /* 38 chip height */
50
+ --bar-h: calc(var(--tap-base) * 4 / 3); /* 64 float bar, day cell, deck actions */
51
+ --header-h: calc(var(--tap-base) * 5 / 4); /* 60 top bar — .topbar and .appbar alike */
39
52
  --screen-pad: var(--sp-4); /* 16 the screen's inset — .sheet-stack cancels it */
40
- --map-ring: 10rem; /* the rough-area ring on a map (sheet.css .map__ring) — a neighbourhood, not a house */
41
- --fab: 4.5rem; /* 72 square floating action */
42
- --badge: 1.625rem; /* 26 count badge */
43
- --dot: 0.625rem; /* 10 signal dot */
44
- --dot-xs: 0.375rem; /* 6 marker dot */
53
+ --map-ring: calc(var(--tap-base) * 10 / 3); /* the rough-area ring on a map (sheet.css .map__ring) — a neighbourhood, not a house */
54
+ --fab: calc(var(--tap-base) * 3 / 2); /* 72 square floating action */
55
+ --badge: calc(var(--fs-base) * 1.625); /* 26 count badge */
56
+ --dot: calc(var(--tap-base) * 5 / 24); /* 10 signal dot */
57
+ --dot-xs: calc(var(--tap-base) / 8); /* 6 marker dot */
45
58
 
46
59
  /* ---- icons (icons.svg sprite, Lucide) ---- */
47
- --icon: 1.75rem; /* 28 default */
48
- --icon-sm: 1.75rem; /* one size everywhere; retune if small contexts appear */
49
- --icon-lg: 2rem; /* 32 empty states */
50
- --icon-xs: 1rem; /* 16 inline with captions/chips only (row meta, link arrows) */
60
+ --icon: calc(var(--tap-base) * 7 / 12); /* 28 default */
61
+ --icon-sm: calc(var(--tap-base) * 7 / 12); /* one size everywhere; retune if small contexts appear */
62
+ --icon-lg: calc(var(--tap-base) * 2 / 3); /* 32 empty states */
63
+ --icon-xs: var(--fs-base); /* 16 inline with captions/chips only (row meta, link arrows) */
51
64
 
52
65
  /* ---- people & big elements ---- */
53
- --avatar-sm: 2.5rem; /* 40 row lead */
54
- --avatar: 3.5rem; /* 56 card head, person row */
55
- --avatar-lg: 5rem; /* 80 chooser row */
56
- --avatar-xl: 6rem; /* 96 sheet head icon ring */
57
- --ring: 11rem; /* 176 .ring gauge diameter */
58
- --ring-w: 1.375rem; /* 22 .ring stroke */
59
- --tile-min: 7.5rem; /* 120 .tile-grid column floor */
60
- --tile-h: 8.25rem; /* 132 tile / band height */
66
+ --avatar-sm: calc(var(--tap-base) * 5 / 6); /* 40 row lead */
67
+ --avatar: calc(var(--tap-base) * 7 / 6); /* 56 card head, person row */
68
+ --avatar-lg: calc(var(--tap-base) * 5 / 3); /* 80 chooser row */
69
+ --avatar-xl: calc(var(--tap-base) * 2); /* 96 sheet head icon ring */
70
+ --ring: calc(var(--tap-base) * 11 / 3); /* 176 .ring gauge diameter */
71
+ --ring-w: calc(var(--tap-base) * 11 / 24); /* 22 .ring stroke */
72
+ --tile-min: calc(var(--tap-base) * 5 / 2); /* 120 .tile-grid column floor */
73
+ --tile-h: calc(var(--tap-base) * 11 / 4); /* 132 tile / band height */
61
74
 
62
75
  /* ---- radius ---- */
63
76
  --r-chip: 999px; /* pills, chips, buttons — always fully round */
64
- --r-card: 2rem; /* 32 the one card radius: every card, row, tile, menu, sheet, fab */
65
- --r-media: 1rem; /* 16 images inside cards */
66
- --r-control: 0.875rem; /* 14 fields, thumbs, menu rows */
67
- --r-badge: 0.625rem; /* 10 tile badge, bars */
77
+ --r-card: calc(var(--r-base) * 2); /* 32 the one card radius: every card, row, tile, menu, sheet, fab */
78
+ --r-media: var(--r-base); /* 16 images inside cards */
79
+ --r-control: calc(var(--r-base) * 0.875); /* 14 fields, thumbs, menu rows */
80
+ --r-badge: calc(var(--r-base) * 0.625); /* 10 tile badge, bars */
68
81
 
69
82
  /* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
70
83
  --border-w: 1.5px;
@@ -85,22 +98,22 @@
85
98
  --font-display: "FC Pride", var(--font-ui); /* the character face for headings and big numbers (fonts.css) */
86
99
  --font-mono: "SF Mono", "JetBrains Mono", ui-monospace, monospace;
87
100
 
88
- --fs-xs: 0.6875rem; /* 11 overline */
89
- --fs-caption: 0.75rem; /* 12 */
90
- --fs-label: 0.8125rem; /* 13 */
91
- --fs-sm: 0.875rem; /* 14 chips, segments */
92
- --fs-body: 0.9375rem; /* 15 */
93
- --fs-md: 1rem; /* 16 buttons */
94
- --fs-title: 1.0625rem; /* 17 */
95
- --fs-lg: 1.25rem; /* 20 day numbers, tile values */
96
- --fs-h2: 1.375rem; /* 22 */
97
- --fs-2xl: 1.5rem; /* 24 composer, small ring */
98
- --fs-h1: 1.75rem; /* 28 */
99
- --fs-3xl: 2rem; /* 32 xl button */
100
- --fs-display: clamp(2.125rem, 9vw, 2.625rem); /* 34–42 */
101
- --fs-stat: clamp(1.875rem, 8vw, 2.375rem); /* 30–38 big numbers */
102
- --fs-hero: 3.5rem; /* 56 the one number on a sheet */
103
- --fs-giant: clamp(3rem, 15vw, 4rem); /* 48–64 data bands */
101
+ --fs-xs: calc(var(--fs-base) * 0.6875); /* 11 overline */
102
+ --fs-caption: calc(var(--fs-base) * 0.75); /* 12 */
103
+ --fs-label: calc(var(--fs-base) * 0.8125); /* 13 */
104
+ --fs-sm: calc(var(--fs-base) * 0.875); /* 14 chips, segments */
105
+ --fs-body: calc(var(--fs-base) * 0.9375); /* 15 */
106
+ --fs-md: var(--fs-base); /* 16 buttons */
107
+ --fs-title: calc(var(--fs-base) * 1.0625); /* 17 */
108
+ --fs-lg: calc(var(--fs-base) * 1.25); /* 20 day numbers, tile values */
109
+ --fs-h2: calc(var(--fs-base) * 1.375); /* 22 */
110
+ --fs-2xl: calc(var(--fs-base) * 1.5); /* 24 composer, small ring */
111
+ --fs-h1: calc(var(--fs-base) * 1.75); /* 28 */
112
+ --fs-3xl: calc(var(--fs-base) * 2); /* 32 xl button */
113
+ --fs-display: clamp(calc(var(--fs-base) * 2.125), 9vw, calc(var(--fs-base) * 2.625)); /* 34–42 */
114
+ --fs-stat: clamp(calc(var(--fs-base) * 1.875), 8vw, calc(var(--fs-base) * 2.375)); /* 30–38 big numbers */
115
+ --fs-hero: calc(var(--fs-base) * 3.5); /* 56 the one number on a sheet */
116
+ --fs-giant: clamp(calc(var(--fs-base) * 3), 15vw, calc(var(--fs-base) * 4)); /* 48–64 data bands */
104
117
 
105
118
  /* level weight size / line-height family */
106
119
  --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) */
@@ -120,35 +133,53 @@
120
133
  /* ============================================================
121
134
  COLOR ROLES — wireframe defaults (grayscale).
122
135
  palette.css overrides these under [data-palette="…"].
136
+
137
+ THE FIVE COLOUR BASES (Lh, 2026-09-20) — a project's own colours, the
138
+ same idea as the size bases: set a base, the roles follow.
139
+ --color-surface the app's ground
140
+ --color-ink the words
141
+ --color-paper the plain card, neutral buttons and fields
142
+ --color-brand the primary action, the strong card, the signal dot
143
+ --color-on-brand the words on the brand
144
+ They have NO default: unset, every role below is its exact wireframe
145
+ value. That is what the fallbacks are for — a private mix (--_*) that
146
+ reads an unset base is invalid, so var(--_x, #grey) lands on the grey.
147
+ Set, the roles derive: tone 2 = the brand washed into the surface, the
148
+ muted inks = the ink washed into their ground. A role set directly
149
+ (a palette, or a project's theme.css) always wins over its derivation.
123
150
  ============================================================ */
151
+ --_muted-on-surface: color-mix(in srgb, var(--color-ink) 66%, var(--color-surface, #f2f2f0));
152
+ --_muted-on-paper: color-mix(in srgb, var(--color-ink) 66%, var(--color-paper, #ffffff));
153
+ --_tone-2: color-mix(in srgb, var(--color-brand) 14%, var(--color-surface, #f2f2f0));
154
+ --_muted-on-tone-2: color-mix(in srgb, var(--color-ink, #141414) 72%, var(--_tone-2));
124
155
 
125
156
  /* app background ("earth-toned surface or shiny color, up to the app") */
126
- --surface: #f2f2f0;
127
- --on-surface: #141414;
128
- --on-surface-muted: #6b6b6b;
157
+ --surface: var(--color-surface, #f2f2f0);
158
+ --on-surface: var(--color-ink, #141414);
159
+ --on-surface-muted: var(--_muted-on-surface, #6b6b6b);
129
160
 
130
161
  /* card boundaries — 3 slots so stacked/adjacent cards can differ */
131
- --card-1-bg: #ffffff; --card-1-ink: #141414;
132
- --card-2-bg: #e6e6e3; --card-2-ink: #141414;
133
- --card-3-bg: #141414; --card-3-ink: #ffffff;
162
+ --card-1-bg: var(--color-paper, #ffffff); --card-1-ink: var(--color-ink, #141414);
163
+ --card-2-bg: var(--_tone-2, #e6e6e3); --card-2-ink: var(--color-ink, #141414);
164
+ --card-3-bg: var(--color-brand, #141414); --card-3-ink: var(--color-on-brand, #ffffff);
134
165
 
135
166
  --card-border: #141414; /* wireframe: visible outline; palettes may set transparent */
136
- --card-muted: #6b6b6b; /* secondary text inside cards (the default, tone-1 card) */
137
- --card-2-muted: #5f5f5f; /* the same text on the tone-2 card — 5.1:1 on its grey (#6b6b6b is 4.3:1, under AA);
167
+ --card-muted: var(--_muted-on-paper, #6b6b6b); /* secondary text inside cards (the default, tone-1 card) */
168
+ --card-2-muted: var(--_muted-on-tone-2, #5f5f5f); /* the same text on the tone-2 card — 5.1:1 on its grey (#6b6b6b is 4.3:1, under AA);
138
169
  a palette whose tone-2 ground is strong
139
170
  sets this to its tone-2 ink — a muted shade cannot reach AA there (clay) */
140
171
 
141
172
  /* actions */
142
- --accent: #141414; /* primary CTA fill */
143
- --on-accent: #ffffff;
144
- --action-bg: #ffffff; /* neutral buttons/chips/inputs */
145
- --action-ink: #141414;
173
+ --accent: var(--color-brand, #141414); /* primary CTA fill */
174
+ --on-accent: var(--color-on-brand, #ffffff);
175
+ --action-bg: var(--color-paper, #ffffff); /* neutral buttons/chips/inputs */
176
+ --action-ink: var(--color-ink, #141414);
146
177
  --action-border: #141414;
147
178
 
148
179
  /* signals (chip dots, deltas) */
149
180
  --pos: #8a8a8a;
150
181
  --neg: #3d3d3d;
151
- --dot-a: #141414;
182
+ --dot-a: var(--color-brand, #141414);
152
183
  --dot-b: #8a8a8a;
153
184
 
154
185
  /* derived tints — read the element's own ink, so they work on any slot */
@@ -4,15 +4,13 @@ export interface ChipProps extends ComponentPropsWithoutRef<'span'> {
4
4
  on?: boolean;
5
5
  /** a leading signal dot: `a` (the accent signal) or `b` (the secondary) */
6
6
  dot?: 'a' | 'b';
7
- /** a speech tail under the chip (a mascot's greeting) */
8
- bubble?: boolean;
9
7
  /** the element: span (default), a link, or a button */
10
8
  as?: 'span' | 'a' | 'button';
11
9
  href?: string;
12
10
  children?: ReactNode;
13
11
  }
14
12
  /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
15
- export declare function Chip({ on, dot, bubble, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
13
+ export declare function Chip({ on, dot, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
16
14
  slot?: string | undefined | undefined;
17
15
  style?: import("react").CSSProperties | undefined;
18
16
  title?: string | undefined | undefined;
@@ -3,9 +3,9 @@ import { createElement } from 'react';
3
3
  import { cx } from '../cx.js';
4
4
  import { Dot } from './Dot.js';
5
5
  /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
6
- export function Chip({ on, dot, bubble, as, href, className, children, ...rest }) {
6
+ export function Chip({ on, dot, as, href, className, children, ...rest }) {
7
7
  const el = as ?? (href ? 'a' : 'span');
8
- return createElement(el, { href, type: el === 'button' ? 'button' : undefined, className: cx('chip', on && 'chip--on', bubble && 'chip--bubble', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b' }), children);
8
+ return createElement(el, { href, type: el === 'button' ? 'button' : undefined, className: cx('chip', on && 'chip--on', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b' }), children);
9
9
  }
10
10
  /** ChipPick — a chip that is a choice: full-width in a ChipGrid (9:00 · 9:30 · …), or `sign` on a ChipRow (+ off, × on — the
11
11
  * interest picker); the picked one fills with accent. A native radio/checkbox inside the label carries the state. */