anentrypoint-design 0.0.312 → 0.0.314

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/app-shell.css CHANGED
@@ -241,11 +241,15 @@ body.canvas-host { background: transparent !important; }
241
241
  .app-topbar nav a { flex: 0 0 auto; }
242
242
  .app-topbar nav a {
243
243
  color: var(--fg-2);
244
- padding: 12px 14px;
245
- min-height: 44px;
244
+ /* Dense desktop chrome (was a 44px pill with 12px 14px pad — marketing-scale
245
+ touch target in the top bar). Matches the .app-side a treatment: ~34px,
246
+ --r-1 rounded rect, tighter padding. The 44px tap target is restored under
247
+ @media(pointer:coarse) (the existing .app-topbar nav a rule further down). */
248
+ padding: var(--space-2) var(--space-2-5);
249
+ min-height: 34px;
246
250
  display: flex;
247
251
  align-items: center;
248
- border-radius: var(--r-pill);
252
+ border-radius: var(--r-1);
249
253
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
250
254
  }
251
255
  .app-topbar nav a:hover { background: var(--bg-2); color: var(--fg); }
@@ -319,13 +323,19 @@ body.canvas-host { background: transparent !important; }
319
323
  }
320
324
  .app-side a {
321
325
  display: grid; grid-template-columns: 18px 1fr auto;
322
- gap: 10px; align-items: center;
323
- padding: 12px 14px;
324
- min-height: 44px;
325
- border-radius: var(--r-pill);
326
+ gap: var(--space-2-5); align-items: center;
327
+ /* Dense rounded-rect nav row (was a 44px pill with 12px 14px padding — tall
328
+ and consumer-shaped). A --r-1 rounded rect at ~34px reads as compact app
329
+ chrome; the full 44px touch target is restored under pointer:coarse below. */
330
+ padding: var(--space-2) var(--space-2-5);
331
+ min-height: 34px;
332
+ border-radius: var(--r-1);
326
333
  color: var(--fg-2);
327
334
  margin-bottom: 2px;
328
335
  }
336
+ @media (pointer: coarse) {
337
+ .app-side a { min-height: 44px; padding: var(--space-2-75) var(--space-3); }
338
+ }
329
339
  .app-side a:hover { background: var(--bg-2); color: var(--fg); }
