@usableapp/cardds 0.7.102 → 0.7.104

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 (60) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/css/actions.css +102 -36
  3. package/css/base.css +139 -15
  4. package/css/card.css +308 -79
  5. package/css/choice.css +25 -33
  6. package/css/finish.css +28 -27
  7. package/css/forms.css +6 -14
  8. package/css/journey.css +4 -4
  9. package/css/layover.css +26 -24
  10. package/css/lists.css +8 -6
  11. package/css/media.css +13 -74
  12. package/css/numbers.css +37 -32
  13. package/css/palette.css +1 -1
  14. package/css/people.css +3 -8
  15. package/css/post.css +23 -19
  16. package/css/sheet.css +84 -56
  17. package/css/stack.css +39 -215
  18. package/css/stamp.css +41 -29
  19. package/css/step.css +1 -107
  20. package/css/sticky.css +43 -41
  21. package/css/theme.template.css +12 -0
  22. package/css/tokens.css +100 -26
  23. package/dist/actions/Chip.d.ts +1 -1
  24. package/dist/actions/Chip.js +3 -2
  25. package/dist/cardds.css +1053 -868
  26. package/dist/cards/Card.d.ts +3 -3
  27. package/dist/cards/Card.js +1 -1
  28. package/dist/cards/CardFeed.js +7 -9
  29. package/dist/catalog.json +1 -1
  30. package/dist/layover/Snackbar.js +2 -2
  31. package/dist/lists/Bubble.d.ts +1 -1
  32. package/dist/lists/Bubble.js +1 -1
  33. package/dist/lists/Row.js +5 -7
  34. package/dist/numbers/Picker.js +11 -18
  35. package/dist/people/Avatar.d.ts +1 -1
  36. package/dist/people/Avatar.js +3 -2
  37. package/dist/post/PostDesk.js +6 -9
  38. package/dist/post/PostLetter.js +4 -4
  39. package/dist/pressable.d.ts +9 -0
  40. package/dist/pressable.js +6 -0
  41. package/dist/room.d.ts +5 -0
  42. package/dist/room.js +9 -0
  43. package/dist/scaffold/Panes.js +8 -14
  44. package/dist/sheets/Sheet.d.ts +1 -1
  45. package/dist/sheets/Sheet.js +2 -3
  46. package/dist/sheets/SheetSteps.js +3 -3
  47. package/dist/sheets/StackSheet.d.ts +1 -1
  48. package/dist/sheets/sheetStepsPile.js +2 -3
  49. package/dist/sheets/stackCard.js +67 -115
  50. package/dist/state.d.ts +21 -0
  51. package/dist/state.js +28 -1
  52. package/dist/sticky/Sticky.js +3 -7
  53. package/dist/templates/Home.d.ts +1 -1
  54. package/dist/templates/Splash.js +2 -2
  55. package/dist/templates/Walkthrough.js +4 -5
  56. package/docs/reference.md +71 -9
  57. package/lib/catalog.mjs +23 -7
  58. package/lint/rules.mjs +11 -6
  59. package/lint/values.mjs +144 -0
  60. package/package.json +6 -4
package/css/media.css CHANGED
@@ -1,52 +1,16 @@
1
1
  /* ============================================================
2
2
  cardds/media.css — media & display inside cards:
3
- cover card · quote · tile grid · fold · waveform
3
+ quote · tile grid · waveform (the cover card and the fold are card variants: card.css)
4
4
  ============================================================ */
5
5
 
6
- /* ---- cover: the image IS the card; text over a scrim ----
7
- <article class="card card--cover"><div class="ph" role="img"></div>
8
- <div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
9
- <div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
10
- <div class="row">…meta…</div></article> */
11
- .card--cover {
12
- --card-bg: var(--card-3-bg);
13
- --dark: var(--card-3-dark);
14
- --accent: var(--card-ink);
15
- --accent-dark: calc(1 - var(--dark));
16
- min-height: var(--_cover-min-h);
17
- align-content: space-between;
18
- overflow: hidden;
19
- isolation: isolate;
20
- }
21
- .card--cover > img,
22
- .card--cover > .ph {
23
- position: absolute;
24
- inset: 0;
25
- width: 100%;
26
- height: 100%;
27
- object-fit: cover;
28
- z-index: -2;
29
- border: 0;
30
- border-radius: 0;
31
- min-height: 0;
32
- }
33
- /* scrim: dark at both ends so the meta stays readable */
34
- .card--cover::before {
35
- content: "";
36
- position: absolute;
37
- inset: 0;
38
- z-index: -1;
39
- background: linear-gradient(to bottom, var(--scrim), transparent 40% 60%, var(--scrim));
40
- }
41
-
42
6
  /* ---- quote: giant mark, big text, attribution ----
43
7
  <blockquote class="quote"><p class="quote__text">It's overcast…</p><footer class="quote__by">Wednesday 15</footer></blockquote> */
