anentrypoint-design 0.0.312 → 0.0.313

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
@@ -319,13 +319,19 @@ body.canvas-host { background: transparent !important; }
319
319
  }
320
320
  .app-side a {
321
321
  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);
322
+ gap: var(--space-2-5); align-items: center;
323
+ /* Dense rounded-rect nav row (was a 44px pill with 12px 14px padding — tall
324
+ and consumer-shaped). A --r-1 rounded rect at ~34px reads as compact app
325
+ chrome; the full 44px touch target is restored under pointer:coarse below. */
326
+ padding: var(--space-2) var(--space-2-5);
327
+ min-height: 34px;
328
+ border-radius: var(--r-1);
326
329
  color: var(--fg-2);
327
330
  margin-bottom: 2px;
328
331
  }
332
+ @media (pointer: coarse) {
333
+ .app-side a { min-height: 44px; padding: var(--space-2-75) var(--space-3); }
334
+ }
329
335
  .app-side a:hover { background: var(--bg-2); color: var(--fg); }
330
336
  .app-side a:focus-visible {
331
337
  outline: var(--focus-w) solid var(--focus-color);
@@ -448,10 +454,16 @@ body.canvas-host { background: transparent !important; }
448
454
  ============================================================ */
449
455
  .btn, .btn-primary, .btn-ghost {
450
456
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
451
- padding: 8px 14px; min-height: 32px;
457
+ /* Fixed height + horizontal-only padding: the control height is exact and
458
+ alignable (flex centres the label vertically), the way webgeist snaps every
459
+ control to a height ladder. Default is the --ctl-md rung; --btn-sm/--btn-lg
460
+ retune height + h-padding + font per size. Density scales the ladder. */
461
+ height: var(--ctl-md); min-height: var(--ctl-md);
462
+ padding: 0 var(--space-2-75);
452
463
  font-family: var(--ff-body);
453
464
  font-weight: 600; font-size: var(--fs-sm);
454
465
  text-decoration: none;
466
+ white-space: nowrap;
455
467
  cursor: pointer;
456
468
  /* Rounded-rect app chrome, not a marketing-page stadium CTA.
457
469
  Shape vocabulary: r-1/r-2 = actionable buttons, icon-buttons, and
@@ -484,6 +496,33 @@ body.canvas-host { background: transparent !important; }
484
496
  outline-offset: var(--focus-offset);
485
497
  }
486
498
 
499
+ /* Button size ladder — the size prop on Btn() (sm | md | lg). md is the base
500
+ rule above; sm/lg retune height (off the --ctl-* ladder), horizontal padding,
501
+ and font-size so a size swap is one class, never inline padding overrides
502
+ (the old preview faked sizes with style=). Applies to every variant. */
503
+ .btn-sm, .btn-sm.btn-primary, .btn-sm.btn-ghost {
504
+ height: var(--ctl-sm); min-height: var(--ctl-sm);
505
+ padding: 0 var(--space-2); font-size: var(--fs-tiny); gap: var(--space-1);
506
+ }
507
+ .btn-lg, .btn-lg.btn-primary, .btn-lg.btn-ghost {
508
+ height: var(--ctl-lg); min-height: var(--ctl-lg);
509
+ padding: 0 var(--space-4); font-size: var(--fs-body); gap: var(--space-2);
510
+ }
511
+
512
+ /* App-mode quiet interactions — the marketing hover-lift (translateY + shadow)
513
+ and the .row .meta slide read as decorative float on a dense working surface.
514
+ Under [data-typescale="app"], state changes are background/border-only: crisp,
515
+ no motion, no elevation. Marketing surfaces (no data-typescale) keep the lift. */
516
+ [data-typescale="app"] .btn:hover,
517
+ [data-typescale="app"] .btn-primary:hover,
518
+ [data-typescale="app"] .btn-ghost:hover { transform: none; box-shadow: none; }
519
+ [data-typescale="app"] .btn:active,
520
+ [data-typescale="app"] .btn-primary:active,
521
+ [data-typescale="app"] .btn-ghost:active { transform: none; }
522
+ [data-typescale="app"] .panel { transition: box-shadow var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease); }
523
+ [data-typescale="app"] a.row:hover .meta,
524
+ [data-typescale="app"] .row[role="button"]:hover .meta { transform: none; }
525
+
487
526
  /* ============================================================
488
527
  IconButton — square icon-only button
489
528
  ============================================================ */
@@ -521,6 +560,10 @@ body.canvas-host { background: transparent !important; }
521
560
  border-radius: var(--r-pill);
522
561
  background: var(--bg-3); color: var(--fg-2);
523
562
  }