330
340
  .app-side a:focus-visible {
331
341
  outline: var(--focus-w) solid var(--focus-color);
@@ -448,10 +458,16 @@ body.canvas-host { background: transparent !important; }
448
458
  ============================================================ */
449
459
  .btn, .btn-primary, .btn-ghost {
450
460
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
451
- padding: 8px 14px; min-height: 32px;
461
+ /* Fixed height + horizontal-only padding: the control height is exact and
462
+ alignable (flex centres the label vertically), the way webgeist snaps every
463
+ control to a height ladder. Default is the --ctl-md rung; --btn-sm/--btn-lg
464
+ retune height + h-padding + font per size. Density scales the ladder. */
465
+ height: var(--ctl-md); min-height: var(--ctl-md);
466
+ padding: 0 var(--space-2-75);
452
467
  font-family: var(--ff-body);
453
468
  font-weight: 600; font-size: var(--fs-sm);
454
469
  text-decoration: none;
470
+ white-space: nowrap;
455
471
  cursor: pointer;
456
472
  /* Rounded-rect app chrome, not a marketing-page stadium CTA.
457
473
  Shape vocabulary: r-1/r-2 = actionable buttons, icon-buttons, and
@@ -484,6 +500,33 @@ body.canvas-host { background: transparent !important; }
484
500
  outline-offset: var(--focus-offset);
485
501
  }
486
502
 
503
+ /* Button size ladder — the size prop on Btn() (sm | md | lg). md is the base
504
+ rule above; sm/lg retune height (off the --ctl-* ladder), horizontal padding,
505
+ and font-size so a size swap is one class, never inline padding overrides
506
+ (the old preview faked sizes with style=). Applies to every variant. */
507
+ .btn-sm, .btn-sm.btn-primary, .btn-sm.btn-ghost {
508
+ height: var(--ctl-sm); min-height: var(--ctl-sm);
509
+ padding: 0 var(--space-2); font-size: var(--fs-tiny); gap: var(--space-1);
510
+ }
511
+ .btn-lg, .btn-lg.btn-primary, .btn-lg.btn-ghost {
512
+ height: var(--ctl-lg); min-height: var(--ctl-lg);
513
+ padding: 0 var(--space-4); font-size: var(--fs-body); gap: var(--space-2);
514
+ }
515
+
516
+ /* App-mode quiet interactions — the marketing hover-lift (translateY + shadow)
517
+ and the .row .meta slide read as decorative float on a dense working surface.
518
+ Under [data-typescale="app"], state changes are background/border-only: crisp,
519
+ no motion, no elevation. Marketing surfaces (no data-typescale) keep the lift. */
520
+ [data-typescale="app"] .btn:hover,
521
+ [data-typescale="app"] .btn-primary:hover,
522
+ [data-typescale="app"] .btn-ghost:hover { transform: none; box-shadow: none; }
523
+ [data-typescale="app"] .btn:active,
524
+ [data-typescale="app"] .btn-primary:active,
525
+ [data-typescale="app"] .btn-ghost:active { transform: none; }
526
+ [data-typescale="app"] .panel { transition: box-shadow var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease); }
527
+ [data-typescale="app"] a.row:hover .meta,
528
+ [data-typescale="app"] .row[role="button"]:hover .meta { transform: none; }
529
+
487
530
  /* ============================================================
488
531
  IconButton — square icon-only button
489
532
  ============================================================ */
@@ -521,6 +564,10 @@ body.canvas-host { background: transparent !important; }
521
564
  border-radius: var(--r-pill);
522
565
  background: var(--bg-3); color: var(--fg-2);
523
566
  }
567
+ /* Badge size ladder — sm for dense inline counts, lg for prominent markers.
568
+ Base (above) stays the 18px default. */
569
+ .ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size: 10px; }
570
+ .ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
524
571
  /* Canonical tone names are tone-success / tone-error / tone-neutral. Every
525
572
  other tone name grouped into the same rule below (tone-green/tone-live,
526
573
  tone-flame, tone-wip, tone-sun/tone-yellow, tone-ok/tone-miss) is a
@@ -544,7 +591,7 @@ body.canvas-host { background: transparent !important; }
544
591
  .ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
545
592
 
546
593
  .chip {
547
- display: inline-flex; align-items: center; gap: 6px;
594
+ display: inline-flex; align-items: center; gap: var(--space-1-75);
548
595
  padding: 3px 10px;
549
596
  background: var(--bg-2); color: var(--fg-2);
550
597
  font-family: var(--ff-body);
@@ -552,6 +599,17 @@ body.canvas-host { background: transparent !important; }
552
599
  letter-spacing: var(--tr-caps); text-transform: uppercase;
553
600
  border-radius: var(--r-pill);
554
601
  }
602
+ /* Chip size ladder — sm for dense toolbars/table cells, lg for prominent
603
+ status. tone-* colouring (below) is orthogonal to size. */
604
+ .chip.chip--sm { padding: var(--space-half) var(--space-2); font-size: var(--fs-micro); gap: var(--space-1); }
605
+ .chip.chip--lg { padding: var(--space-1-75) var(--space-2-75); font-size: var(--fs-xs); }
606
+ /* Tag variant — a rectangular, sentence-case chip for dense data (table cells,
607
+ inline labels) where the all-caps pill reads too shouty and space-hungry.
608
+ Keeps the tone-* palette; drops the caps + pill for a tight --r-0 corner. */
609
+ .chip.chip--tag {
610
+ text-transform: none; letter-spacing: 0; font-weight: 500;
611
+ border-radius: var(--r-0); padding: var(--space-half) var(--space-2);
612
+ }
555
613
  /* Same canonical/legacy-alias grouping as .ds-badge above. */