44
8
  .quote { margin: 0; display: grid; gap: var(--quote-gap, var(--sp-3)); }
45
9
  .quote::before {
46
10
  content: "\201C";
47
- font: 900 calc(var(--fs-h1) * 3)/0.55 var(--font-display);
11
+ font: var(--fw-black) calc(var(--fs-h1) * 3) / var(--lh-mark) var(--font-display);
48
12
  height: var(--sp-10);
49
- opacity: 0.9;
13
+ opacity: var(--fade-most);
50
14
  }
51
15
  .quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
52
16
  .quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
@@ -60,7 +24,7 @@
60
24
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
61
25
  gap: var(--tile-grid-gap, var(--sp-3));
62
26
  }
63
- .tile-grid--3 { --tile-min: 6rem; } /* three across on a phone (attachments, upload kinds) */
27
+ .tile-grid--3 { --tile-min: calc(var(--tap-base) * 2); } /* three across on a phone (attachments, upload kinds) */
64
28
  .tile {
65
29
  display: grid;
66
30
  align-content: space-between;
@@ -107,42 +71,18 @@
107
71
  .tile--5 { background: var(--tile-5-bg, var(--card-5-bg)); --dark: var(--card-5-dark); color: var(--tile-5-ink, var(--card-ink)); border-color: transparent; }
108
72
  .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--tile-3-ink, var(--card-ink)); border-color: transparent; }
109
73
 