563
+ /* Badge size ladder — sm for dense inline counts, lg for prominent markers.
564
+ Base (above) stays the 18px default. */
565
+ .ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size: 10px; }
566
+ .ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
524
567
  /* Canonical tone names are tone-success / tone-error / tone-neutral. Every
525
568
  other tone name grouped into the same rule below (tone-green/tone-live,
526
569
  tone-flame, tone-wip, tone-sun/tone-yellow, tone-ok/tone-miss) is a
@@ -544,7 +587,7 @@ body.canvas-host { background: transparent !important; }
544
587
  .ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
545
588
 
546
589
  .chip {
547
- display: inline-flex; align-items: center; gap: 6px;
590
+ display: inline-flex; align-items: center; gap: var(--space-1-75);
548
591
  padding: 3px 10px;
549
592
  background: var(--bg-2); color: var(--fg-2);
550
593
  font-family: var(--ff-body);
@@ -552,6 +595,17 @@ body.canvas-host { background: transparent !important; }
552
595
  letter-spacing: var(--tr-caps); text-transform: uppercase;
553
596
  border-radius: var(--r-pill);
554
597
  }
598
+ /* Chip size ladder — sm for dense toolbars/table cells, lg for prominent
599
+ status. tone-* colouring (below) is orthogonal to size. */
600
+ .chip.chip--sm { padding: var(--space-half) var(--space-2); font-size: var(--fs-micro); gap: var(--space-1); }
601
+ .chip.chip--lg { padding: var(--space-1-75) var(--space-2-75); font-size: var(--fs-xs); }
602
+ /* Tag variant — a rectangular, sentence-case chip for dense data (table cells,
603
+ inline labels) where the all-caps pill reads too shouty and space-hungry.
604
+ Keeps the tone-* palette; drops the caps + pill for a tight --r-0 corner. */
605
+ .chip.chip--tag {
606
+ text-transform: none; letter-spacing: 0; font-weight: 500;
607
+ border-radius: var(--r-0); padding: var(--space-half) var(--space-2);
608
+ }
555
609
  /* Same canonical/legacy-alias grouping as .ds-badge above. */
556
610
  .chip.tone-green, .chip.tone-live, .chip.tone-success, .chip.tone-ok {
557
611
  background: var(--green-tint); color: var(--green-deep);
@@ -621,6 +675,17 @@ body.canvas-host { background: transparent !important; }
621
675
  }
622
676
  .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
623
677
  .panel.panel-wide { background: transparent; box-shadow: none; border: none; }
678
+ /* Dense panel — a compact container for app surfaces (inspector sections,
679
+ sidebars) that want the .panel look without the roomy 16px pad + 24px stack
680
+ gap. Tighter corner + padding + head, so it sits between .panel and the
681
+ flush editor .ds-ep-panel. */
682
+ .panel.panel--dense {
683
+ padding: var(--space-2); margin-bottom: var(--space-2-75);
684
+ border-radius: var(--r-1);
685
+ }
686
+ .panel.panel--dense .panel-head { padding: var(--space-1-5) var(--space-2); }
687
+ .panel.panel--dense .panel-body { padding: var(--space-1-5) var(--space-2) var(--space-2); }
688
+ .panel.panel--dense .panel-body > * + * { margin-top: var(--space-2); }
624
689
  .panel-head {
625
690
  display: flex; align-items: baseline; justify-content: space-between;
626
691
  /* Shares the .panel-body 16px side padding so the caps label and the body
@@ -650,9 +715,13 @@ body.canvas-host { background: transparent !important; }
650
715
  grid-template-columns: minmax(80px, 12ch) minmax(0, 1fr) auto;
651
716
  gap: var(--space-3);
652
717
  align-items: baseline;
653
- padding: var(--space-3) var(--space-4);
718
+ /* App density is the BASE: a list row is quiet working chrome, so it defaults
719
+ to the compact 12px/16px rhythm (density-scaled) rather than the old
720
+ 16px/24px marketing padding. .row--lede restores the roomier marketing
721
+ voice for editorial index pages. */
722
+ padding: calc(var(--space-2-75) * var(--density)) var(--space-3);
654
723
  background: var(--bg);
655
- border-radius: var(--r-2);
724
+ border-radius: var(--r-1);
656
725
  color: var(--fg);
657
726
  position: relative;
658
727
  /* Only background + the leading rail animate; padding/size never change on
@@ -730,8 +799,14 @@ a.row:hover .meta, .row[role="button"]:hover .meta { color: var(--accent-ink); t
730
799
  .row.row-nocode { grid-template-columns: minmax(0, 1fr) auto; }
731
800
 
732
801
  .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; }
802
+ /* Title defaults to the quiet app voice (fs-sm/500) the 18px/600 marketing
803
+ voice is now opt-in via .row--lede. */
804
+ .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
805
  .row .title .sub { font-family: var(--ff-body); font-weight: 400; font-size: var(--fs-sm); color: var(--fg-3); }
806
+ /* Marketing/editorial index row — restores the roomier padding + larger title
807
+ the default carried before app-scale became the base. */
808
+ .row--lede { padding: var(--space-3) var(--space-4); }
809
+ .row--lede .title { font-size: var(--fs-lg); font-weight: 600; }
735
810
  /* App typescale: list rows are quiet working chrome (the 18px/600 default is a
736
811
  marketing-surface voice). One size for ALL app list rows - matches
737
812
  .ds-file-row/.ds-session-title at fs-sm/500; values mirror the kit's own
@@ -970,16 +1045,19 @@ table.kv td:last-child {
970
1045
  table {
971
1046
  width: 100%; border-collapse: collapse;
972
1047
  font-family: var(--ff-body); font-size: var(--fs-sm);
973
- border-radius: var(--r-3); overflow: hidden;
1048
+ /* A data grid, not a card: a tight --r-0 corner (was --r-3/22px, which read
1049
+ as a rounded card). Cells drop to 8px 12px for a dense, scannable row
1050
+ (was 12/14 x 16 => ~43px rows). Density scales the vertical padding. */
1051
+ border-radius: var(--r-0); overflow: hidden;
974
1052
  }