556
614
  .chip.tone-green, .chip.tone-live, .chip.tone-success, .chip.tone-ok {
557
615
  background: var(--green-tint); color: var(--green-deep);
@@ -621,6 +679,17 @@ body.canvas-host { background: transparent !important; }
621
679
  }
622
680
  .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
623
681
  .panel.panel-wide { background: transparent; box-shadow: none; border: none; }
682
+ /* Dense panel — a compact container for app surfaces (inspector sections,
683
+ sidebars) that want the .panel look without the roomy 16px pad + 24px stack
684
+ gap. Tighter corner + padding + head, so it sits between .panel and the
685
+ flush editor .ds-ep-panel. */
686
+ .panel.panel--dense {
687
+ padding: var(--space-2); margin-bottom: var(--space-2-75);
688
+ border-radius: var(--r-1);
689
+ }
690
+ .panel.panel--dense .panel-head { padding: var(--space-1-5) var(--space-2); }
691
+ .panel.panel--dense .panel-body { padding: var(--space-1-5) var(--space-2) var(--space-2); }
692
+ .panel.panel--dense .panel-body > * + * { margin-top: var(--space-2); }
624
693
  .panel-head {
625
694
  display: flex; align-items: baseline; justify-content: space-between;
626
695
  /* Shares the .panel-body 16px side padding so the caps label and the body
@@ -650,9 +719,13 @@ body.canvas-host { background: transparent !important; }
650
719
  grid-template-columns: minmax(80px, 12ch) minmax(0, 1fr) auto;
651
720
  gap: var(--space-3);
652
721
  align-items: baseline;
653
- padding: var(--space-3) var(--space-4);
722
+ /* App density is the BASE: a list row is quiet working chrome, so it defaults
723
+ to the compact 12px/16px rhythm (density-scaled) rather than the old
724
+ 16px/24px marketing padding. .row--lede restores the roomier marketing
725
+ voice for editorial index pages. */
726
+ padding: calc(var(--space-2-75) * var(--density)) var(--space-3);
654
727
  background: var(--bg);
655
- border-radius: var(--r-2);
728
+ border-radius: var(--r-1);
656
729
  color: var(--fg);
657
730
  position: relative;
658
731
  /* Only background + the leading rail animate; padding/size never change on
@@ -730,8 +803,14 @@ a.row:hover .meta, .row[role="button"]:hover .meta { color: var(--accent-ink); t
730
803
  .row.row-nocode { grid-template-columns: minmax(0, 1fr) auto; }
731
804
 
732
805
  .row .code { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
733
- .row .title { font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-lg); line-height: var(--lh-snug); display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0; }
806
+ /* Title defaults to the quiet app voice (fs-sm/500) the 18px/600 marketing
807
+ voice is now opt-in via .row--lede. */
808
+ .row .title { font-family: var(--ff-body); font-weight: 500; font-size: var(--fs-sm); line-height: var(--lh-snug); display: flex; align-items: baseline; gap: var(--space-2-5); flex-wrap: wrap; min-width: 0; }
734
809
  .row .title .sub { font-family: var(--ff-body); font-weight: 400; font-size: var(--fs-sm); color: var(--fg-3); }
810
+ /* Marketing/editorial index row — restores the roomier padding + larger title
811
+ the default carried before app-scale became the base. */
812
+ .row--lede { padding: var(--space-3) var(--space-4); }
813
+ .row--lede .title { font-size: var(--fs-lg); font-weight: 600; }
735
814
  /* App typescale: list rows are quiet working chrome (the 18px/600 default is a
736
815
  marketing-surface voice). One size for ALL app list rows - matches
737
816
  .ds-file-row/.ds-session-title at fs-sm/500; values mirror the kit's own
@@ -970,16 +1049,19 @@ table.kv td:last-child {
970
1049
  table {
971
1050
  width: 100%; border-collapse: collapse;
972
1051
  font-family: var(--ff-body); font-size: var(--fs-sm);
973
- border-radius: var(--r-3); overflow: hidden;
1052
+ /* A data grid, not a card: a tight --r-0 corner (was --r-3/22px, which read
1053
+ as a rounded card). Cells drop to 8px 12px for a dense, scannable row
1054
+ (was 12/14 x 16 => ~43px rows). Density scales the vertical padding. */
1055
+ border-radius: var(--r-0); overflow: hidden;
974
1056
  }