110
- /* ---- fold: a sticky-note corner on any card (decorative) ----
111
- No border. The card's outline follows its border box, so it ran straight
112
- across the squared bottom-right corner and cut through the fold's diagonal —
113
- the corner read as a notch with a line through it rather than a lifted flap.
114
- The COLOUR goes transparent rather than the border going to 0, so the box
115
- keeps its geometry and a fold card still measures the same as the cards
116
- beside it. */
117
- .card--fold {
118
- border-color: transparent;
119
- border-bottom-right-radius: 0;
120
- background-image: linear-gradient(to top left,
121
- var(--card-bg) 0 50%, /* the card's own background, not --surface (the page's) — a mismatch here drew a seam at the flap's edge */
122
- color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
123
- background-repeat: no-repeat;
124
- background-position: bottom right;
125
- background-size: var(--sp-8) var(--sp-8);
126
- background-origin: border-box; /* the flap sits on the true corner, not inside the (transparent) border */
127
- /* the corner is CUT: the triangle under the flap shows what is behind the card. Painted in the card's own colour it
128
- read as the square corner of a second card underneath (Lh 2026-09-23). The polygon reaches 3rem past the other
129
- edges so a coloured palette's shadow is not clipped there. */
130
- clip-path: polygon(-3rem -3rem, calc(100% + 3rem) -3rem, calc(100% + 3rem) calc(100% - var(--sp-8) - 3rem),
131
- calc(100% - var(--sp-8) - 3rem) calc(100% + 3rem), -3rem calc(100% + 3rem));
132
- }
133
-
134
74
  /* ---- wave: audio waveform stand-in for voice rows ----
135
75
  <span class="wave"></span> — sits in a .row__body, fills the width */
136
76
  .wave {
137
77
  /* only WHOLE bars show (Lh 2026-09-23 — a clipped last bar read thinner): the columns are as many whole bar-widths as fit
138
78
  (auto-fill), spread edge to edge; bars that do not fit go to rows of no height, clipped away */
139
79
  display: grid;
140
- grid-template-columns: repeat(auto-fit, var(--wave-bar-width, 0.125rem)); /* auto-FIT: columns no bar fills collapse, so a wide row still spreads its bars edge to edge */
80
+ grid-template-columns: repeat(auto-fit, var(--wave-bar-width, var(--hairline-w))); /* auto-FIT: columns no bar fills collapse, so a wide row still spreads its bars edge to edge */
141
81
  grid-template-rows: 100%;
142
82
  grid-auto-rows: 0;
143
83
  justify-content: space-between;
144
84
  align-items: center;
145
- column-gap: var(--wave-gap, 0.125rem);
85
+ column-gap: var(--wave-gap, var(--hairline-w));
146
86
  height: var(--wave-h, var(--icon));
147
87
  width: 100%;
148
88
  overflow: hidden;
@@ -168,7 +108,6 @@
168
108
  }
169
109
  .mosaic > :first-child { grid-row: span 2; }
170
110
  .mosaic > * { min-height: 0; border-radius: 0; } /* opacity/border-width here were no-ops: .ph restates its own below, and nothing else in a mosaic carries either by default */
171
- .mosaic > .ph { opacity: var(--off); border-width: var(--border-w); }
172
111
  .mosaic > img { width: 100%; height: 100%; object-fit: cover; }
173
112
 
174
113
  /* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
@@ -226,8 +165,7 @@
226
165
  .postcard__turn:focus-visible { outline-offset: calc(-1 * var(--sp-1)); } /* the ring inside the card's edge */
227
166
  /* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
228
167
  .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--postcard-front-ink, var(--card-ink)); }
229
- .postcard__front .postcard__turn > img,
230
- .postcard__front .postcard__turn > .ph {
168
+ .postcard__front .postcard__turn > img {
231
169
  position: absolute;
232
170
  inset: 0;
233
171
  width: 100%;
@@ -241,7 +179,7 @@
241
179
  content: "";
242
180
  position: absolute;
243
181
  inset: 0;
244
- z-index: 1;
182
+ z-index: var(--z-over);
245
183
  pointer-events: none;
246
184
  background: linear-gradient(to top, var(--scrim), transparent 45%);
247
185
  }
@@ -257,7 +195,7 @@
257
195
  Taps between its words fall through to the turn; its buttons take their own. */
258
196
  .postcard__band {
259
197
  position: relative;
260
- z-index: 2;
198
+ z-index: var(--z-over-2);
261
199
  display: flex;
262
200
  align-items: end;
263
201
  gap: var(--postcard-band-gap, var(--sp-3));
@@ -267,9 +205,10 @@
267
205
  .postcard__band > * { pointer-events: auto; }
268
206
  .postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
269
207
  .postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
270
- .postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
271
208
  /* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
272
209
  under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
273
- .postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
210
+ .postcard__voice { position: absolute; inset: 0; z-index: var(--z-over-2); display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
274
211
  .postcard__voice > * { pointer-events: auto; }
275
- .postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
212
+
213
+ /* ==== the tile a choice draws, picked (choice.css drives it with its input) (#62: a block is written in its own file) ==== */
214
+ .tile--pick:has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
package/css/numbers.css CHANGED
@@ -28,13 +28,13 @@
28
28
  border-radius: 50%;
29
29
  background: conic-gradient(var(--accent) calc(var(--value) * 1%), var(--ink-tint) 0);
30
30
  /* masks only read alpha: currentColor = "opaque" without naming a color */
31
- mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
32
- -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
31
+ mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - var(--px)), currentColor calc(100% - var(--_w)));
32
+ -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - var(--px)), currentColor calc(100% - var(--_w)));
33
33
  }
34
34
  .ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
35
35
  .ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
36
36
  .ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
37
- .ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
37
+ .ring--sm .ring__value { font: var(--fw-heavy) var(--fs-2xl) / var(--lh-solid) var(--font-display); }
38
38
  .ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
39
39
  .ring--xs .ring__value { font: var(--type-caption); }
40
40
 
@@ -75,44 +75,31 @@
75
75
  }
76
76
  .track__step:first-child::before { display: none; }
77
77
  .track__step.is-done::before { background: var(--accent); }