975
1053
  table th {
976
1054
  text-align: left;
977
- padding: 12px 16px;
1055
+ padding: calc(var(--space-2) * var(--density)) var(--space-2-75);
978
1056
  font-size: var(--fs-tiny); font-weight: 600;
979
1057
  color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps);
980
1058
  background: var(--bg-2);
981
1059
  }
982
- table td { padding: 14px 16px; border-top: 1px solid var(--rule); }
1060
+ table td { padding: calc(var(--space-2) * var(--density)) var(--space-2-75); border-top: 1px solid var(--rule); }
983
1061
  table tr.clickable { cursor: pointer; }
984
1062
  table tr.clickable:hover td { background: var(--bg-2); }
985
1063
  table tr.clickable:focus-visible {
@@ -1047,13 +1125,16 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1047
1125
  gap: var(--space-2);
1048
1126
  }
1049
1127
  .kpi-card {
1050
- padding: var(--space-5);
1128
+ /* Was --space-5 (32px) pad + --r-3 (22px) — four cards ate a full fold. A
1129
+ 16px pad on a --r-1 corner keeps them prominent but dense; the number drops
1130
+ from a 48px marketing display to a 32px app-scale figure. */
1131
+ padding: var(--space-3);
1051
1132
  background: var(--bg-2);
1052
- border-radius: var(--r-3);
1133
+ border-radius: var(--r-1);
1053
1134
  }
1054
1135
  .kpi-card .num {
1055
1136
  font-family: var(--ff-body); font-weight: 600;
1056
- font-size: clamp(30px, 3.6cqi, 48px); line-height: 1;
1137
+ font-size: clamp(22px, 3cqi, 32px); line-height: 1;
1057
1138
  letter-spacing: var(--tr-tight);
1058
1139
  }
1059
1140
  .kpi-card .lbl {
@@ -1102,11 +1183,14 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1102
1183
  Empty state
1103
1184
  ============================================================ */
1104
1185
  .empty {
1105
- padding: var(--space-8);
1186
+ /* Was --space-8 (96px) — an empty list spent 192px of vertical space on
1187
+ nothing. --space-6 (48px) still centres a message with air, without the
1188
+ empty state dominating the fold. Mobile drops it further (below). */
1189
+ padding: var(--space-6);
1106
1190
  text-align: center;
1107
1191
  color: var(--fg-3);
1108
1192
  background: var(--bg-2);
1109
- border-radius: var(--r-3);
1193
+ border-radius: var(--r-1);
1110
1194
  font-size: var(--fs-sm);
1111
1195
  }
1112
1196
 
@@ -2119,6 +2203,54 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2119
2203
  .ds-preview-meta { display: none; }
2120
2204
  }
2121
2205
 