975
1057
  table th {
976
1058
  text-align: left;
977
- padding: 12px 16px;
1059
+ padding: calc(var(--space-2) * var(--density)) var(--space-2-75);
978
1060
  font-size: var(--fs-tiny); font-weight: 600;
979
1061
  color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps);
980
1062
  background: var(--bg-2);
981
1063
  }
982
- table td { padding: 14px 16px; border-top: 1px solid var(--rule); }
1064
+ table td { padding: calc(var(--space-2) * var(--density)) var(--space-2-75); border-top: 1px solid var(--rule); }
983
1065
  table tr.clickable { cursor: pointer; }
984
1066
  table tr.clickable:hover td { background: var(--bg-2); }
985
1067
  table tr.clickable:focus-visible {
@@ -1047,13 +1129,16 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1047
1129
  gap: var(--space-2);
1048
1130
  }
1049
1131
  .kpi-card {
1050
- padding: var(--space-5);
1132
+ /* Was --space-5 (32px) pad + --r-3 (22px) — four cards ate a full fold. A
1133
+ 16px pad on a --r-1 corner keeps them prominent but dense; the number drops
1134
+ from a 48px marketing display to a 32px app-scale figure. */
1135
+ padding: var(--space-3);
1051
1136
  background: var(--bg-2);
1052
- border-radius: var(--r-3);
1137
+ border-radius: var(--r-1);
1053
1138
  }
1054
1139
  .kpi-card .num {
1055
1140
  font-family: var(--ff-body); font-weight: 600;
1056
- font-size: clamp(30px, 3.6cqi, 48px); line-height: 1;
1141
+ font-size: clamp(22px, 3cqi, 32px); line-height: 1;
1057
1142
  letter-spacing: var(--tr-tight);
1058
1143
  }
1059
1144
  .kpi-card .lbl {
@@ -1102,11 +1187,14 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1102
1187
  Empty state
1103
1188
  ============================================================ */
1104
1189
  .empty {
1105
- padding: var(--space-8);
1190
+ /* Was --space-8 (96px) — an empty list spent 192px of vertical space on
1191
+ nothing. --space-6 (48px) still centres a message with air, without the
1192
+ empty state dominating the fold. Mobile drops it further (below). */
1193
+ padding: var(--space-6);
1106
1194
  text-align: center;
1107
1195
  color: var(--fg-3);
1108
1196
  background: var(--bg-2);
1109
- border-radius: var(--r-3);
1197
+ border-radius: var(--r-1);
1110
1198
  font-size: var(--fs-sm);
1111
1199
  }
1112
1200
 
@@ -2119,6 +2207,54 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2119
2207
  .ds-preview-meta { display: none; }
2120
2208
  }
2121
2209
 