78
- .track__step > .icon,
79
- .track__step > .track__dot {
80
- width: var(--_step);
81
- height: var(--_step);
78
+ .track__step > .icon { --icon-size: var(--_step); --icon-pad: calc(var(--_step) * 7 / 32); --icon-radius: 50%; --icon-bg: var(--ink-rail); --icon-ink: var(--card-ink); }
79
+ .track__step > .track__dot { /* a number in the round (TrackStep num) */
82
80
  flex: none;
83
- padding: calc(var(--_step) * 7 / 32); /* the glyph = 18 in the 32 round */
84
81
  border-radius: 50%;
85
- background: var(--ink-rail);
82
+ background: var(--ink-tint); /* a number is words: on the 30% rail it fell under 4.5:1 on a mid-dark track; the 12% tint is what every tone holds its ink on */
86
83
  color: var(--card-ink);
87
- }
88
- .track__step > .track__dot { background: var(--ink-tint); } /* a number is words: on the 30% rail it fell under 4.5:1 on a mid-dark track; the 12% tint is what every tone holds its ink on */
89
- .track__step > .track__dot { /* a number in the round (TrackStep num) */
90
84
  display: grid; place-items: center; box-sizing: border-box;
91
- font: var(--track-dot-font, var(--type-caption)); line-height: 1; font-variant-numeric: tabular-nums;
85
+ font: var(--track-dot-font, var(--type-caption)); line-height: var(--lh-solid); font-variant-numeric: tabular-nums;
92
86
  width: var(--track-dot-size, 1.877em); height: var(--track-dot-size, 1.877em); /* em, not rem (Lh 2026-10-03, as the SheetSteps' number and the StackSheet's count): the round is cut from ITS OWN
93
87
  number's size — 1.877em of a 17px caption = the 32px of an icon's round; at a larger text it grows with its number, and the pill with it */
94
88
  padding: 0 0 var(--track-dot-lift, 0.1em); /* the room UNDER the number lifts it onto the round's middle: a digit has no descender */
95
89
  }
96
- .track__step.is-done > .icon,
90
+ .track__step.is-done > .icon { --icon-bg: var(--accent); --icon-ink: var(--on-accent); }
97
91
  .track__step.is-done > .track__dot { background: var(--accent); color: var(--on-accent); }
98
92
 
99
93
  /* ---- band: data card — 2-line label · giant number · footnote ----
100
94
  <article class="card card--band"><span class="band__label">Hours<br>banked</span><span class="band__num">18</span><span class="band__foot">this week</span></article> */
101
- .card--band {
102
- grid-template-columns: 1fr auto;
103
- grid-template-areas: "label num" "foot num";
104
- align-items: end;
105
- gap: var(--sp-2) var(--sp-4);
106
- min-height: var(--card-band-min-h, var(--tile-h));
107
- }
108
- .band__label { grid-area: label; align-self: start; font: var(--band-label-font, var(--type-label)); font-size: var(--band-label-font-size, var(--fs-body)); line-height: 1.2; }
95
+ .band__label { grid-area: label; align-self: start; font: var(--band-label-font, var(--type-label)); font-size: var(--band-label-font-size, var(--fs-body)); line-height: var(--lh-label); }
109
96
  .band__num {
110
97
  grid-area: num;
111
- font: 800 var(--fs-giant) / 0.9 var(--font-display);
98
+ font: var(--fw-heavy) var(--fs-giant) / var(--lh-band) var(--font-display);
112
99
  letter-spacing: var(--track-display);
113
100
  font-variant-numeric: tabular-nums;
114
101
  }
115
- .band__num small { font-size: 0.5em; font-weight: 700; margin-right: 0.1em; } /* unit or % */
102
+ .band__num small { font-size: 0.5em; font-weight: var(--fw-bold); margin-right: 0.1em; } /* unit or % */
116
103
  .band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
117
104
  /* band stack: ONE card boundary; bands are frameless inside it, a hairline
118
105
  between each. Bands may still carry slot tints (card--2 / card--3). */
@@ -122,8 +109,6 @@
122
109
  border-radius: var(--band-stack-radius, var(--r-card));
123
110
  overflow: hidden;
124
111
  }
125
- .band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
126
- .band-stack > .card--band + .card--band { border-top: var(--border-w) solid var(--hairline); }
127
112
 
128
113
  /* ---- dotgrid: progress over time as dots — --total, --done, --cols ----
129
114
  one element, no children:
@@ -157,7 +142,7 @@
157
142
  );
158
143
  }
159
144
  /* no round(): show the grid without the done fill rather than a broken shape */