2206
+ /* ============================================================
2207
+ Catalog / demo shell — a shared, compact framing for the preview/*.html
2208
+ component galleries, so each preview stops hand-rolling its own <style>
2209
+ (body padding, section margins, demo boxes). Mirrors webgeist's dense
2210
+ catalog: a 240px sticky sidebar + capped main + a hairline-bordered,
2211
+ flex-wrapping .ds-demo cell that packs variants inline and reflows.
2212
+ ============================================================ */
2213
+ .ds-catalog { display: flex; min-height: 100vh; gap: 0; }
2214
+ .ds-catalog-side {
2215
+ width: 240px; flex-shrink: 0;
2216
+ border-right: var(--bw-hair) solid var(--rule);
2217
+ padding: var(--space-3);
2218
+ position: sticky; top: 0; height: 100vh; overflow-y: auto;
2219
+ }
2220
+ .ds-catalog-side h1 { font-size: var(--fs-lg); margin: 0 0 var(--space-1); }
2221
+ .ds-catalog-side .subtitle { font-size: var(--fs-micro); color: var(--fg-3); margin: 0 0 var(--space-4); }
2222
+ .ds-catalog-side nav h4 {
2223
+ font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--tr-label);
2224
+ color: var(--fg-3); margin: var(--space-3) 0 var(--space-1);
2225
+ }
2226
+ .ds-catalog-side nav a {
2227
+ display: block; padding: var(--space-half) 0; font-size: var(--fs-tiny);
2228
+ color: var(--fg-2); text-decoration: none;
2229
+ }
2230
+ .ds-catalog-side nav a:hover { color: var(--accent-ink); }
2231
+ .ds-catalog-main { flex: 1; padding: var(--space-5); max-width: var(--measure-wide); min-width: 0; }
2232
+ .ds-catalog-section { margin-bottom: var(--space-5); padding-top: var(--space-3); border-top: var(--bw-hair) solid var(--rule); }
2233
+ .ds-catalog-section > h2 { font-size: var(--fs-h3-app); margin: 0 0 var(--space-3); }
2234
+
2235
+ /* The gallery cell: a hairline-bordered, flex-wrapping bin that packs a
2236
+ component's variants inline and reflows — the signature "showcase" frame. */
2237
+ .ds-demo {
2238
+ border: var(--bw-hair) solid var(--rule);
2239
+ border-radius: var(--r-1);
2240
+ padding: var(--space-3);
2241
+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
2242
+ background: var(--bg);
2243
+ }
2244
+ .ds-demo + .ds-demo { margin-top: var(--space-2-75); }
2245
+ .ds-demo-label { font-size: var(--fs-xs); font-weight: 500; margin: 0 0 var(--space-1); }
2246
+ .ds-demo-desc { font-size: var(--fs-tiny); color: var(--fg-3); margin: 0 0 var(--space-2); }
2247
+
2248
+ @media (max-width: 768px) {
2249
+ .ds-catalog { flex-direction: column; }
2250
+ .ds-catalog-side { width: 100%; height: auto; position: static; border-right: 0; border-bottom: var(--bw-hair) solid var(--rule); }
2251
+ .ds-catalog-main { padding: var(--space-3); }
2252
+ }
2253
+
2122
2254
  /* -- File browser UX affordances ---------------------------- */
2123
2255
  /* Toolbar filter input — compact search box. */