2210
+ /* ============================================================
2211
+ Catalog / demo shell — a shared, compact framing for the preview/*.html
2212
+ component galleries, so each preview stops hand-rolling its own <style>
2213
+ (body padding, section margins, demo boxes). Mirrors webgeist's dense
2214
+ catalog: a 240px sticky sidebar + capped main + a hairline-bordered,
2215
+ flex-wrapping .ds-demo cell that packs variants inline and reflows.
2216
+ ============================================================ */
2217
+ .ds-catalog { display: flex; min-height: 100vh; gap: 0; }
2218
+ .ds-catalog-side {
2219
+ width: 240px; flex-shrink: 0;
2220
+ border-right: var(--bw-hair) solid var(--rule);
2221
+ padding: var(--space-3);
2222
+ position: sticky; top: 0; height: 100vh; overflow-y: auto;
2223
+ }
2224
+ .ds-catalog-side h1 { font-size: var(--fs-lg); margin: 0 0 var(--space-1); }
2225
+ .ds-catalog-side .subtitle { font-size: var(--fs-micro); color: var(--fg-3); margin: 0 0 var(--space-4); }
2226
+ .ds-catalog-side nav h4 {
2227
+ font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--tr-label);
2228
+ color: var(--fg-3); margin: var(--space-3) 0 var(--space-1);
2229
+ }
2230
+ .ds-catalog-side nav a {
2231
+ display: block; padding: var(--space-half) 0; font-size: var(--fs-tiny);
2232
+ color: var(--fg-2); text-decoration: none;
2233
+ }
2234
+ .ds-catalog-side nav a:hover { color: var(--accent-ink); }
2235
+ .ds-catalog-main { flex: 1; padding: var(--space-5); max-width: var(--measure-wide); min-width: 0; }
2236
+ .ds-catalog-section { margin-bottom: var(--space-5); padding-top: var(--space-3); border-top: var(--bw-hair) solid var(--rule); }
2237
+ .ds-catalog-section > h2 { font-size: var(--fs-h3-app); margin: 0 0 var(--space-3); }
2238
+
2239
+ /* The gallery cell: a hairline-bordered, flex-wrapping bin that packs a
2240
+ component's variants inline and reflows — the signature "showcase" frame. */
2241
+ .ds-demo {
2242
+ border: var(--bw-hair) solid var(--rule);
2243
+ border-radius: var(--r-1);
2244
+ padding: var(--space-3);
2245
+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
2246
+ background: var(--bg);
2247
+ }
2248
+ .ds-demo + .ds-demo { margin-top: var(--space-2-75); }
2249
+ .ds-demo-label { font-size: var(--fs-xs); font-weight: 500; margin: 0 0 var(--space-1); }
2250
+ .ds-demo-desc { font-size: var(--fs-tiny); color: var(--fg-3); margin: 0 0 var(--space-2); }
2251
+
2252
+ @media (max-width: 768px) {
2253
+ .ds-catalog { flex-direction: column; }
2254
+ .ds-catalog-side { width: 100%; height: auto; position: static; border-right: 0; border-bottom: var(--bw-hair) solid var(--rule); }
2255
+ .ds-catalog-main { padding: var(--space-3); }
2256
+ }
2257
+
2122
2258
  /* -- File browser UX affordances ---------------------------- */
2123
2259
  /* Toolbar filter input — compact search box. */