160
- @supports not (width: round(up, 1px, 1px)) {
145
+ @supports not (width: round(up, 0.0625rem, 0.0625rem)) {
161
146
  .dotgrid::before { display: none; }
162
147
  }
163
148
  /* ---- picker: horizontal snap picker, centre value big ----
@@ -202,7 +187,7 @@
202
187
  .picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
203
188
  /* the scrolled-to item lights up by position — no JS */
204
189
  @supports (animation-timeline: view(x)) {
205
- .picker__item { animation: picker-focus linear both; animation-timeline: view(x); }
190
+ .picker__item { animation: picker-focus var(--ease-linear) both; animation-timeline: view(x); }
206
191
  @keyframes picker-focus {
207
192
  0%, 100% { color: var(--_rest); }
208
193
  45%, 55% { color: var(--card-ink); }
@@ -228,7 +213,7 @@
228
213
 
229
214
  /* ---- bars: three or four bars with a count on top — the glance chart ----
230
215
  <div class="bars"><div class="bar" style="--v: 16"><span>+16</span></div>… --max on the parent */
231
- .bars { --max: 20; --_h: 5rem; display: flex; align-items: end; gap: var(--bars-gap, var(--sp-2)); height: calc(var(--_h) + var(--sp-8)); }
216
+ .bars { --max: 20; --_h: calc(var(--tap-base) * 5 / 3); display: flex; align-items: end; gap: var(--bars-gap, var(--sp-2)); height: calc(var(--_h) + var(--sp-8)); }
232
217
  .bar {
233
218
  flex: 1;
234
219
  min-width: 0;
@@ -238,7 +223,7 @@
238
223
  gap: var(--bar-gap, var(--sp-1));
239
224
  height: 100%;
240
225
  font: var(--bar-font, var(--type-caption));
241
- font-weight: 700;
226
+ font-weight: var(--fw-bold);
242
227
  }
243
228
  .bar::after {
244
229
  content: "";
@@ -254,8 +239,7 @@
254
239
  <span class="spinner" role="status"><svg class="icon">…loader-circle…</svg>Loading…</span>
255
240
  The ring turns; words beside it are optional and take the caption's look. Still when motion is reduced. */
256
241
  .spinner { display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2); vertical-align: middle; font: var(--type-caption); color: var(--spinner-ink, currentColor); }
257
- .spinner > .icon { flex: none; }
258
- @media (prefers-reduced-motion: no-preference) { .spinner > .icon { animation: turn 0.9s linear infinite; } } /* `turn`: tokens.css */
242
+ @media (prefers-reduced-motion: no-preference) { .spinner > .icon { --icon-spin: turn var(--motion-spin) var(--ease-linear) infinite; } } /* `turn`: tokens.css */
259
243
 
260
244
  /* ---- progress: one straight line, the done share solid, the rest faint (Lh 2026-09-22) — a voice note's
261
245
  timeline, a download, a step count as a line. <div class="progress" role="progressbar" style="--progress-at: 0.4"></div>
@@ -272,3 +256,24 @@
272
256
  background-size: calc(var(--progress-at, 0) * 100%) 100%;
273
257
  background-repeat: no-repeat;
274
258
  }
259
+
260
+ /* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
261
+ /* a TRACK as the screen's TOP (Lh 2026-10-03): the walk's progress stands where a bar would, a row of the column, in the screen's
262
+ inset — and it never touches what comes up under it: the step (and so a sheet at full) starts --track-top-gap below it. That
263
+ room is the track's own, not the screen's gap; --track-top-lead is the air over it, under the status bar. Both = the screen's side inset (16): the pill
264
+ stands the same distance from everything around it (Lh 2026-10-03: 8 over was too little, 24 under too much). */
265
+ .screen--fill > .track:first-child {
266
+ flex: none;
267
+ margin-top: var(--track-top-lead, var(--sp-4));
268
+ margin-bottom: calc(var(--track-top-gap, var(--sp-4)) - var(--screen-gap));
269
+ }
270
+ /* …and over a STAGE (a map behind a sheet, Lh 2026-10-03): the stage is the whole screen, so the track is a LAYER on it — the map
271
+ shows around the pill — and the stage keeps the track's line free itself: a sheet at full stops --track-top-gap under the
272
+ track, as in a step. --top-h is the track's own height, measured by Screen (a numbered track grows with the text). */
273
+ .screen--fill > .track:first-child:has(+ .sheet-stage) {
274
+ position: absolute; z-index: var(--z-over);
275
+ inset: calc(var(--safe-top) + var(--track-top-lead, var(--sp-4))) var(--screen-pad) auto;
276
+ margin: 0;
277
+ }
278
+ /* ---- where it sits in what media.css draws (docs/reference.md rule 15) ---- */
279
+ .postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
package/css/palette.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ============================================================
2
- cardds/palette.css — COLOR SETTINGS. The only file you tune.
2
+ cardds/palette.css — the palettes cardds ships. A project tunes its own theme.css (theme.template.css), never this file.
3
3
  Apply with <html data-palette="clay"> (or <body>).
4
4
  No attribute = wireframe grayscale from tokens.css.
5
5
  Each palette overrides role tokens only — components never change.
package/css/people.css CHANGED
@@ -21,12 +21,12 @@
21
21
  font: var(--avatar-font, var(--type-label));
22
22
  font-size: calc(var(--_size) * 0.3);
23
23
  text-transform: uppercase;
24
- letter-spacing: 0.02em;
24
+ letter-spacing: var(--track-loose);
25
25
  position: relative;
26
26
  text-decoration: none; /* .avatar works as <a> too */
27
27
  }
28
28
  .avatar > img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
29
- .avatar > .icon { width: 50%; height: 50%; }
29
+ .avatar > .icon { --icon-size: 50%; }
30
30
  .avatar--sm { --_size: var(--avatar-sm); }
31
31
  .avatar--lg { --_size: var(--avatar-lg); }
32
32
  .avatar--xl { --_size: var(--avatar-xl); } /* sheet head icon ring */
@@ -42,7 +42,7 @@
42
42
  inset: calc(-0.55 * var(--_size));
43
43
  border-radius: 50%;
44
44
  background: repeating-radial-gradient(circle, transparent 0 calc(var(--_size) * 0.5), currentColor calc(var(--_size) * 0.5) calc(var(--_size) * 0.5 + var(--border-w)), transparent calc(var(--_size) * 0.5 + var(--border-w)) calc(var(--_size) * 0.68));
45
- opacity: 0.25;
45
+ opacity: var(--fade-faint);
46
46
  pointer-events: none;
47
47
  }
48
48
 
@@ -51,11 +51,6 @@
51
51
  .avatar-stack { display: inline-flex; align-items: center; }
52
52
  .avatar-stack > .avatar { box-shadow: 0 0 0 var(--sp-1) var(--card-bg, var(--surface)); }
53
53
  .avatar-stack > .avatar + .avatar { margin-left: calc(-1 * var(--sp-3)); }
54
- /* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
55
- lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
56
- <span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
57
- .avatar-stack > .stamp { flex: none; margin-inline: 0; }
58
- .avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
59
54
  /* more: the count that trails the stack — plain words on the ground, title size so it reads at arm's length (the accent "+7" avatar is the small, framed way) */
60
55
  .avatar-stack__more { margin-left: var(--avatar-stack-more-gap, var(--sp-3)); font: var(--avatar-stack-more-font, var(--type-title)); color: var(--avatar-stack-more-ink, inherit); white-space: nowrap; }
61
56
 
package/css/post.css CHANGED
@@ -19,7 +19,7 @@
19
19
  .postmark {
20
20
  --_base: calc(var(--tap) * 1.75);
21
21
  position: relative;
22
- z-index: 1;
22
+ z-index: var(--z-over);
23
23
  flex: none;
24
24
  display: grid;
25
25
  place-content: center;
@@ -34,7 +34,7 @@
34
34
  color: var(--postmark-ink, color-mix(in srgb, currentColor 72%, transparent));
35
35
  font: var(--postmark-font, var(--type-label));
36
36
  text-align: center;
37
- rotate: var(--postmark-tilt, -10deg);
37
+ rotate: var(--postmark-tilt, calc(var(--tilt-base) * -10));
38
38
  }
39
39
  .postmark::after { /* the cancel lines that run on over the stamp */
40
40
  content: "";
@@ -59,9 +59,8 @@
59
59
  display: grid;
60
60
  width: var(--post-corner-stamp-w, var(--_w));
61
61
  aspect-ratio: 4 / 5;
62
- rotate: var(--post-corner-stamp-tilt, 2deg);
62
+ rotate: var(--post-corner-stamp-tilt, calc(var(--tilt-base) * 2));
63
63
  }
64
- .post-corner__stamp > :is(.stamp, .stamp-place) { grid-area: 1 / 1; width: 100%; max-width: none; } /* the corner is the postcard's own size (72 × 90), outside the stamp's two sizes, by the owner's decision (2026-10-11); its box sizes the stamp, which fills it */
65
64
 
66
65
  /* ============================================================
67
66
  post-letter — READING one that arrived. The written side stood upright: the address half over the message half.
@@ -102,15 +101,15 @@
102
101
  min-height: 0;
103
102
  overflow: clip;
104
103
  visibility: hidden;
105
- rotate: calc(-1 * var(--post-letter-tilt, 2deg));
104
+ rotate: calc(-1 * var(--post-letter-tilt, calc(var(--tilt-base) * 2)));
106
105
  }
107
- .post-letter__sheet--back { rotate: var(--post-letter-tilt, 2deg); } /* turned over, the lean is the other way */
106
+ .post-letter__sheet--back { rotate: var(--post-letter-tilt, calc(var(--tilt-base) * 2)); } /* turned over, the lean is the other way */
108
107
  .post-letter--back > .post-letter__sheet--back,
109
108
  .post-letter--picture > .post-letter__sheet--picture { visibility: visible; }
110
109
  .post-letter:not(:has(> .post-letter__journey)) { --_room: 0rem; } /* no journey line: its room goes back to the sheet */
111
110
  .post-letter__journey { grid-area: 2 / 1; margin: var(--sp-3) 0 0; text-align: center; font: var(--type-caption); color: var(--post-letter-journey-ink, var(--on-surface-muted)); }
112
111
 
113
- /* the picture side: the print with its white margin, the place printed under it */
112
+ /* the picture side: the print with its margin of paper, the place printed under it */
114
113
  .post-letter__sheet--picture {
115
114
  display: grid;
116
115
  grid-template-rows: minmax(0, 1fr) auto;
@@ -129,7 +128,7 @@
129
128
  .post-letter__sheet--back { display: flex; flex-direction: column; gap: var(--post-letter-gap, var(--sp-4)); padding: var(--post-letter-pad, var(--sp-5)); }
130
129
  .post-letter__head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--sp-2); padding-block-end: var(--sp-4); border-block-end: var(--rule-w) solid var(--_rule); }