2124
2256
  .ds-filter-input {
@@ -2986,19 +3118,61 @@ input[type="checkbox"], input[type="radio"] {
2986
3118
  Enhanced Form Inputs
2987
3119
  -------------------------------------------------------------- */
2988
3120
 
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 {
3121
+ /* Base chrome for any BARE native control (outside .ds-field/.row-form/.input):
3122
+ the same editorial tonal-fill + printed-baseline-rule as .ds-field, so a raw
3123
+ <input> is never a browser-default white box in dark mode. The guards are
3124
+ wrapped in :where() so they add ZERO specificity — the whole selector stays
3125
+ at (0,1,1), exactly like `.ds-field input`, so the later .ds-field / .ds-field--sm
3126
+ / .row-form descendant rules win by source order instead of being out-specified
3127
+ (a bare :not(.input) counts as a class and would beat every .ds-field rule,
3128
+ which clobbered the size variants). */
3129
+ input[type="text"]:where(:not(.input):not(.ds-search-input)),
3130
+ input[type="email"]:where(:not(.input):not(.ds-search-input)),
3131
+ input[type="password"]:where(:not(.input):not(.ds-search-input)),
3132
+ input[type="number"]:where(:not(.input):not(.ds-search-input)),
3133
+ input[type="search"]:where(:not(.input):not(.ds-search-input)),
3134
+ input[type="url"]:where(:not(.input):not(.ds-search-input)),
3135
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3136
+ textarea:where(:not(.input)),
3137
+ select:where(:not(.ds-select)) {
3138
+ font: inherit; color: var(--fg);
3139
+ background: var(--bg-2);
3140
+ border: 0;
3141
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3142
+ inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3143
+ border-radius: var(--r-1);
3144
+ padding: 0 var(--space-3);
2998
3145
  transition: background var(--dur-snap) var(--ease),
2999
3146
  border-color var(--dur-snap) var(--ease),
3000
3147
  box-shadow var(--dur-snap) var(--ease);
3001
3148
  }
3149
+ /* Single-line bare controls snap to the field height; textarea keeps auto
3150
+ height with symmetric padding (its content decides the box). */
3151
+ input[type="text"]:where(:not(.input):not(.ds-search-input)),
3152
+ input[type="email"]:where(:not(.input):not(.ds-search-input)),
3153
+ input[type="password"]:where(:not(.input):not(.ds-search-input)),
3154
+ input[type="number"]:where(:not(.input):not(.ds-search-input)),
3155
+ input[type="search"]:where(:not(.input):not(.ds-search-input)),
3156
+ input[type="url"]:where(:not(.input):not(.ds-search-input)),
3157
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3158
+ select:where(:not(.ds-select)) {
3159
+ height: var(--field-height);
3160
+ }
3161
+ textarea:where(:not(.input)) { padding: var(--space-2) var(--space-3); min-height: calc(4 * 1.5em); }
3162
+ input[type="text"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3163
+ input[type="email"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3164
+ input[type="password"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3165
+ input[type="number"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3166
+ input[type="search"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3167
+ input[type="url"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3168
+ input[type="tel"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3169
+ textarea:where(:not(.input)):focus-visible,
3170
+ select:where(:not(.ds-select)):focus-visible {
3171
+ outline: none;
3172
+ background: var(--bg);
3173
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3174
+ inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
3175
+ }
3002
3176
 
3003
3177
  input[type="text"]::placeholder,
3004
3178
  input[type="email"]::placeholder,
@@ -3131,11 +3305,29 @@ hr.rule-dotted { border-top: 1px dotted var(--rule-strong); }
3131
3305
  the electric lead on focus, instead of a uniform hairline box. */
3132
3306
  box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3133
3307
  inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3134
- border-radius: var(--r-2, 10px);
3135
- padding: 10px 14px;
3308
+ /* Tighter corner than the old --r-2 (14px): a dense pro control, not a soft
3309
+ consumer box. Single-line controls snap to --field-height with h-only
3310
+ padding so they align pixel-exact with buttons on the same --ctl-md rung. */
3311
+ border-radius: var(--r-1);
3312
+ height: var(--field-height);
3313
+ padding: 0 var(--space-3);
3136
3314
  transition: box-shadow var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
3137
3315
  }
3138
- .ds-field textarea { min-height: calc(4 * 1.5em); resize: vertical; }
3316
+ /* textarea is multi-line: height is auto (min-height instead), keep symmetric
3317
+ vertical padding so text doesn't sit flush to the top edge. */
3318
+ .ds-field textarea { height: auto; min-height: calc(4 * 1.5em); padding: var(--space-2) var(--space-3); resize: vertical; }
3319
+
3320
+ /* Field size ladder (size prop on TextField/Select/SearchInput). md == base.
3321
+ sm/lg retune height + h-padding + font off the --ctl-* ladder, matching the
3322
+ button ladder so a form of mixed controls stays on one rhythm. */
3323
+ .ds-field--sm input, .ds-field--sm .ds-select, .ds-field--sm .ds-search-input {
3324
+ height: var(--ctl-sm); padding: 0 var(--space-2-5); font-size: var(--fs-tiny);
3325
+ }
3326
+ .ds-field--sm textarea { height: auto; padding: var(--space-1-75) var(--space-2-5); font-size: var(--fs-tiny); }
3327
+ .ds-field--lg input, .ds-field--lg .ds-select, .ds-field--lg .ds-search-input {
3328
+ height: var(--ctl-lg); padding: 0 var(--space-4); font-size: var(--fs-body);
3329
+ }
3330
+ .ds-field--lg textarea { height: auto; padding: var(--space-2-5) var(--space-4); font-size: var(--fs-body); }
3139
3331
  .ds-search-input::placeholder { color: var(--fg-3); }
3140
3332
  /* Only present when SearchInput is passed resultCount - transparent to any
3141
3333
  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 {