2124
2260
  .ds-filter-input {
@@ -2986,19 +3122,61 @@ input[type="checkbox"], input[type="radio"] {
2986
3122
  Enhanced Form Inputs
2987
3123
  -------------------------------------------------------------- */
2988
3124
 
2989
- input[type="text"],
2990
- input[type="email"],
2991
- input[type="password"],
2992
- input[type="number"],
2993
- input[type="search"],
2994
- input[type="url"],
2995
- input[type="tel"],
2996
- textarea,
2997
- select {
3125
+ /* Base chrome for any BARE native control (outside .ds-field/.row-form/.input):
3126
+ the same editorial tonal-fill + printed-baseline-rule as .ds-field, so a raw
3127
+ <input> is never a browser-default white box in dark mode. The guards are
3128
+ wrapped in :where() so they add ZERO specificity — the whole selector stays
3129
+ at (0,1,1), exactly like `.ds-field input`, so the later .ds-field / .ds-field--sm
3130
+ / .row-form descendant rules win by source order instead of being out-specified
3131
+ (a bare :not(.input) counts as a class and would beat every .ds-field rule,
3132
+ which clobbered the size variants). */
3133
+ input[type="text"]:where(:not(.input):not(.ds-search-input)),
3134
+ input[type="email"]:where(:not(.input):not(.ds-search-input)),
3135
+ input[type="password"]:where(:not(.input):not(.ds-search-input)),
3136
+ input[type="number"]:where(:not(.input):not(.ds-search-input)),
3137
+ input[type="search"]:where(:not(.input):not(.ds-search-input)),
3138
+ input[type="url"]:where(:not(.input):not(.ds-search-input)),
3139
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3140
+ textarea:where(:not(.input)),
3141
+ select:where(:not(.ds-select)) {
3142
+ font: inherit; color: var(--fg);
3143
+ background: var(--bg-2);
3144
+ border: 0;
3145
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3146
+ inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3147
+ border-radius: var(--r-1);
3148
+ padding: 0 var(--space-3);
2998
3149
  transition: background var(--dur-snap) var(--ease),
2999
3150
  border-color var(--dur-snap) var(--ease),
3000
3151
  box-shadow var(--dur-snap) var(--ease);
3001
3152
  }
3153
+ /* Single-line bare controls snap to the field height; textarea keeps auto
3154
+ height with symmetric padding (its content decides the box). */
3155
+ input[type="text"]:where(:not(.input):not(.ds-search-input)),
3156
+ input[type="email"]:where(:not(.input):not(.ds-search-input)),
3157
+ input[type="password"]:where(:not(.input):not(.ds-search-input)),
3158
+ input[type="number"]:where(:not(.input):not(.ds-search-input)),
3159
+ input[type="search"]:where(:not(.input):not(.ds-search-input)),
3160
+ input[type="url"]:where(:not(.input):not(.ds-search-input)),
3161
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3162
+ select:where(:not(.ds-select)) {
3163
+ height: var(--field-height);
3164
+ }
3165
+ textarea:where(:not(.input)) { padding: var(--space-2) var(--space-3); min-height: calc(4 * 1.5em); }
3166
+ input[type="text"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3167
+ input[type="email"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3168
+ input[type="password"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3169
+ input[type="number"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3170
+ input[type="search"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3171
+ input[type="url"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3172
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3173
+ textarea:where(:not(.input)):focus-visible,
3174
+ select:where(:not(.ds-select)):focus-visible {
3175
+ outline: none;
3176
+ background: var(--bg);
3177
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3178
+ inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
3179
+ }
3002
3180
 
3003
3181
  input[type="text"]::placeholder,
3004
3182
  input[type="email"]::placeholder,
@@ -3131,11 +3309,29 @@ hr.rule-dotted { border-top: 1px dotted var(--rule-strong); }
3131
3309
  the electric lead on focus, instead of a uniform hairline box. */
3132
3310
  box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3133
3311
  inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3134
- border-radius: var(--r-2, 10px);
3135
- padding: 10px 14px;
3312
+ /* Tighter corner than the old --r-2 (14px): a dense pro control, not a soft
3313
+ consumer box. Single-line controls snap to --field-height with h-only
3314
+ padding so they align pixel-exact with buttons on the same --ctl-md rung. */
3315
+ border-radius: var(--r-1);
3316
+ height: var(--field-height);
3317
+ padding: 0 var(--space-3);
3136
3318
  transition: box-shadow var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
3137
3319
  }
3138
- .ds-field textarea { min-height: calc(4 * 1.5em); resize: vertical; }
3320
+ /* textarea is multi-line: height is auto (min-height instead), keep symmetric
3321
+ vertical padding so text doesn't sit flush to the top edge. */
3322
+ .ds-field textarea { height: auto; min-height: calc(4 * 1.5em); padding: var(--space-2) var(--space-3); resize: vertical; }
3323
+
3324
+ /* Field size ladder (size prop on TextField/Select/SearchInput). md == base.
3325
+ sm/lg retune height + h-padding + font off the --ctl-* ladder, matching the
3326
+ button ladder so a form of mixed controls stays on one rhythm. */
3327
+ .ds-field--sm input, .ds-field--sm .ds-select, .ds-field--sm .ds-search-input {
3328
+ height: var(--ctl-sm); padding: 0 var(--space-2-5); font-size: var(--fs-tiny);
3329
+ }
3330
+ .ds-field--sm textarea { height: auto; padding: var(--space-1-75) var(--space-2-5); font-size: var(--fs-tiny); }
3331
+ .ds-field--lg input, .ds-field--lg .ds-select, .ds-field--lg .ds-search-input {
3332
+ height: var(--ctl-lg); padding: 0 var(--space-4); font-size: var(--fs-body);
3333
+ }
3334
+ .ds-field--lg textarea { height: auto; padding: var(--space-2-5) var(--space-4); font-size: var(--fs-body); }
3139
3335
  .ds-search-input::placeholder { color: var(--fg-3); }
3140
3336
  /* Only present when SearchInput is passed resultCount - transparent to any
3141
3337
  flex/grid layout the bare input previously sat in directly. */
@@ -184,7 +184,9 @@
184
184
  --space-1-75: 6px;
185
185
  --space-2: 8px;
186
186
  --space-2-5: 10px;
187
+ --space-2-75: 12px; /* fills the 10->16 hole (row-form gap, dense cell pad) */
187
188
  --space-3: 16px;
189
+ --space-3-5: 20px; /* fills the 16->24 hole (settings body, mono block pad) */
188
190
  --space-4: 24px;
189
191
  --space-5: 32px;
190
192
  --space-6: 48px;
@@ -258,6 +260,19 @@
258
260
  --size-base: 42px;
259
261
  --size-lg: 56px;
260
262
 
263
+ /* Dense control-height ladder — the actual heights front-door controls
264
+ (buttons, fields, rows) snap to, one rung tighter than the --size-*
265
+ chrome scale so app UI reads compact instead of marketing-scale. Density
266
+ scales them so [data-density=compact/spacious] moves every control at once.
267
+ Mirrors webgeist's 30/38/46 button ladder, retuned for this system. */
268
+ --ctl-sm: calc(28px * var(--density));
269
+ --ctl-md: calc(34px * var(--density));
270
+ --ctl-lg: calc(42px * var(--density));
271
+ /* The canonical single-line input/select/searchfield height (== --ctl-md)
272
+ and the default list-row min-height, both density-aware. */
273
+ --field-height: var(--ctl-md);
274
+ --row-height: calc(40px * var(--density));
275
+
261
276
  /* Unified focus-ring tokens (single source for the design system). Outset
262
277
  rings (the default) tokenize colour+width+offset; bordered text fields use
263
278
  the inset variant. Deliberate negative offsets on edge-flush/clipping
@@ -287,25 +302,26 @@
287
302
  outline-offset: var(--focus-offset);
288
303
  }
289
304
 
290
- /* Link focus-visible */
305
+ /* Link focus-visible — all route through the single --focus-* token set so a
306
+ focus retune (colour/width/offset) happens in one place, never per selector. */
291
307
  a:focus-visible,
292
308
  [role="link"]:focus-visible {
293
- outline: 2px solid var(--accent-ink);
294
- outline-offset: 2px;
309
+ outline: var(--focus-w) solid var(--focus-color);
310
+ outline-offset: var(--focus-offset);
295
311
  }
296
312
 
297
313
  /* Form inputs with focus-visible */
298
314
  input:focus-visible,
299
315
  textarea:focus-visible,
300
316
  select:focus-visible {
301
- outline: 2px solid var(--accent-ink);
302
- outline-offset: 2px;
317
+ outline: var(--focus-w) solid var(--focus-color);
318
+ outline-offset: var(--focus-offset);
303
319
  }
304
320
 
305
321
  /* Interactive element focus-visible */
306
322
  [tabindex]:focus-visible {
307
- outline: 2px solid var(--accent-ink);
308
- outline-offset: 2px;
323
+ outline: var(--focus-w) solid var(--focus-color);
324
+ outline-offset: var(--focus-offset);
309
325
  }
310
326
 
311
327
  [data-theme="ink"],
package/community.css CHANGED
@@ -297,7 +297,7 @@
297
297
  background: color-mix(in oklab, var(--fg) 8%, transparent);
298
298
  }
299
299
  .cm-cat-add:focus-visible {
300
- outline: 2px solid var(--accent-ink);
300
+ outline: var(--focus-w) solid var(--focus-color);
301
301
  outline-offset: 2px;
302
302
  }
303
303
 
@@ -480,7 +480,7 @@
480
480
  background: color-mix(in oklab, var(--fg) 10%, transparent);
481
481
  }
482
482
  .cm-ch-action-btn:focus-visible {
483
- outline: 2px solid var(--accent-ink);
483
+ outline: var(--focus-w) solid var(--focus-color);
484
484
  outline-offset: 2px;
485
485
  }
486
486
 
@@ -1063,44 +1063,44 @@
1063
1063
  .cm-server-icon:focus-visible,
1064
1064
  .cm-server-back:focus-visible,
1065
1065
  .cm-server-add:focus-visible {
1066
- outline: 2px solid var(--accent-ink);
1066
+ outline: var(--focus-w) solid var(--focus-color);
1067
1067
  outline-offset: 2px;
1068
1068
  border-radius: var(--r-2);
1069
1069
  }
1070
1070
 
1071
1071
  .cm-channel-item:focus-visible {
1072
- outline: 2px solid var(--accent-ink);
1072
+ outline: var(--focus-w) solid var(--focus-color);
1073
1073
  outline-offset: -2px;
1074
1074
  border-radius: var(--r-1);
1075
1075
  }
1076
1076
 
1077
1077
  .cm-server-header:focus-visible,
1078
1078
  .cm-category-header:focus-visible {
1079
- outline: 2px solid var(--accent-ink);
1079
+ outline: var(--focus-w) solid var(--focus-color);
1080
1080
  outline-offset: -2px;
1081
1081
  }
1082
1082
 
1083
1083
  .cm-member-item:focus-visible {
1084
- outline: 2px solid var(--accent-ink);
1084
+ outline: var(--focus-w) solid var(--focus-color);
1085
1085
  outline-offset: -2px;
1086
1086
  border-radius: var(--r-1);
1087
1087
  }
1088
1088
 
1089
1089
  .cm-vs-btn:focus-visible,
1090
1090
  .cm-user-btn:focus-visible {
1091
- outline: 2px solid var(--accent-ink);
1091
+ outline: var(--focus-w) solid var(--focus-color);
1092
1092
  outline-offset: 2px;
1093
1093
  border-radius: var(--r-1);
1094
1094
  }
1095
1095
 
1096
1096
  /* ThreadPanel / Forum / Page interactive elements */
1097
1097
  .cm-tp-item:focus-visible,
1098
- .cm-forum-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; border-radius: var(--r-1); }
1098
+ .cm-forum-item:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; border-radius: var(--r-1); }
1099
1099
  .cm-tp-new:focus-visible,
1100
1100
  .cm-tp-close:focus-visible,
1101
1101
  .cm-forum-new:focus-visible,
1102
1102
  .cm-forum-sort:focus-visible,
1103
- .cm-page-edit:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--r-1); }
1103
+ .cm-page-edit:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 2px; border-radius: var(--r-1); }
1104
1104
  .cm-forum-search:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent-ink); }
1105
1105
 
1106
1106
  /* ---------- mobile header ---------- */
@@ -1129,7 +1129,7 @@
1129
1129
  border-radius: var(--r-1);
1130
1130
  }
1131
1131
  .cm-mh-btn:hover { background: var(--panel-3); }
1132
- .cm-mh-btn:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1132
+ .cm-mh-btn:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 2px; }
1133
1133
  .cm-mh-title {
1134
1134
  flex: 1 1 auto;
1135
1135
  min-width: 0;
@@ -1174,7 +1174,7 @@
1174
1174
  border-radius: var(--r-1);
1175
1175
  }
1176
1176
  .cm-rb-cancel:hover { background: var(--panel-3); color: var(--fg); }
1177
- .cm-rb-cancel:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
1177
+ .cm-rb-cancel:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; }
1178
1178
 
1179
1179
  /* ---------- banner ---------- */
1180
1180
  .cm-banner {