131
130
  .post-letter__to { display: grid; gap: var(--sp-2); min-width: 0; align-self: stretch; align-content: space-between; }
132
- .post-letter__kicker { font: var(--type-caption); color: var(--_muted); letter-spacing: 0.08em; }
131
+ .post-letter__kicker { font: var(--type-caption); color: var(--_muted); letter-spacing: var(--track-caps); }
133
132
  .post-letter__lines { display: grid; gap: var(--sp-1); }
134
133
  .post-letter__line { display: block; padding-block: var(--sp-1); border-block-end: var(--border-w) solid var(--_rule); font: var(--type-caption); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
135
134
  .post-letter__line--name { font: var(--type-title); }
@@ -142,7 +141,7 @@
142
141
  gap: var(--sp-3);
143
142
  font: var(--post-letter-hand-font, var(--type-h2));
144
143
  font-size: var(--post-letter-hand-font-size, var(--fs-lg));
145
- line-height: var(--post-letter-hand-leading, 1.6);
144
+ line-height: var(--post-letter-hand-leading, var(--lh-hand));
146
145
  }
147
146
  .post-letter__message > p { margin: 0; }
148
147
  .post-letter__sign { align-self: flex-end; }
@@ -154,7 +153,7 @@
154
153
  inset-block-start: calc(var(--_peek) / 2);
155
154
  inset-inline-start: var(--sp-2);
156
155
  translate: 0 -50%;
157
- z-index: 2;
156
+ z-index: var(--z-over-2);
158
157
  display: inline-flex;
159
158
  align-items: center;
160
159
  gap: var(--sp-2);
@@ -215,8 +214,8 @@
215
214
  background: transparent;
216
215
  color: inherit;
217
216
  font: var(--post-desk-hand-font, var(--type-h2));
218
- line-height: calc(var(--fs-body) * var(--post-desk-leading, 1.7));
219
- --_line: calc(var(--fs-body) * var(--post-desk-leading, 1.7));
217
+ line-height: calc(var(--fs-body) * var(--post-desk-leading, var(--lh-ruled)));
218
+ --_line: calc(var(--fs-body) * var(--post-desk-leading, var(--lh-ruled)));
220
219
  /* the ruled lines: one under every line of writing */
221
220
  background-image: linear-gradient(to bottom, transparent calc(100% - var(--border-w)), var(--_rule) 0);
222
221
  background-size: 100% var(--_line);
@@ -233,10 +232,10 @@
233
232
  /* the stamp box: empty it is a place of dots to press; pressed, the chosen stamp lies in it */
234
233
  .post-desk__slot { border: 0; background: transparent; color: inherit; }
235
234
  @media (prefers-reduced-motion: no-preference) {
236
- .post-desk__slot > .stamp { animation: post-desk-stick var(--motion-quick) ease-out both; }
237
- .post-desk .postmark { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) ease-in both; } /* struck as a StampOn is (`strike`: tokens.css), in the same time */
235
+ .post-desk__slot > .stamp { --stamp-arrive: post-desk-stick var(--motion-quick) var(--ease-out) both; }
236
+ .post-desk .postmark { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) var(--ease-in) both; } /* struck as a StampOn is (`strike`: tokens.css), in the same time */
238
237
  }
239
- @keyframes post-desk-stick { from { translate: 0 calc(-1 * var(--sp-8)); rotate: 14deg; opacity: 0; } }
238
+ @keyframes post-desk-stick { from { translate: 0 calc(-1 * var(--sp-8)); rotate: calc(var(--tilt-base) * 14); opacity: 0; } }
240
239
  /* the other half's showing edge brings it across: a tap-wide strip out to the screen's edge */
241
240
  .post-desk__reach { position: absolute; inset-block: 0; width: var(--tap); border: 0; background: transparent; }
242
241
  .post-desk__reach--address { inset-inline-end: calc(-1 * var(--screen-pad)); }
@@ -266,7 +265,7 @@
266
265
  border-block-end: var(--rule-w) solid transparent;
267
266
  background: transparent;
268
267
  }
269
- .post-desk__pick > .stamp { rotate: var(--post-corner-stamp-tilt, 2deg); } /* in the tray a stamp keeps its words */
268
+ .post-desk__pick > .stamp { --stamp-turn: var(--post-corner-stamp-tilt, calc(var(--tilt-base) * 2)); } /* in the tray a stamp keeps its words */
270
269
  .post-desk__pick[aria-pressed="true"] { border-block-end-color: var(--on-surface); }
271
270
  .post-desk__note { margin: 0; font: var(--type-caption); color: var(--post-desk-note-ink, var(--on-surface-muted)); min-height: 1.35em; }
272
271
  /* what stands where the card was, once it has left */
@@ -291,7 +290,7 @@
291
290
  margin-block-end: calc(var(--_e) - var(--_h));
292
291
  display: grid;
293
292
  grid-template-rows: var(--post-box-edge, var(--_edge)) minmax(0, 1fr);
294
- transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
293
+ transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease);
295
294
  }
296
295
  .post-box {
297
296
  --_edge: calc(var(--sp-base) * 20);
@@ -306,8 +305,8 @@
306
305
  }
307
306
  .post-box:has(> .post-box__card.is-open:not(:last-child)) { padding-block-end: var(--_part); }
308
307
  .post-box__card:last-child { margin-block-end: 0; }
309
- .post-box__card:nth-child(3n + 1) { rotate: var(--post-box-lean, 0.6deg); }
310
- .post-box__card:nth-child(3n + 2) { rotate: calc(var(--post-box-lean, 0.6deg) * -0.8); }
308
+ .post-box__card:nth-child(3n + 1) { rotate: var(--post-box-lean, calc(var(--tilt-base) * 0.6)); }
309
+ .post-box__card:nth-child(3n + 2) { rotate: calc(var(--post-box-lean, calc(var(--tilt-base) * 0.6)) * -0.8); }
311
310
  .post-box__card.is-open { rotate: none; }
312
311
  .post-box__card.is-open ~ .post-box__card { translate: 0 var(--_part); }
313
312
  .post-box__edge {
@@ -334,3 +333,8 @@
334
333
  .post-box__card:not(.is-open) .postmark > span:last-child:not(:first-child) { display: none; }
335
334
  .post-box__card:not(.is-open) .postmark { box-sizing: border-box; place-content: start center; padding-block-start: calc(var(--_size) * 0.18); }
336
335
  @media (prefers-reduced-motion: reduce) { .post-box__card { transition: none; } }
336
+
337
+ /* ---- the desk's card leaves as a card with `leave` does (card.css LEAVE): the same two knobs, the same time ---- */
338
+ .post-desk__card { transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease); }
339
+ .post-desk--sent .post-desk__card { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, calc(var(--tilt-base) * -5)); }
340
+ @media (prefers-reduced-motion: reduce) { .post-desk__card { transition: none; } }