agentgui 1.0.1117 → 1.0.1118

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.
@@ -34,7 +34,7 @@
34
34
  --ink-2: #333338;
35
35
  --ink-3: #525252; /* WCAG-verified 2026 restyle: npm run a11y caught 4.10:1 at #58585F against --paper-3 (#c6c5c5-equivalent surfaces); #525252 clears 4.54:1 there and 7.81:1 against pure paper */
36
36
  --ink-3-dark: #4D4D52; /* dark theme bg-3 (tier-3 surface) */
37
- --paper-3-dark: #9B9B9B; /* dark theme fg-3 (tier-3 text); WCAG-verified: npm run a11y caught 4.41:1 at #999999 against --ink-2 (#333338); #9B9B9B clears 4.52:1 there */
37
+ --paper-3-dark: #BEBEBE; /* dark theme fg-3 (tier-3 text); #9B9B9B cleared 4.52:1 against --ink-2 but --fg-3 also renders on --bg-3 (--ink-3-dark, #4D4D52, e.g. .chip.tone-dim), where it measured only 3.02:1. #BEBEBE clears 4.52:1 on --ink-3-dark, 6.76:1 on --ink-2, 9.36:1 on --ink. */
38
38
 
39
39
  /* Lore palette. --acid was the prior electric-lime lead; RESTYLED to a
40
40
  neutral near-black accent matching webjsx-toolkit's grayscale --primary
@@ -189,6 +189,16 @@
189
189
  --ff-body: system-ui, sans-serif;
190
190
  --ff-mono: ui-monospace, Menlo, Consolas, monospace;
191
191
 
192
+ /* Weight scale — regular is the default voice for body/UI text everywhere;
193
+ medium/semibold/bold are reserved for genuine emphasis (active state,
194
+ unread counts, own-message author, section headers). Never reach for a
195
+ hardcoded 600/700 in a component rule — use these so the whole surface
196
+ can be re-tuned in one place instead of drifting per-component. */
197
+ --fw-regular: 400;
198
+ --fw-medium: 500;
199
+ --fw-semibold: 600;
200
+ --fw-bold: 700;
201
+
192
202
  /* Sub-12px tiers exist ONLY for dense OS/data chrome — taskbar strips,
193
203
  session chips, tool-call headers, file-row metadata, ID/stat columns.
194
204
  Never use them for body copy or ordinary UI text: at these sizes the
@@ -203,12 +213,24 @@
203
213
  --fs-body: 16px;
204
214
  --fs-lg: 18px;
205
215
  --fs-xl: 21px;
206
- --fs-h4: clamp(19px, 1.8cqi, 24px);
207
- --fs-h3: clamp(22px, 2.4cqi, 30px);
216
+ --fs-h4: clamp(24px, 2cqi, 28px);
217
+ --fs-h3: clamp(26px, 2.6cqi, 34px);
208
218
  --fs-h2: clamp(28px, 3.6cqi, 44px);
209
219
  --fs-h1: clamp(34px, 5cqi, 64px);
210
220
  --fs-hero: clamp(42px, 7cqi, 96px);
211
221
  --fs-mega: clamp(56px, 11cqi, 168px);
222
+ /* Bridges the fixed --fs-* ladder (tops out at --fs-xl, 21px) and the fluid
223
+ heading clamps below it — use for fixed (non-clamping) UI text that needs
224
+ to sit between --fs-xl and a heading tier, e.g. empty-state icon glyphs. */
225
+ --fs-2xl: 28px;
226
+
227
+ /* Icon size scale — canonical sizes for glyphs/icons across the kit going
228
+ forward. Existing ad-hoc pixel values (6/7/8/10/12/14/16/18px etc.)
229
+ don't need to be migrated in this pass; new/updated icon usage should. */
230
+ --icon-xs: 12px;
231
+ --icon-sm: 16px;
232
+ --icon-md: 20px;
233
+ --icon-lg: 24px;
212
234
 
213
235
  /* App/chrome typescale: the default heading scale above is tuned for
214
236
  marketing/hero layouts (h1 up to 64px). Application chrome — page titles,
@@ -357,6 +379,9 @@
357
379
  --z-below: -1;
358
380
  --z-base: 0;
359
381
  --z-raised: 100;
382
+ --z-raised-1: calc(var(--z-raised) + 1);
383
+ --z-raised-2: calc(var(--z-raised) + 2);
384
+ --z-raised-3: calc(var(--z-raised) + 3);
360
385
  --z-sticky: 200;
361
386
  --z-header: 300;
362
387
  --z-drawer: 400;
@@ -386,6 +411,17 @@
386
411
  --sidebar-width: 220px;
387
412
  --sidebar-width-collapsed: 64px;
388
413
 
414
+ /* Canonical breakpoint values — @media can't reference custom properties,
415
+ so these are documented here (not tokenized) for consistency across
416
+ responsive.css/responsive2-workspace.css/hero-content.css:
417
+ - 480px (max-width) mobile portrait
418
+ - 768px (max-width) mobile/tablet boundary, compact nav/sidebar collapse
419
+ - 768-1024px (min/max-width) tablet landscape band
420
+ - 900px (max-width) / 901px (min-width) tablet/desktop layout reflow
421
+ - 1025px (min-width) desktop
422
+ Always use these exact values; a near-miss (e.g. 760px, 481px) should be
423
+ aligned to its canonical neighbour when touched. */
424
+
389
425
  --lh-tall: 1.4;
390
426
 
391
427
  /* Control size scale — heights for status/topbar/crumb + controls. */
@@ -417,6 +453,9 @@
417
453
  --focus-w: 2px;
418
454
  --focus-offset: 2px;
419
455
  --focus-ring-inset: inset 0 0 0 var(--focus-w) var(--focus-color);
456
+
457
+ /* letterbox background for video/media containers */
458
+ --media-letterbox: #000;
420
459
  }
421
460
 
422
461
  /* ============================================================
@@ -519,6 +558,27 @@
519
558
  --code-num: var(--sun);
520
559
  }
521
560
 
561
+ /* [data-accent="X"] overrides (below) hardcode LIGHT-tuned fills (--acid,
562
+ --purple, --green) with no dark-theme companion, so data-theme=dark +
563
+ data-accent=acid/purple/green produced a near-invisible fill against the
564
+ dark page bg (measured: acid 1.15:1, purple 1.04:1, green 2.98:1 on --ink
565
+ -- all under the 3:1 non-text UI-fill floor). Same convention as the base
566
+ --accent retune above: restate the fill as its dark-tuned equivalent so
567
+ the swatch stays visible, using tones already verified elsewhere in this
568
+ file (green retune 7.48:1 on --ink, purple-2 retune 6.11:1 on --ink). */
569
+ .ds-247420[data-theme="ink"][data-accent="acid"],
570
+ .ds-247420[data-theme="dark"][data-accent="acid"] {
571
+ --accent: #BFBFBF; --accent-bright: #BFBFBF; --accent-fg: var(--ink); --accent-ink: var(--paper); --panel-accent: #BFBFBF;
572
+ }
573
+ .ds-247420[data-theme="ink"][data-accent="purple"],
574
+ .ds-247420[data-theme="dark"][data-accent="purple"] {
575
+ --accent: #C277FF; --accent-bright: #C277FF; --accent-fg: var(--ink); --accent-ink: #C277FF; --panel-accent: #C277FF;
576
+ }
577
+ .ds-247420[data-theme="ink"][data-accent="green"],
578
+ .ds-247420[data-theme="dark"][data-accent="green"] {
579
+ --accent: #5CBF52; --accent-bright: #5CBF52; --accent-fg: var(--ink); --accent-ink: #5CBF52; --panel-accent: #5CBF52;
580
+ }
581
+
522
582
  /* Restores the pre-existing (weaker) dark-theme selection tint after the
523
583
  light-theme-only bump in app-shell.css's base ::selection rule. */
524
584
  .ds-247420[data-theme="ink"] ::selection,
@@ -718,8 +778,20 @@
718
778
 
719
779
  /* Type-scale modifier — opt-in body/lg/xl bump for surfaces that want denser
720
780
  or larger reading sizes without a custom override. */
721
- .ds-247420[data-typescale="sm"] { --fs-body: 15px; --fs-lg: 17px; --fs-xl: 20px; }
722
- .ds-247420[data-typescale="lg"] { --fs-body: 17px; --fs-lg: 20px; --fs-xl: 24px; }
781
+ .ds-247420[data-typescale="sm"] {
782
+ --fs-body: 15px; --fs-lg: 17px; --fs-xl: 20px;
783
+ --fs-h4: clamp(22px, 1.9cqi, 24px);
784
+ --fs-h3: clamp(23px, 2.4cqi, 32px);
785
+ --fs-h2: clamp(26px, 3.4cqi, 41px);
786
+ --fs-h1: clamp(32px, 4.7cqi, 60px);
787
+ }
788
+ .ds-247420[data-typescale="lg"] {
789
+ --fs-body: 17px; --fs-lg: 20px; --fs-xl: 24px;
790
+ --fs-h4: clamp(27px, 2.2cqi, 31px);
791
+ --fs-h3: clamp(28px, 2.9cqi, 38px);
792
+ --fs-h2: clamp(31px, 4cqi, 49px);
793
+ --fs-h1: clamp(38px, 5.6cqi, 71px);
794
+ }
723
795
 
724
796
  /* Debug grid overlay — 8px mascot grid. Toggle via class on any container. */
725
797
  .ds-247420 .with-grid-overlay {
@@ -805,50 +877,41 @@
805
877
  }
806
878
 
807
879
  /* app-shell.css */
808
- /* ============================================================
809
- 247420 design system — component sheet
810
- shadcn-neutral: system-font display + body + mono, no custom web fonts.
811
- Confident hierarchy, centered editorial stack, neutral grayscale accent.
812
- Indicator rails over hairlines.
813
- ============================================================ */
814
-
815
- /* ============================================================
816
- Base
817
- ============================================================ */
818
880
  .ds-247420 * { box-sizing: border-box; }
819
881
  .ds-247420, .ds-247420 body { margin: 0; padding: 0; }
820
882
  .ds-247420 button, .ds-247420 input, .ds-247420 select, .ds-247420 textarea { font: inherit; }
821
883
 
822
- /* ============================================================
823
- Accessibility — Skip Link
824
- ============================================================ */
884
+ /* Global reduced-motion safety net: catches any component transition/animation
885
+ that lacks its own per-file prefers-reduced-motion override (most components
886
+ already opt out locally; this is the backstop for the rest, e.g.
887
+ plugins-config.css, catalog-theme.css, sidebar-misc.css, row-print.css,
888
+ topbar.css, primitives.css, git-status.css, panel-row.css). Scroll-snap
889
+ carousels handle scroll-behavior themselves and are unaffected. */
890
+ @media (prefers-reduced-motion: reduce) {
891
+ .ds-247420 *, .ds-247420 *::before, .ds-247420 *::after {
892
+ animation-duration: 0.001ms !important;
893
+ animation-iteration-count: 1 !important;
894
+ transition-duration: 0.001ms !important;
895
+ scroll-behavior: auto !important;
896
+ }
897
+ }
898
+
825
899
  .ds-247420 .skip-link {
900
+ --skip-link-preflight-translate-clear: -100%;
826
901
  position: absolute;
827
- /* -40px alone left the 44px-tall pill (padding + min-height floor below)
828
- poking ~4px into every page pre-focus -- measured live via
829
- getBoundingClientRect(). The offset must clear the full rendered height,
830
- not a guessed constant, so translateY pushes it out by its own box. */
831
902
  top: 0;
832
903
  left: 0;
833
- transform: translateY(-100%);
904
+ transform: translateY(var(--skip-link-preflight-translate-clear));
834
905
  background: var(--accent);
835
906
  color: var(--accent-fg);
836
907
  padding: var(--space-2) var(--space-3);
837
908
  text-decoration: none;
838
- /* Must outrank every layer of chrome once focused, or the first Tab press
839
- lands on a control hidden behind the sticky header (was z-index:100,
840
- which the header alone now matches). */
841
909
  z-index: var(--z-top);
842
910
  border-radius: var(--r-pill);
843
911
  font-weight: 600;
844
912
  font-size: var(--fs-sm);
845
- /* The skip link is the first thing a keyboard or switch user reaches, so it
846
- is the last control that should be hard to hit. Its padding alone rendered
847
- it 39px tall -- measured live under coarse-pointer emulation on every kit.
848
- Floored unconditionally rather than under a pointer:coarse query, because
849
- switch and head-pointer users are not detected as coarse pointers and need
850
- the larger target just as much. */
851
- min-height: 44px;
913
+ --skip-link-min-touch-target: 44px;
914
+ min-height: var(--skip-link-min-touch-target);
852
915
  display: inline-flex;
853
916
  align-items: center;
854
917
  }
@@ -858,23 +921,6 @@
858
921
  outline: var(--focus-w) solid var(--focus-color);
859
922
  outline-offset: var(--focus-offset);
860
923
  }
861
- /* Visually-hidden but still in the accessibility tree. Emitted by Row() (the
862
- rail status word), FilterBar()/FileGrid() (aria-live result counts) and
863
- form-primitives' required marker — all of which shipped BEFORE this rule
864
- existed anywhere the app shell loads. The only other definitions live in
865
- app-surfaces.css (pre-scoped to `.ds-247420`, so it never reaches a kit that
866
- does not mount that root) and editor-primitives.css (a separate opt-in
867
- <link>), which left every app-shell consumer rendering screen-reader-only
868
- text VISIBLY the moment it passed `rail`/`resultCount`.
869
- Deliberately NOT `display:none` / `visibility:hidden` / `width:0`: those
870
- remove the node from the accessibility tree too, which defeats the entire
871
- purpose — the text exists only for AT. The clip + 1px box is what keeps it
872
- announced while occupying no visible area.
873
- `margin: -1px` is OFF-SCALE ON PURPOSE — it is not rhythm, it is one half of
874
- the canonical visually-hidden idiom: the negative margin cancels the 1px box
875
- so the clipped element contributes zero layout. Snapping it to a --space-*
876
- rung would reintroduce a measurable gap. `clip` is deprecated but retained
877
- alongside `clip-path` for older AT/browser pairs that ignore the latter. */
878
924
  .ds-247420 .sr-only {
879
925
  position: absolute;
880
926
  width: 1px;
@@ -888,11 +934,6 @@
888
934
  border: 0;
889
935
  }
890
936
 
891
- /* .ds-aspect — generic aspect-ratio utility. Native `aspect-ratio` property
892
- (broadly supported for years) rather than the padding-bottom hack: no
893
- extra wrapper markup, no absolutely-positioned inner layer needed just to
894
- fill the box. `--aspect` defaults to 16/9; a caller overrides it inline
895
- (e.g. style="--aspect:1/1") or via a ratio-named modifier class below. */
896
937
  .ds-247420 .ds-aspect {
897
938
  aspect-ratio: var(--aspect, 16 / 9);
898
939
  width: 100%;
@@ -912,14 +953,11 @@
912
953
  -webkit-font-smoothing: antialiased;
913
954
  text-rendering: optimizeLegibility;
914
955
  }
915
- /* Light (paper, the default theme) needs a stronger accent share than dark to
916
- clear a visible selection tint against --bg-2 -- the dark-theme value (24%)
917
- read too weak on paper. Dark/ink keeps its existing 24% mix, restored below
918
- in the ink/dark + auto-dark blocks (mirrors the --danger/--flame pattern of
919
- scoping a value per theme rather than sharing one across both). */
920
- .ds-247420 ::selection { background: color-mix(in oklab, var(--accent) 46%, var(--bg-2)); color: var(--fg); }
956
+ .ds-247420 {
957
+ --selection-tint-paper-share: 46%;
958
+ }
959
+ .ds-247420 ::selection { background: color-mix(in oklab, var(--accent) var(--selection-tint-paper-share), var(--bg-2)); color: var(--fg); }
921
960
 
922
- /* Every root has a CQ container so fluid type can resolve to a meaningful inline-size. */
923
961
  .ds-247420 .app, .ds-247420 .ds-stage {
924
962
  container-type: inline-size;
925
963
  overflow-x: clip;
@@ -927,29 +965,31 @@
927
965
  }
928
966
  .ds-247420 .app * { min-width: 0; }
929
967
 
930
- /* Scrollbar treatment, applied once at the root rather than per-component.
931
- chat.css already styled its own thread and code blocks this way, but every
932
- other scroll container (.app-side-shell, .app-main, .ds-scroll-x,
933
- .ds-table-wrap, .ds-live-log) fell back to the UA default -- measured live,
934
- 4 of 4 containers on dashboard and 7 of 7 on gm_inspector were unstyled while
935
- chat's two were not. A default scrollbar reads as a bright seam against a
936
- near-black panel in the ink and dark themes, and inconsistently between two
937
- panels on the same page. Tokens mean the thumb follows the active theme.
938
- Inheriting from the container also covers scrollers added later. */
939
968
  .ds-247420 .app, .ds-247420 .ds-stage, .ds-247420 .app *, .ds-247420 .ds-stage * {
940
969
  scrollbar-width: thin;
941
970
  scrollbar-color: var(--bg-3) transparent;
942
971
  }
972
+ /* Firefox gets themed scrollbars via scrollbar-width/-color above; Chrome,
973
+ Safari, and Edge need the ::-webkit-scrollbar family too, or every
974
+ scrollable node in the app shell falls back to the raw OS scrollbar and
975
+ clashes with the dark UI. Same selector scope, same --bg-3 token. */
976
+ .ds-247420 .app::-webkit-scrollbar, .ds-247420 .ds-stage::-webkit-scrollbar, .ds-247420 .app *::-webkit-scrollbar, .ds-247420 .ds-stage *::-webkit-scrollbar {
977
+ width: 8px;
978
+ height: 8px;
979
+ }
980
+ .ds-247420 .app::-webkit-scrollbar-track, .ds-247420 .ds-stage::-webkit-scrollbar-track, .ds-247420 .app *::-webkit-scrollbar-track, .ds-247420 .ds-stage *::-webkit-scrollbar-track {
981
+ background: transparent;
982
+ }
983
+ .ds-247420 .app::-webkit-scrollbar-thumb, .ds-247420 .ds-stage::-webkit-scrollbar-thumb, .ds-247420 .app *::-webkit-scrollbar-thumb, .ds-247420 .ds-stage *::-webkit-scrollbar-thumb {
984
+ background: var(--bg-3);
985
+ border-radius: var(--r-pill, 999px);
986
+ }
943
987
 
944
- /* ============================================================
945
- Typography
946
- ============================================================ */
947
988
  .ds-247420 h1, .ds-247420 .t-h1 { font-family: var(--ff-display); font-size: var(--fs-h1); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
948
989
  .ds-247420 h2, .ds-247420 .t-h2 { font-family: var(--ff-display); font-size: var(--fs-h2); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
949
990
  .ds-247420 h3, .ds-247420 .t-h3 { font-family: var(--ff-display); font-size: var(--fs-h3); line-height: var(--lh-snug); letter-spacing: -0.01em; font-weight: 600; margin: 0; }
950
991
  .ds-247420 h4, .ds-247420 .t-h4 { font-family: var(--ff-display); font-size: var(--fs-h4); line-height: var(--lh-snug); font-weight: 600; margin: 0; }
951
992
 
952
- /* App/chrome typescale opt-in: compact heading ceiling for application UIs. */
953
993
  .ds-247420[data-typescale="app"] h1, .ds-247420[data-typescale="app"] .t-h1 { font-size: var(--fs-h1-app); line-height: 1.15; }
954
994
  .ds-247420[data-typescale="app"] h2, .ds-247420[data-typescale="app"] .t-h2 { font-size: var(--fs-h2-app); }
955
995
  .ds-247420[data-typescale="app"] h3, .ds-247420[data-typescale="app"] .t-h3 { font-size: var(--fs-h3-app); }
@@ -959,16 +999,9 @@
959
999
  .ds-247420 .t-mega { font-family: var(--ff-display); font-size: var(--fs-mega); line-height: 0.92; letter-spacing: var(--tr-tighter); font-weight: 700; margin: 0; }
960
1000
 
961
1001
  .ds-247420 .t-display { font-family: var(--ff-display); font-weight: 600; letter-spacing: var(--tr-tight); }
962
- .ds-247420 .t-mono { font-family: var(--ff-mono); }
1002
+ .ds-247420 .t-mono { font-family: var(--ff-mono); font-weight: 400; }
963
1003
  .ds-247420 .t-body { font-family: var(--ff-body); }
964
1004
 
965
- /* House small-label voice, in two sizes. Every declaration here is sourced
966
- from a token, so a sheet that cannot join these selector lists (community.css
967
- and gm-prose.css ship as separate <link>-able exports and must not depend on
968
- app-shell.css being present) still composes the same voice by referencing the
969
- same four tokens — --ff-mono / --fs-* / uppercase / --tr-label. That token
970
- set, not this rule, is the single source of truth; a selector that restates a
971
- literal instead of the token is the drift this pairing exists to prevent. */
972
1005
  .ds-247420 .t-label { font-family: var(--ff-mono); font-size: var(--fs-tiny); text-transform: uppercase; letter-spacing: var(--tr-label); font-weight: 500; }
973
1006
  .ds-247420 .t-micro { font-family: var(--ff-mono); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--tr-label); color: var(--fg-3); }
974
1007
  .ds-247420 .t-meta { font-family: var(--ff-body); font-size: var(--fs-sm); color: var(--fg-3); }
@@ -979,17 +1012,18 @@
979
1012
  margin: 0;
980
1013
  }
981
1014
 
982
- /* h1/h2/h3/h4 and .lede/.t-lede are both margin:0 primitives by design (a
983
- heading used standalone must not force space its context doesn't want).
984
- But a heading immediately followed by a lede is a named pairing, not two
985
- independent primitives that happen to be adjacent -- it needs its own gap
986
- regardless of whether the surrounding shell opts into .ds-app-surface
987
- (which only adds this spacing on landing/marketing surfaces, leaving every
988
- Operate-mode kit that skips that class with a zero-gap heading+subtitle). */
989
1015
  .ds-247420 h1 + .lede, .ds-247420 h1 + .t-lede, .ds-247420 h2 + .lede, .ds-247420 h2 + .t-lede, .ds-247420 h3 + .lede, .ds-247420 h3 + .t-lede, .ds-247420 .t-h1 + .lede, .ds-247420 .t-h1 + .t-lede {
990
1016
  margin-top: var(--space-2);
991
1017
  }
992
1018
 
1019
+ .ds-247420 .lede + .post-body, .ds-247420 .t-lede + .post-body, .ds-247420 .lede + .ds-prose, .ds-247420 .t-lede + .ds-prose {
1020
+ margin-top: var(--space-4);
1021
+ }
1022
+
1023
+ .ds-247420 .lede + .ds-section, .ds-247420 .t-lede + .ds-section {
1024
+ margin-top: var(--space-8);
1025
+ }
1026
+
993
1027
  .ds-247420 .eyebrow {
994
1028
  font-family: var(--ff-mono);
995
1029
  font-size: var(--fs-tiny);
@@ -999,7 +1033,6 @@
999
1033
  color: var(--accent-ink);
1000
1034
  display: inline-flex; align-items: center; gap: 12px;
1001
1035
  }
1002
- /* Editorial kicker mark — a short solid ink rule, the print-byline tell. */
1003
1036
  .ds-247420 .eyebrow::before {
1004
1037
  content: '';
1005
1038
  width: 28px; height: 3px; background: currentColor;
@@ -1028,21 +1061,6 @@
1028
1061
  font-family: var(--ff-body);
1029
1062
  }
1030
1063
  .ds-247420 .ds-prose blockquote p:last-child { margin-bottom: 0; }
1031
- /* Closing line of an article ("end. reply by opening a pr."), set off from the
1032
- body it terminates. Quieter and smaller than the prose around it: it takes
1033
- the mono face and the --fg-3 tone the house uses for ancillary voices
1034
- (.t-micro, .dim), so the endnote reads as chrome around the article rather
1035
- than a final paragraph of it. The --space-5 lead does the "set off" — it
1036
- breaks the paragraph rhythm so the eye registers the piece has ended before
1037
- reading the words; margin-bottom:0 overrides .ds-prose p's --space-4 so the
1038
- article does not end on dead space.
1039
- Lives here, beside the rest of the .ds-prose family, NOT in gm-prose.css:
1040
- the blog kit that uses this class links only colors_and_type.css +
1041
- app-shell.css, so a rule in that separately-<link>-ed export would be exactly
1042
- as undefined as the class was before. Sized at --fs-xs, not the --fs-micro
1043
- label rung: this is a readable sentence with a link in it, not a micro-label.
1044
- The link inherits .ds-prose a's --accent-ink treatment (a readable TEXT tone;
1045
- the bare --accent fill is ~1.07:1 on paper) — deliberately not re-stated. */
1046
1064
  .ds-247420 .ds-prose .ds-prose-endnote,
1047
1065
  .ds-247420 .ds-prose-endnote {
1048
1066
  margin-top: var(--space-5);
@@ -1055,7 +1073,6 @@
1055
1073
 
1056
1074
  .ds-247420 .dim { color: var(--fg-3); }
1057
1075
 
1058
- /* Links */
1059
1076
  .ds-247420 a { color: inherit; text-decoration: none; }
1060
1077
  .ds-247420 a.ds-link-accent, .ds-247420 .ds-prose a {
1061
1078
  color: var(--accent-ink);
@@ -1063,6 +1080,13 @@
1063
1080
  text-underline-offset: 4px;
1064
1081
  text-decoration-thickness: 2px;
1065
1082
  }
1083
+ /* Visited state was entirely absent -- a returning user had no way to tell an
1084
+ already-read link from an unread one, the one link state browsers don't
1085
+ fake for you. --purple-2 keeps it distinct from both the unvisited accent
1086
+ and any hover/danger tone already in the palette. */
1087
+ .ds-247420 a.ds-link-accent:visited, .ds-247420 .ds-prose a:visited {
1088
+ color: var(--purple-2);
1089
+ }
1066
1090
  .ds-247420 a.ds-link-accent:hover, .ds-247420 .ds-prose a:hover {
1067
1091
  background: var(--accent); color: var(--accent-fg); text-decoration-color: transparent;
1068
1092
  }
@@ -1073,8 +1097,9 @@
1073
1097
  }
1074
1098
 
1075
1099
  .ds-247420 code {
1100
+ --code-pill-tier3-surface: var(--bg-3);
1076
1101
  font-family: var(--ff-mono); font-size: 0.92em;
1077
- background: var(--bg-2); padding: 0.15em 0.5em; border-radius: var(--r-1);
1102
+ background: var(--code-pill-tier3-surface); padding: 0.15em 0.5em; border-radius: var(--r-1);
1078
1103
  }
1079
1104
 
1080
1105
  .ds-247420 pre {
@@ -1145,7 +1170,7 @@
1145
1170
  display: flex; flex-wrap: wrap;
1146
1171
  align-items: center; gap: var(--space-4);
1147
1172
  min-height: var(--app-topbar-h);
1148
- padding: 10px var(--pad-x);
1173
+ padding: var(--space-2-5) var(--pad-x);
1149
1174
  background: color-mix(in oklab, var(--bg) 97%, transparent);
1150
1175
  }
1151
1176
 
@@ -1158,7 +1183,7 @@
1158
1183
  position: sticky; top: 0; z-index: var(--z-header);
1159
1184
  display: flex; flex-wrap: wrap; align-items: center;
1160
1185
  gap: var(--space-2) var(--space-4);
1161
- padding: 8px var(--pad-x);
1186
+ padding: var(--space-2) var(--pad-x);
1162
1187
  background: color-mix(in oklab, var(--bg) 97%, transparent);
1163
1188
  }
1164
1189
  .ds-247420 .app-chrome > .app-topbar,
@@ -1175,7 +1200,24 @@
1175
1200
  .ds-247420 .app-topbar > .brand { flex: 0 0 auto; }
1176
1201
  .ds-247420 .app-topbar > .app-search { flex: 1 1 auto; }
1177
1202
  .ds-247420 .app-topbar > nav { margin-left: auto; }
1178
- .ds-247420 .app-topbar nav { display: flex; gap: 4px; font-size: var(--fs-sm); flex-wrap: wrap; }
1203
+ /* Utility cluster grouping: the theme toggle sits after the repo-info nav
1204
+ links with no visual separation between the two groups, so the row read
1205
+ as one undifferentiated strip of controls. A thin inset rule plus extra
1206
+ left gap reads as "these are a distinct utility group", matching the
1207
+ subtle divider already used between .app-status segments. */
1208
+ .ds-247420 .app-topbar > .app-topbar-theme {
1209
+ flex: 0 0 auto;
1210
+ margin-left: var(--space-2);
1211
+ padding-left: var(--space-3);
1212
+ position: relative;
1213
+ }
1214
+ .ds-247420 .app-topbar > .app-topbar-theme::before {
1215
+ content: ''; position: absolute; left: 0; top: 50%;
1216
+ transform: translateY(-50%);
1217
+ width: 1px; height: 18px;
1218
+ background: var(--rule-strong);
1219
+ }
1220
+ .ds-247420 .app-topbar nav { display: flex; gap: var(--space-1); font-size: var(--fs-sm); flex-wrap: wrap; }
1179
1221
  .ds-247420 .app-topbar nav a { flex: 0 0 auto; }
1180
1222
  .ds-247420 .app-topbar nav a {
1181
1223
  color: var(--fg-2);
@@ -1228,6 +1270,12 @@
1228
1270
  border-radius: var(--r-pill);
1229
1271
  font-size: var(--fs-sm); color: var(--fg-3);
1230
1272
  max-width: 360px;
1273
+ /* Match the system-wide input border language (.ds-field / .ds-search-input:
1274
+ tonal fill + printed baseline rule) instead of a borderless pill, so the
1275
+ topbar search reads as the same control family as every other input. */
1276
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
1277
+ inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
1278
+ transition: box-shadow var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
1231
1279
  }
1232
1280
  .ds-247420 .app-search .icon { opacity: 0.6; }
1233
1281
  .ds-247420 .app-search input {
@@ -1235,22 +1283,70 @@
1235
1283
  font-family: inherit; font-size: inherit; color: var(--fg);
1236
1284
  width: 100%;
1237
1285
  }
1286
+ /* Same focus language as .ds-field/.ds-search-input: no outline ring, the
1287
+ baseline rule thickens to the accent instead — matching every other form
1288
+ input in the system rather than a one-off outline ring. */
1238
1289
  .ds-247420 .app-search:focus-within {
1239
- outline: var(--focus-w) solid var(--focus-color);
1240
- outline-offset: var(--focus-offset);
1290
+ background: var(--bg);
1291
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
1292
+ inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
1241
1293
  }
1242
1294
 
1243
1295
  .ds-247420 .app-crumb {
1244
1296
  min-height: var(--app-crumb-h);
1245
- padding: 6px var(--pad-x);
1246
- display: flex; align-items: center; gap: 8px;
1297
+ padding: var(--space-1-75) var(--pad-x);
1298
+ display: flex; align-items: center; gap: var(--space-2);
1247
1299
  font-size: var(--fs-sm); color: var(--fg-3);
1300
+ min-width: 0;
1248
1301
  }
1249
- .ds-247420 .app-crumb .sep { opacity: 0.5; }
1250
- .ds-247420 .app-crumb .leaf { color: var(--fg); font-weight: 600; }
1251
- .ds-247420 .app-crumb .crumb-right { margin-left: auto; color: var(--fg-3); }
1252
-
1253
- .ds-247420 .app-side-shell { background: var(--bg); }
1302
+ .ds-247420 .app-crumb .sep { opacity: 0.5; flex: 0 0 auto; }
1303
+ /* Trail segments and the current/leaf segment truncate independently instead
1304
+ of the whole crumb bar overflowing — a long segment (deep file path, long
1305
+ title) must not push the sibling segments or the right slot off-screen. */
1306
+ .ds-247420 .app-crumb > span:not(.sep):not(.crumb-right) {
1307
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1308
+ min-width: 0; flex: 0 1 auto;
1309
+ }
1310
+ /* Ancestor trail segments previously had no rule of their own and simply
1311
+ inherited .app-crumb's base --fg-3 — the same tone the separators sit on
1312
+ top of (before their own 0.5 opacity dims them further). That leaves only
1313
+ two visually distinct tiers in practice (dim separator, bold leaf) rather
1314
+ than three, since the ancestor tone reads as "default text colour", not a
1315
+ deliberate middle step. --fg-3 is the crumb bar's OWN base, so explicitly
1316
+ restating it here is the anchor for the 3-tier read: separator (--fg-3 at
1317
+ 0.5 opacity, dimmest) < ancestor (--fg-3, mid) < leaf (--fg bold, brightest). */
1318
+ .ds-247420 .app-crumb > span:not(.sep):not(.leaf):not(.crumb-right) { color: var(--fg-3); }
1319
+ .ds-247420 .app-crumb .leaf { color: var(--fg); font-weight: 600; flex: 0 1 auto; }
1320
+ .ds-247420 .app-crumb .crumb-right { margin-left: auto; color: var(--fg-3); flex: 0 0 auto; }
1321
+
1322
+ /* The shell is the grid cell (.app-body's fixed sidebar column); it must own
1323
+ its own scroll, not just size to content, because .app-body's row track is
1324
+ `minmax(0, 1fr)` — a real height the sidebar can overflow once bins +
1325
+ labels + more exceed it. Without this the extra rows were silently chopped
1326
+ at the viewport edge with no way to reach them. */
1327
+ /* .app-side-shell and .app-main (below) are two INDEPENDENT scroll regions
1328
+ side by side -- correct by design (a sidebar nav and a main panel each own
1329
+ their own overflow), but with default invisible/overlay OS scrollbars a
1330
+ user hovering the boundary has no visual cue which region the wheel will
1331
+ scroll, or that there even are two scrollable zones rather than one page
1332
+ scroll. Thin always-visible scrollbar tracks make each region's own scroll
1333
+ ownership legible without changing the (correct) two-region architecture. */
1334
+ .ds-247420 .app-side-shell {
1335
+ background: var(--bg); overflow-y: auto; min-height: 0;
1336
+ scrollbar-width: thin; scrollbar-color: var(--rule-strong) transparent;
1337
+ /* Bottom-edge fade: a rail longer than the viewport otherwise shows only
1338
+ the thin scrollbar track as its sole "there is more below" cue. Mirrors
1339
+ the horizontal fade already used by .os-instances (src/kits/os/theme.css)
1340
+ for its own overflow edge, oriented top-to-bottom for this vertical
1341
+ scroller. The top edge stays fully opaque (rows start flush under the
1342
+ topbar with no need to hint upward scroll on initial load); only the
1343
+ trailing 24px softens toward transparent. */
1344
+ -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 24px), transparent);
1345
+ mask-image: linear-gradient(to bottom, black calc(100% - 24px), transparent);
1346
+ }
1347
+ .ds-247420 .app-side-shell::-webkit-scrollbar { width: 8px; }
1348
+ .ds-247420 .app-side-shell::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: var(--r-pill, 999px); }
1349
+ .ds-247420 .app-side-shell::-webkit-scrollbar-track { background: transparent; }
1254
1350
  .ds-247420 .app-body { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); align-items: stretch; align-content: stretch; flex: 1; min-height: 0; }
1255
1351
  .ds-247420 .app-body.no-side { grid-template-columns: minmax(0, 1fr); }
1256
1352
  /* When there is no sidebar, the empty side-shell must not occupy a grid row,
@@ -1273,7 +1369,7 @@
1273
1369
  .ds-247420 .app-side .group {
1274
1370
  font-size: var(--fs-xs); font-weight: 600;
1275
1371
  color: var(--fg-3); padding: 0 var(--space-3);
1276
- margin-bottom: 6px;
1372
+ margin-bottom: var(--space-1-75);
1277
1373
  }
1278
1374
  .ds-247420 .app-side a {
1279
1375
  display: grid; grid-template-columns: 18px 1fr auto;
@@ -1288,7 +1384,7 @@
1288
1384
  min-height: 44px;
1289
1385
  border-radius: var(--r-1);
1290
1386
  color: var(--fg-2);
1291
- margin-bottom: 2px;
1387
+ margin-bottom: var(--space-hair);
1292
1388
  }
1293
1389
  @media (pointer: coarse) {
1294
1390
  .ds-247420 .app-side a { min-height: 44px; padding: var(--space-2-75) var(--space-3); }
@@ -1358,6 +1454,15 @@
1358
1454
  /* Reserve the scrollbar track so routes with/without overflow don't shift
1359
1455
  the layout horizontally when the bar appears. */
1360
1456
  .ds-247420 .app-main { scrollbar-gutter: stable; }
1457
+ /* Same visible-scroll-ownership rationale as .app-side-shell above -- makes
1458
+ this the visually distinct SECOND independent scroll region rather than
1459
+ an invisible-scrollbar mystery next to the sidebar. */
1460
+ .ds-247420 .app-main {
1461
+ scrollbar-width: thin; scrollbar-color: var(--rule-strong) transparent;
1462
+ }
1463
+ .ds-247420 .app-main::-webkit-scrollbar { width: 8px; }
1464
+ .ds-247420 .app-main::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: var(--r-pill, 999px); }
1465
+ .ds-247420 .app-main::-webkit-scrollbar-track { background: transparent; }
1361
1466
  /* Jump-target anchors clear the inner-scroll top edge (no chrome overlap,
1362
1467
  but a little breathing room when deep-linked within .app-main). */
1363
1468
  .ds-247420 .app-main [id] { scroll-margin-top: var(--space-4); }
@@ -1367,13 +1472,41 @@
1367
1472
  .ds-247420 .app-main > .ds-file-stage,
1368
1473
  .ds-247420 .app-main > .ds-files-stack,
1369
1474
  .ds-247420 .app-main > .grow { flex: 1 1 auto; min-height: 0; }
1370
- .ds-247420 .app-main.narrow { max-width: var(--measure-narrow); margin: 0 auto; }
1475
+ /* width:100% is load-bearing, not redundant with max-width: .app-body is a
1476
+ CSS grid, and margin:auto on a grid item overrides the default stretch,
1477
+ shrinking the item to its max-content width instead of filling the track
1478
+ and then clamping -- confirmed live at 2560px viewport where mainWidth
1479
+ measured 688px against a correctly-resolved 1400px max-width. Explicit
1480
+ width:100% restores fill-then-cap for every .narrow page, not just wide
1481
+ viewports (the shrink-to-fit bug undersized even the base 760px case). */
1482
+ .ds-247420 .app-main.narrow { width: 100%; max-width: var(--measure-narrow); margin: 0 auto; }
1483
+ /* .narrow means "prose reading measure", not "shrink the whole shell on wide
1484
+ screens". The old clamp(760px, 46vw, 1000px) grew too slowly to track the
1485
+ real available column: at 1920px viewport the .app-body grid's second
1486
+ track (.app width minus --sidebar-width) is ~1556px, but 46vw=883px sat
1487
+ well under the 1000px ceiling, leaving a ~340px dead gutter between the
1488
+ sidebar and the centered narrow column on an ordinary desktop screen (not
1489
+ just ultrawide) -- confirmed live via a real 1920x1080 CDP viewport, a
1490
+ project page (#/p/<slug>). 62vw tracks the actual column growth far closer
1491
+ (1920*0.62=1190px, much nearer the 1556px track than 883px was) while the
1492
+ 1200px ceiling still keeps prose from stretching into an unreadable
1493
+ full-bleed line -- sidebar-present narrow pages read the same width class
1494
+ as the no-side variant below instead of visibly starving next to it. */
1495
+ .ds-247420 .app-body:not(.no-side) .app-main.narrow {
1496
+ max-width: clamp(var(--measure-narrow), 62vw, 1200px);
1497
+ }
1371
1498
  /* A shell with no sidebar has no competing column, so the prose clamp there was
1372
1499
  leaving the whole sidebar's worth of width empty -- 760px of content inside a
1373
1500
  1413px shell on a real page. Widen the clamp by the space the absent sidebar
1374
- would have taken so the measure still reads as prose, just not starved. */
1501
+ would have taken so the measure still reads as prose, just not starved.
1502
+ The floor stays a fixed 980px (no-side is still readable narrow); the
1503
+ ceiling grows with viewport instead of staying pinned at 980px forever --
1504
+ on a 2560px ultrawide the old fixed value left over half the shell as dead
1505
+ side margin. 1400px ceiling keeps prose measure sane (nobody wants a
1506
+ 2400px-wide paragraph) while letting non-prose content (index rows, meta)
1507
+ actually use the extra room. */
1375
1508
  .ds-247420 .app-body.no-side .app-main.narrow {
1376
- max-width: calc(var(--measure-narrow) + var(--sidebar-width));
1509
+ max-width: clamp(calc(var(--measure-narrow) + var(--sidebar-width)), 60vw, 1400px);
1377
1510
  }
1378
1511
  /* Article-body heading rhythm and lede spacing, consumed by flatspace-rendered
1379
1512
  long-form pages (docs, papers) that drop headings/panels directly into .app-main. */
@@ -1426,8 +1559,16 @@
1426
1559
  color: var(--fg-3);
1427
1560
  border-top: 1px solid var(--rule);
1428
1561
  }
1429
- .ds-247420 .app-status .item { color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
1562
+ .ds-247420 .app-status .item { color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; display: flex; align-items: center; gap: var(--space-3); }
1430
1563
  .ds-247420 .app-status .item:first-of-type { color: var(--accent-ink); }
1564
+ /* Subtle divider between adjacent status segments — a thin inset rule, not a
1565
+ full-height border, so it reads as a quiet separator rather than boxing
1566
+ each item. */
1567
+ .ds-247420 .app-status .item:not(:first-of-type)::before {
1568
+ content: ''; flex: 0 0 auto;
1569
+ width: 1px; height: 10px;
1570
+ background: var(--rule-strong);
1571
+ }
1431
1572
  .ds-247420 .app-status .spread { flex: 1; }
1432
1573
  /* The status bar NEVER wraps: at narrow widths the trailing items (hints,
1433
1574
  agent target) drop in priority order instead of breaking mid-word onto a
@@ -1448,17 +1589,21 @@
1448
1589
  color: var(--fg-3);
1449
1590
  }
1450
1591
  .ds-247420 .dateline span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
1451
- .ds-247420 .dateline .spread { flex: 1 1 auto; min-width: var(--space-3); }
1592
+ /* The spreader itself only ever set flex-basis -- an empty div with no
1593
+ background renders as blank space, not the "dotted spreader" the preview
1594
+ page's own caption describes. border-bottom (not background-image) keeps
1595
+ the dots crisp at 1px without a repeating-gradient banding artifact. */
1596
+ .ds-247420 .dateline .spread {
1597
+ flex: 1 1 auto; min-width: var(--space-3); align-self: stretch;
1598
+ display: flex; align-items: center;
1599
+ }
1600
+ .ds-247420 .dateline .spread::after {
1601
+ content: ''; flex: 1 1 auto;
1602
+ border-bottom: var(--bw-hair, 1px) dotted var(--rule-strong);
1603
+ }
1452
1604
 
1453
- /* ============================================================
1454
- Primitives
1455
- ============================================================ */
1456
1605
  .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost {
1457
1606
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
1458
- /* Fixed height + horizontal-only padding: the control height is exact and
1459
- alignable (flex centres the label vertically), the way webgeist snaps every
1460
- control to a height ladder. Default is the --ctl-md rung; --btn-sm/--btn-lg
1461
- retune height + h-padding + font per size. Density scales the ladder. */
1462
1607
  height: var(--ctl-md); min-height: var(--ctl-md);
1463
1608
  padding: 0 var(--space-2-75);
1464
1609
  font-family: var(--ff-body);
@@ -1466,41 +1611,28 @@
1466
1611
  text-decoration: none;
1467
1612
  white-space: nowrap;
1468
1613
  cursor: pointer;
1469
- /* Rounded-rect app chrome, not a marketing-page stadium CTA.
1470
- Shape vocabulary: r-1/r-2 = actionable buttons, icon-buttons, and
1471
- containers; r-pill = reserved for status/input/chip/badge/progress
1472
- surfaces (composer input, chips, badges, progress bars, theme-toggle
1473
- segments) - never for actions. */
1474
1614
  border-radius: var(--r-1);
1475
1615
  border: var(--bw-hair) solid transparent;
1476
1616
  box-shadow: none;
1477
- /* Restrained hover-lift: a 1px rise + hairline shadow reads as a crisp,
1478
- confident affordance rather than a soft glow-card float. */
1479
1617
  transition: transform var(--dur-base) var(--ease-spring), background var(--dur-snap) var(--ease),
1480
1618
  color var(--dur-snap) var(--ease), box-shadow var(--dur-base) var(--ease-spring),
1481
1619
  border-color var(--dur-snap) var(--ease);
1482
1620
  }
1483
1621
  .ds-247420 .btn { background: var(--bg-2); color: var(--fg); border-color: var(--rule); }
1484
1622
  .ds-247420 .btn:hover { background: var(--bg-3); color: var(--fg); transform: translateY(-1px); box-shadow: var(--shadow-1); }
1485
- /* Primary CTA — the electric lead as a solid editorial block; hover inverts to
1486
- an ink block with the lead as text, a confident two-tone flip. */
1487
1623
  .ds-247420 .btn-primary {
1488
- background: var(--accent); color: var(--accent-fg);
1624
+ background: var(--green); color: var(--on-color);
1489
1625
  font-weight: 700;
1490
1626
  }
1491
- .ds-247420 .btn-primary:hover { background: var(--fg); color: var(--accent-ink); transform: translateY(-1px); box-shadow: var(--shadow-2); }
1627
+ .ds-247420 .btn-primary:hover { background: var(--fg); color: var(--green); transform: translateY(-1px); box-shadow: var(--shadow-2); }
1492
1628
  .ds-247420 .btn-ghost { background: transparent; color: var(--fg); box-shadow: inset 0 0 0 2px var(--fg); }
1493
- .ds-247420 .btn-ghost:hover { background: var(--fg); color: var(--bg); transform: translateY(-1px); }
1629
+ .ds-247420 .btn-ghost:hover { background: color-mix(in oklab, var(--fg) 12%, transparent); transform: translateY(-1px); }
1494
1630
  .ds-247420 .btn:active, .ds-247420 .btn-primary:active, .ds-247420 .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
1495
1631
  .ds-247420 .btn:focus-visible, .ds-247420 .btn-primary:focus-visible, .ds-247420 .btn-ghost:focus-visible, .ds-247420 .ds-icon-btn:focus-visible {
1496
1632
  outline: var(--focus-w) solid var(--focus-color);
1497
1633
  outline-offset: var(--focus-offset);
1498
1634
  }
1499
1635
 
1500
- /* Button size ladder — the size prop on Btn() (sm | md | lg). md is the base
1501
- rule above; sm/lg retune height (off the --ctl-* ladder), horizontal padding,
1502
- and font-size so a size swap is one class, never inline padding overrides
1503
- (the old preview faked sizes with style=). Applies to every variant. */
1504
1636
  .ds-247420 .btn-sm, .ds-247420 .btn-sm.btn-primary, .ds-247420 .btn-sm.btn-ghost {
1505
1637
  height: var(--ctl-sm); min-height: var(--ctl-sm);
1506
1638
  padding: 0 var(--space-2); font-size: var(--fs-tiny); gap: var(--space-1);
@@ -1510,10 +1642,9 @@
1510
1642
  padding: 0 var(--space-4); font-size: var(--fs-body); gap: var(--space-2);
1511
1643
  }
1512
1644
 
1513
- /* App-mode quiet interactions — the marketing hover-lift (translateY + shadow)
1514
- and the .row .meta slide read as decorative float on a dense working surface.
1515
- Under [data-typescale="app"], state changes are background/border-only: crisp,
1516
- no motion, no elevation. Marketing surfaces (no data-typescale) keep the lift. */
1645
+ /* App chrome is compact/dense (see --fs-h1-app etc in colors_and_type.css) --
1646
+ the marketing-scale hover lift/shadow reads as an oversized flourish at
1647
+ this density, so app-typescale buttons opt out of it deliberately. */
1517
1648
  .ds-247420[data-typescale="app"] .btn:hover,
1518
1649
  .ds-247420[data-typescale="app"] .btn-primary:hover,
1519
1650
  .ds-247420[data-typescale="app"] .btn-ghost:hover { transform: none; box-shadow: none; }
@@ -1524,36 +1655,38 @@
1524
1655
  .ds-247420[data-typescale="app"] a.row:hover .meta,
1525
1656
  .ds-247420[data-typescale="app"] .row[role="button"]:hover .meta { transform: none; }
1526
1657
 
1527
- /* ============================================================
1528
- IconButton — square icon-only button
1529
- ============================================================ */
1530
1658
  .ds-247420 .ds-icon-btn {
1531
1659
  display: inline-flex; align-items: center; justify-content: center;
1532
1660
  padding: 0; border: none; cursor: pointer;
1533
- border-radius: var(--r-1, 6px);
1661
+ border-radius: var(--r-1, 8px);
1534
1662
  background: transparent; color: var(--fg);
1535
1663
  width: 32px; height: 32px;
1664
+ transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1536
1665
  }
1666
+ .ds-247420 .ds-icon-btn:hover { background: var(--bg-2); }
1537
1667
  .ds-247420 .ds-icon-btn-xs { width: 22px; height: 22px; }
1538
1668
  .ds-247420 .ds-icon-btn-sm { width: 26px; height: 26px; }
1539
1669
  .ds-247420 .ds-icon-btn-base { width: 32px; height: 32px; }
1540
1670
  .ds-247420 .ds-icon-btn-lg { width: 40px; height: 40px; }
1541
1671
  .ds-247420 .ds-icon-btn-xl { width: 48px; height: 48px; }
1672
+ @media (pointer: coarse) {
1673
+ .ds-247420 .ds-icon-btn-xs { width: 32px; height: 32px; }
1674
+ .ds-247420 .ds-icon-btn-sm { width: 32px; height: 32px; }
1675
+ }
1542
1676
  .ds-247420 .ds-icon-btn-ghost { background: transparent; }
1543
1677
  .ds-247420 .ds-icon-btn-ghost:hover { background: var(--fg); color: var(--bg); }
1544
1678
  .ds-247420 .ds-icon-btn-primary { background: var(--accent); color: var(--accent-fg); }
1545
1679
  .ds-247420 .ds-icon-btn-primary:hover { background: var(--fg); color: var(--accent-ink); }
1546
- /* warn = destructive ACTION tone; flame stays exclusively error STATUS. */
1547
- .ds-247420 .ds-icon-btn-danger { background: var(--warn); color: var(--on-color); }
1680
+ /* --ink, not --on-color: --on-color is a fixed #fff, which measures 3.07:1 on
1681
+ --warn (#FF5A52) -- below the 4.5:1 AA floor. --ink measures 6.27:1. Same
1682
+ fix as .btn-primary.danger in app-shell/files.css. */
1683
+ .ds-247420 .ds-icon-btn-danger { background: var(--warn); color: var(--ink); }
1548
1684
  .ds-247420 .ds-icon-btn-danger:hover { filter: brightness(0.92); }
1549
1685
  .ds-247420 .ds-icon-btn:active { transform: translateY(1px); }
1550
1686
  .ds-247420 .ds-icon-btn:disabled, .ds-247420 .ds-icon-btn.is-disabled {
1551
1687
  opacity: 0.5; cursor: not-allowed; pointer-events: none;
1552
1688
  }
1553
1689
 
1554
- /* ============================================================
1555
- Badge — small inline count/status pill
1556
- ============================================================ */
1557
1690
  .ds-247420 .ds-badge {
1558
1691
  display: inline-flex; align-items: center; justify-content: center;
1559
1692
  min-width: 18px; height: 18px; padding: 0 6px;
@@ -1561,18 +1694,25 @@
1561
1694
  border-radius: var(--r-pill);
1562
1695
  background: var(--bg-3); color: var(--fg-2);
1563
1696
  }
1564
- /* Badge size ladder — sm for dense inline counts, lg for prominent markers.
1565
- Base (above) stays the 18px default. */
1566
- .ds-247420 .ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size: 10px; }
1697
+ /* --fs-nano (11px), not --fs-pico (10px): --fs-pico sits below the 11px
1698
+ legibility floor other small-scale labels in this system respect (see
1699
+ .ds-stat-lbl, .ds-session-row-id), and this size is used live for the
1700
+ workspace rail item-count badge (atoms.js Badge size="sm"). */
1701
+ .ds-247420 .ds-badge.ds-badge--sm { min-width: var(--icon-xs); height: var(--icon-xs); padding: 0 var(--space-1); font-size: var(--fs-nano); }
1567
1702
  .ds-247420 .ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
1568
- /* Canonical tone names are tone-success / tone-error / tone-neutral. Every
1569
- other tone name grouped into the same rule below (tone-green/tone-live,
1570
- tone-flame, tone-wip, tone-sun/tone-yellow, tone-ok/tone-miss) is a
1571
- true-synonym legacy alias, kept working but sharing one declaration so the
1572
- tone-family colors can't drift apart across names. */
1573
- .ds-247420 .ds-badge.tone-green, .ds-247420 .ds-badge.tone-live, .ds-247420 .ds-badge.tone-success {
1703
+ .ds-247420 .ds-badge.tone-green, .ds-247420 .ds-badge.tone-success {
1574
1704
  background: var(--green-tint); color: var(--green-deep);
1575
1705
  }
1706
+ /* tone-live is its own hue (sky), split out of the success/green group:
1707
+ "live/broadcasting now" and "operation succeeded" are different facts and
1708
+ read identically when both borrow the brand's accent green — a LIVE badge
1709
+ next to an "ok" chip must not look like the same state. --sky is already
1710
+ used for informational surfaces (ds-alert-info, audio-file icons) and is
1711
+ never pinned by the thebird brand's `--accent:var(--green)!important`
1712
+ override, so it stays visually distinct under every accent preset. */
1713
+ .ds-247420 .ds-badge.tone-live {
1714
+ background: color-mix(in oklab, var(--sky) 22%, var(--bg)); color: var(--sky);
1715
+ }
1576
1716
  .ds-247420 .ds-badge.tone-flame, .ds-247420 .ds-badge.tone-error {
1577
1717
  background: var(--flame); color: var(--on-color);
1578
1718
  }
@@ -1585,7 +1725,7 @@
1585
1725
  background: var(--sun); color: var(--ink);
1586
1726
  }
1587
1727
  .ds-247420 .ds-badge.tone-blue { background: var(--accent-tint); color: var(--accent-ink); }
1588
- .ds-247420 .ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
1728
+ .ds-247420 .ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 22%, var(--bg)); color: var(--flame); }
1589
1729
 
1590
1730
  .ds-247420 .chip {
1591
1731
  display: inline-flex; align-items: center; gap: var(--space-1-75);
@@ -1596,42 +1736,55 @@
1596
1736
  letter-spacing: var(--tr-caps); text-transform: uppercase;
1597
1737
  border-radius: var(--r-pill);
1598
1738
  }
1599
- /* Chip size ladder — sm for dense toolbars/table cells, lg for prominent
1600
- status. tone-* colouring (below) is orthogonal to size. */
1601
1739
  .ds-247420 .chip.chip--sm { padding: var(--space-half) var(--space-2); font-size: var(--fs-micro); gap: var(--space-1); }
1602
1740
  .ds-247420 .chip.chip--lg { padding: var(--space-1-75) var(--space-2-75); font-size: var(--fs-xs); }
1603
- /* Tag variant — a rectangular, sentence-case chip for dense data (table cells,
1604
- inline labels) where the all-caps pill reads too shouty and space-hungry.
1605
- Keeps the tone-* palette; drops the caps + pill for a tight --r-0 corner. */
1606
1741
  .ds-247420 .chip.chip--tag {
1607
1742
  text-transform: none; letter-spacing: 0; font-weight: 500;
1608
1743
  border-radius: var(--r-0); padding: var(--space-half) var(--space-2);
1609
1744
  }
1610
- /* Same canonical/legacy-alias grouping as .ds-badge above. */
1611
- .ds-247420 .chip.tone-green, .ds-247420 .chip.tone-live, .ds-247420 .chip.tone-success, .ds-247420 .chip.tone-ok {
1745
+ .ds-247420 .chip.tone-green, .ds-247420 .chip.tone-success, .ds-247420 .chip.tone-ok {
1612
1746
  background: var(--green-tint); color: var(--green-deep);
1613
1747
  }
1748
+ /* tone-live: its own sky hue, split from tone-ok/tone-success — see the
1749
+ .ds-badge.tone-live comment above for the rationale. A small pulsing dot
1750
+ precedes the label so "live" reads as an active broadcast state, not a
1751
+ static status chip. */
1752
+ .ds-247420 .chip.tone-live {
1753
+ background: color-mix(in oklab, var(--sky) 16%, var(--bg));
1754
+ color: var(--sky);
1755
+ position: relative;
1756
+ padding-left: 22px;
1757
+ }
1758
+ .ds-247420 .chip.tone-live::before {
1759
+ content: '';
1760
+ position: absolute;
1761
+ left: 10px; top: 50%;
1762
+ width: 6px; height: 6px;
1763
+ border-radius: 50%;
1764
+ background: var(--sky);
1765
+ transform: translateY(-50%);
1766
+ animation: chip-live-pulse 1.6s ease-in-out infinite;
1767
+ }
1768
+ @keyframes chip-live-pulse {
1769
+ 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in oklab, var(--sky) 50%, transparent); }
1770
+ 50% { opacity: 0.7; box-shadow: 0 0 0 4px transparent; }
1771
+ }
1772
+ @media (prefers-reduced-motion: reduce) {
1773
+ .ds-247420 .chip.tone-live::before { animation: none; }
1774
+ }
1614
1775
  .ds-247420 .chip.tone-flame, .ds-247420 .chip.tone-error, .ds-247420 .chip.tone-miss {
1615
1776
  background: var(--flame); color: var(--on-color);
1616
1777
  }
1778
+ .ds-247420 .chip.tone-warn {
1779
+ background: color-mix(in oklab, var(--amber) 22%, var(--bg));
1780
+ color: var(--amber);
1781
+ }
1617
1782
  .ds-247420 .chip.tone-wip, .ds-247420 .chip.tone-neutral {
1618
1783
  background: var(--bg-3); color: var(--fg-2);
1619
1784
  }
1620
- /* `.chip.accent` is the bare-adjective spelling of tone-accent (ui_kits/blog
1621
- uses it for a "draft" chip). It had no rule anywhere, so an accented chip
1622
- rendered byte-identical to a plain one. Folded into the alias grouping rather
1623
- than given its own block, so the two spellings cannot drift apart. --accent
1624
- is the FILL and --accent-ink the readable text tone (AGENTS.md) — the bare
1625
- lead is ~1.07:1 on paper and unreadable as text. */
1626
1785
  .ds-247420 .chip.accent, .ds-247420 .chip.tone-accent {
1627
1786
  background: var(--accent-tint); color: var(--accent-ink);
1628
1787
  }
1629
- /* tone-dim was applied by five kits (aicat, project_page, signin, system_primer,
1630
- terminal) with no rule anywhere, so a "dim" chip rendered byte-identical to a
1631
- plain one — measured at rgb(234,230,218) on rgb(31,31,38), same opacity. It is
1632
- the recede-into-the-row tone: the tier-3 foreground on the same recessed
1633
- surface tone-wip uses, which is quieter than the default --fg-2 without
1634
- dropping to an opacity fade that would take the border and background with it. */
1635
1788
  .ds-247420 .chip.tone-dim {
1636
1789
  background: var(--bg-3); color: var(--fg-3);
1637
1790
  }
@@ -1643,12 +1796,7 @@
1643
1796
  .ds-247420 .chip.tone-disabled { background: var(--bg-3); color: var(--fg-3); }
1644
1797
  .ds-247420 .chip.tone-blue { background: var(--accent-tint); color: var(--accent-ink); }
1645
1798
  .ds-247420 .chip.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
1646
- .ds-247420 .chip.tone-yellow { background: var(--sun); color: var(--ink); }
1647
1799
 
1648
- /* Removable chip — a trailing dismiss (x) button for editable tag/filter
1649
- chip lists (session tags, saved-search chips). Reuses the chip's own
1650
- tone/size; the button itself stays neutral-at-rest, per the standing
1651
- color-reserved-for-meaning convention, tinting only on hover/focus. */
1652
1800
  .ds-247420 .chip.ds-chip-removable { padding-right: var(--space-1); }
1653
1801
  .ds-247420 .ds-chip-remove-btn {
1654
1802
  display: inline-flex; align-items: center; justify-content: center;
@@ -1658,14 +1806,12 @@
1658
1806
  cursor: pointer;
1659
1807
  }
1660
1808
  .ds-247420 .ds-chip-remove-btn:hover, .ds-247420 .ds-chip-remove-btn:focus-visible { opacity: 1; background: color-mix(in oklab, currentColor 16%, transparent); }
1661
- .ds-247420 .ds-chip-remove-btn svg { width: 10px; height: 10px; }
1809
+ .ds-247420 .ds-chip-remove-btn svg { width: var(--icon-xs); height: var(--icon-xs); }
1662
1810
  @media (pointer: coarse) {
1663
1811
  .ds-247420 .ds-chip-remove-btn { width: 24px; height: 24px; margin-left: 0; }
1664
- .ds-247420 .ds-chip-remove-btn svg { width: 12px; height: 12px; }
1812
+ .ds-247420 .ds-chip-remove-btn svg { width: var(--icon-xs); height: var(--icon-xs); }
1665
1813
  }
1666
1814
 
1667
- /* ds-pill — plain neutral chip, distinct from the toned .ds-badge/.chip family
1668
- (gmsniff's .pill: a bare rounded label with no semantic tone). */
1669
1815
  .ds-247420 .ds-pill {
1670
1816
  display: inline-block;
1671
1817
  padding: 1px 7px;
@@ -1677,26 +1823,75 @@
1677
1823
  .ds-247420 .ds-pill.tone-accent { background: var(--accent-tint); color: var(--accent-ink); }
1678
1824
  .ds-247420 .ds-pill.tone-muted { background: transparent; color: var(--fg-3); box-shadow: inset 0 0 0 1px var(--rule); }
1679
1825
 
1826
+ /* .glyph-* sizes (14/18/22px) are for small inline text-adjacent glyphs
1827
+ (single characters/icons sitting in a text flow), a different scale and
1828
+ purpose than the .ds-icon-btn-* tap-target scale (22/26/32/40/48px) below --
1829
+ the -sm/-lg naming overlap is coincidental, not a shared system. */
1680
1830
  .ds-247420 .glyph {
1681
1831
  display: inline-flex; align-items: center; justify-content: center;
1682
1832
  width: 18px; height: 18px;
1683
1833
  font-family: var(--ff-mono); font-size: var(--fs-tiny);
1684
1834
  opacity: 0.8;
1685
1835
  }
1686
- /* Glyph() (src/components/shell/atoms.js) emits `glyph glyph-{size}` and an
1687
- inline `font-size: var(--glyph-size-{size}, <literal>)`. The custom property
1688
- is the documented theming hook — it is what lets a theme retune glyph scale
1689
- without touching the component — but nothing declared it, so every glyph fell
1690
- through to the hard-coded inline literal and the hook was inert. Declaring
1691
- the three rungs on the scale makes the hook real; the sm/lg classes also give
1692
- the box a matching footprint instead of the one-size 18px square. */
1693
- /* Values mirror Glyph()'s own inline fallbacks exactly (11 / 13 / 16px), so
1694
- declaring the hook changes nothing visually — it only makes the property
1695
- themeable, which is what it always claimed to be. */
1696
1836
  .ds-247420 .glyph { --glyph-size-sm: var(--fs-nano); --glyph-size-base: var(--fs-tiny); --glyph-size-lg: var(--fs-body); }
1697
- .ds-247420 .glyph-sm { width: 14px; height: 14px; }
1837
+ .ds-247420 .glyph-sm { width: var(--icon-xs); height: var(--icon-xs); }
1838
+ .ds-247420 .glyph-base { width: 18px; height: 18px; }
1698
1839
  .ds-247420 .glyph-lg { width: 22px; height: 22px; }
1699
1840
 
1841
+ /* Shared empty-state base -- hero-content.css's .empty and community.css's
1842
+ .fd-empty previously duplicated this full rule set; both are joined into
1843
+ this selector list so the shared declarations live in one place, with
1844
+ each keeping its own file-local rule for context-specific extras
1845
+ (background/radius/font-size for .empty; padding for .fd-empty). New
1846
+ consumers can add .ds-empty-state directly instead of a fresh copy. */
1847
+ .ds-247420 .ds-empty-state, .ds-247420 .empty, .ds-247420 .fd-empty {
1848
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
1849
+ gap: var(--space-2);
1850
+ padding: var(--space-6) var(--space-3);
1851
+ text-align: center;
1852
+ color: var(--fg-3);
1853
+ }
1854
+ .ds-247420 .ds-empty-state--panel { background: var(--bg-2); border-radius: var(--r-1); }
1855
+ .ds-247420 .ds-empty-state--compact { padding: var(--space-5) var(--space-3); }
1856
+
1857
+ /* .ds-tooltip — shared floating bubble (overlay-primitives/tooltip.js appends
1858
+ one instance to <body> and repositions it via useFloating). Fade timing
1859
+ matches the component's own 350ms hover-intent delay. */
1860
+ .ds-247420 .ds-tooltip {
1861
+ position: fixed;
1862
+ z-index: var(--z-top);
1863
+ max-width: 240px;
1864
+ padding: var(--space-1) var(--space-2);
1865
+ border-radius: var(--r-1);
1866
+ background: var(--fg);
1867
+ color: var(--bg);
1868
+ font-size: var(--fs-xs);
1869
+ line-height: 1.35;
1870
+ box-shadow: var(--shadow-2);
1871
+ pointer-events: none;
1872
+ opacity: 0;
1873
+ transform: scale(0.96);
1874
+ transition: opacity var(--dur-snap) var(--ease), transform var(--dur-snap) var(--ease);
1875
+ }
1876
+ .ds-247420 .ds-tooltip:not([hidden]) { opacity: 1; transform: scale(1); }
1877
+ .ds-247420 .ds-tooltip::after {
1878
+ content: '';
1879
+ position: absolute;
1880
+ width: 6px;
1881
+ height: 6px;
1882
+ background: var(--fg);
1883
+ transform: rotate(45deg);
1884
+ }
1885
+ .ds-247420 .ds-tooltip[data-placement^="top"]::after { bottom: -3px; left: 50%; margin-left: -3px; }
1886
+ .ds-247420 .ds-tooltip[data-placement^="bottom"]::after { top: -3px; left: 50%; margin-left: -3px; }
1887
+ .ds-247420 .ds-tooltip[data-placement^="left"]::after { right: -3px; top: 50%; margin-top: -3px; }
1888
+ .ds-247420 .ds-tooltip[data-placement^="right"]::after { left: -3px; top: 50%; margin-top: -3px; }
1889
+ .ds-247420 .ds-tooltip.kind-danger { background: var(--warn); color: var(--ink); }
1890
+ .ds-247420 .ds-tooltip.kind-danger::after { background: var(--warn); }
1891
+ @media (prefers-reduced-motion: reduce) {
1892
+ .ds-247420 .ds-tooltip { transition: none; }
1893
+ }
1894
+
1700
1895
  /* ============================================================
1701
1896
  Panel — soft tonal container, no border decoration
1702
1897
  ============================================================ */
@@ -1758,9 +1953,19 @@
1758
1953
  font-size: var(--fs-tiny); font-weight: 600;
1759
1954
  color: var(--fg-3);
1760
1955
  letter-spacing: var(--tr-caps); text-transform: uppercase;
1956
+ /* A hairline divider is the header's only structural separator from the
1957
+ body — without it, an uppercase caps label at fs-tiny/600 reads as just
1958
+ another dim meta line once the eye drops past the top of the panel. */
1959
+ border-bottom: var(--bw-hair) solid var(--rule);
1960
+ margin-bottom: var(--space-1);
1761
1961
  }
1762
1962
  .ds-247420 .panel.panel-wide .panel-head { padding-left: 0; padding-right: 0; }
1763
- .ds-247420 .panel-head > :last-child {
1963
+ /* A borderless .panel-wide (no card edge to lean on) would otherwise show a
1964
+ divider floating with nothing to visually anchor it — drop it there and
1965
+ rely on spacing alone, matching panel-wide's already-transparent chrome. */
1966
+ .ds-247420 .panel.panel-wide .panel-head { border-bottom: none; margin-bottom: 0; }
1967
+ .ds-247420 .panel.panel--dense .panel-head { border-bottom: var(--bw-hair) solid var(--rule); margin-bottom: 0; }
1968
+ .ds-247420 .panel-head > :last-child:not(.ds-badge) {
1764
1969
  font-weight: 500; color: var(--fg-3);
1765
1970
  font-family: var(--ff-mono); letter-spacing: 0;
1766
1971
  }
@@ -1772,6 +1977,13 @@
1772
1977
  margin: 0; font: inherit; font-size: inherit; font-weight: inherit;
1773
1978
  letter-spacing: inherit; text-transform: inherit; color: inherit;
1774
1979
  }
1980
+ /* Category glyph beside a panel title (kits / previews / decks / docs on the
1981
+ homepage) — same inline-svg vocabulary as icons.js, sized to sit on the
1982
+ text baseline rather than dominating it. */
1983
+ .ds-247420 .ds-panel-title-glyph {
1984
+ display: inline-flex; align-items: center; gap: var(--space-1-5, 6px);
1985
+ }
1986
+ .ds-247420 .ds-panel-title-glyph .ds-icon { flex-shrink: 0; color: var(--fg-3); }
1775
1987
  .ds-247420 .panel-body { padding: var(--space-2) var(--space-3) var(--space-3); }
1776
1988
  .ds-247420 .panel-body > * { margin: 0; }
1777
1989
  /* One-line explanatory caption under a panel's title — e.g. distinguishing
@@ -1812,6 +2024,14 @@
1812
2024
  transition: background var(--dur-snap) var(--ease), box-shadow var(--dur-base) var(--ease-spring);
1813
2025
  }
1814
2026
  .ds-247420 .row + .row { margin-top: 2px; }
2027
+ /* Mobile tap-target floor: at compact/comfortable density on a touch
2028
+ viewport the padding-only row height can fall under the 44px effective
2029
+ tap target (WCAG 2.5.5), especially at [data-density="compact"] (0.75x).
2030
+ Force a min-height on narrow/coarse-pointer viewports only, so the dense
2031
+ desktop mono/list layout (file-row, index-row) is untouched above 640px. */
2032
+ @media (max-width: 640px), (pointer: coarse) {
2033
+ .ds-247420 .row, .ds-247420 a.row, .ds-247420 button.row, .ds-247420 .row[role="button"] { min-height: 44px; }
2034
+ }
1815
2035
  /* Interactive rows (anchor/button wrappers) get a hairline lift on hover,
1816
2036
  matching the btn/panel elevation language — non-interactive rows (plain
1817
2037
  divs used as static list items) are unaffected since the selector needs a
@@ -2046,6 +2266,12 @@
2046
2266
  padding-bottom: var(--space-2); border-bottom: var(--bw-hair) solid var(--bg-3);
2047
2267
  }
2048
2268
  .ds-247420 .ds-hero-stat:last-child { border-bottom: 0; padding-bottom: 0; }
2269
+ /* HeroFromPageData's richer badge shape ({label, desc}) renders a strong
2270
+ figure plus a muted descriptor inside the same .ds-hero-stat row — give
2271
+ the pair a baseline-aligned inline layout instead of falling back to
2272
+ unstyled default inline flow. */
2273
+ .ds-247420 .ds-hero-stat-n { font-weight: 700; color: var(--fg); margin-right: var(--space-2); }
2274
+ .ds-247420 .ds-hero-stat-l { color: var(--fg-3); font-size: var(--fs-xs); }
2049
2275
  .ds-247420 .ds-hero-title {
2050
2276
  font-family: var(--ff-display); font-weight: 700;
2051
2277
  /* Bespoke display clamp, deliberately off the --fs-* ladder: --fs-hero is
@@ -2054,7 +2280,12 @@
2054
2280
  still fits a narrow column, and a 116px ceiling between the two tiers.
2055
2281
  Snapping to either changes the hero's whole optical weight. */
2056
2282
  font-size: clamp(40px, 9cqi, 116px);
2057
- line-height: 0.96; letter-spacing: var(--tr-tighter);
2283
+ /* 0.96 let ascenders/descenders visibly touch between wrapped lines at the
2284
+ upper end of this clamp (measured on "the creative department of the
2285
+ internet" at the 116px ceiling) -- 1.02 keeps the tight, no-air display
2286
+ feel this title wants while clearing enough vertical room that letters
2287
+ stop colliding. */
2288
+ line-height: 1.02; letter-spacing: var(--tr-tighter);
2058
2289
  margin: 0; max-width: 20ch; text-wrap: balance;
2059
2290
  }
2060
2291
  .ds-247420 .ds-hero-body {
@@ -2066,8 +2297,20 @@
2066
2297
  .ds-247420 .ds-hero-accent { color: var(--accent-ink); font-weight: 700; }
2067
2298
  .ds-247420 .ds-hero-actions {
2068
2299
  display: flex; gap: var(--space-2, 10px); flex-wrap: wrap;
2069
- justify-content: flex-start; margin-top: var(--space-hair, 4px);
2070
- }
2300
+ justify-content: flex-start; margin-top: var(--space-4);
2301
+ }
2302
+ /* Hero CTA labels are content-driven (e.g. "GitHub", proper nouns from
2303
+ home.yaml) and the site's voice is deliberately all-lowercase everywhere
2304
+ else. Force the rendered case here rather than hand-lowercasing every
2305
+ proper-noun label at the content layer, so a future CTA label doesn't
2306
+ silently reintroduce Title Case. */
2307
+ .ds-247420 .ds-hero-actions .btn { text-transform: lowercase; }
2308
+ /* Filter input above the homepage's "ui kits" panel — reuses the same
2309
+ .input token the search kit's own topbar query box uses, just placed
2310
+ inline above the panel instead of in a topbar slot. */
2311
+ .ds-247420 .ds-kits-panel-wrap { display: flex; flex-direction: column; gap: var(--space-2, 10px); }
2312
+ .ds-247420 .ds-kits-filter { display: flex; }
2313
+ .ds-247420 .ds-kits-filter-input { width: 100%; max-width: 32ch; }
2071
2314
  /* Below this width the aside card's own internal rhythm (stat rows still
2072
2315
  stacked vertically at full width) is already correct for a narrow column —
2073
2316
  no separate breakpoint override needed now that .ds-hero is always a
@@ -2141,12 +2384,15 @@
2141
2384
  padding: var(--space-2) 0;
2142
2385
  font-family: var(--ff-display); font-weight: 600;
2143
2386
  text-transform: uppercase; letter-spacing: var(--tr-caps);
2387
+ mask-image: linear-gradient(90deg, transparent 0, var(--fg) 28px, var(--fg) calc(100% - 28px), transparent 100%);
2388
+ -webkit-mask-image: linear-gradient(90deg, transparent 0, var(--fg) 28px, var(--fg) calc(100% - 28px), transparent 100%);
2144
2389
  }
2145
2390
  .ds-247420 .ds-marquee-track {
2146
2391
  display: inline-flex; gap: var(--space-5);
2147
2392
  white-space: nowrap; will-change: transform;
2148
2393
  animation: ds-marquee-run 28s linear infinite;
2149
2394
  }
2395
+ .ds-247420 .ds-marquee-run { display: inline-flex; align-items: center; gap: var(--space-5); }
2150
2396
  .ds-247420 .ds-marquee-item { display: inline-flex; align-items: center; gap: var(--space-5); }
2151
2397
  .ds-247420 .ds-marquee-sep { color: var(--accent-ink); }
2152
2398
  .ds-247420 .ds-marquee:hover .ds-marquee-track,
@@ -2172,7 +2418,7 @@
2172
2418
  .ds-247420 .cli {
2173
2419
  display: flex; align-items: center; gap: 14px;
2174
2420
  overflow-x: auto;
2175
- padding: 20px 24px;
2421
+ padding: var(--space-3-5) var(--space-4);
2176
2422
  background: var(--ink); color: var(--paper);
2177
2423
  border-radius: var(--r-3);
2178
2424
  font-family: var(--ff-mono); font-size: var(--fs-lg);
@@ -2180,12 +2426,13 @@
2180
2426
  .ds-247420 .cli .prompt { color: var(--green-2); }
2181
2427
  .ds-247420 .cli .cmd { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; color: var(--paper); }
2182
2428
  .ds-247420 .cli .copy {
2183
- padding: 8px 16px; background: transparent; color: var(--paper);
2429
+ padding: var(--space-2) var(--space-3); background: transparent; color: var(--paper);
2184
2430
  font-size: var(--fs-tiny); text-transform: uppercase; letter-spacing: var(--tr-caps);
2185
2431
  cursor: pointer; box-shadow: inset 0 0 0 1px var(--rule-strong);
2186
2432
  border-radius: var(--r-pill);
2187
2433
  }
2188
2434
  .ds-247420 .cli .copy:hover { background: var(--fg-3); }
2435
+ .ds-247420 .cli .copy:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2189
2436
 
2190
2437
  /* Multi-line CLI block: when .cli holds .ds-cli-row / .ds-cli-comment children
2191
2438
  (quickstart scripts, multi-command snippets — see CliBlock in
@@ -2202,12 +2449,32 @@
2202
2449
  padding: 3px 0;
2203
2450
  }
2204
2451
  .ds-247420 .ds-cli-row .prompt { color: var(--green-2); flex: 0 0 auto; user-select: none; }
2205
- .ds-247420 .ds-cli-row .cmd { flex: 1 1 auto; white-space: pre-wrap; word-break: break-word; color: var(--paper); }
2452
+ /* `overflow-wrap: break-word` only breaks a token when it genuinely cannot
2453
+ fit the line at all (last resort); `word-break: break-word` (its
2454
+ predecessor here) breaks ANY overflowing token eagerly, which is what
2455
+ split "anentrypoint-design" mid-identifier inside the importmap JSON line.
2456
+ `min-width: 0` lets the flex child actually shrink to trigger wrapping
2457
+ instead of pushing the panel wider. */
2458
+ .ds-247420 .ds-cli-row .cmd { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: break-word; word-break: normal; color: var(--paper); }
2206
2459
  .ds-247420 .ds-cli-comment {
2207
2460
  color: var(--fg-3);
2208
2461
  font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1.6;
2209
- padding: 3px 0; white-space: pre-wrap; word-break: break-word;
2462
+ padding: 3px 0; white-space: pre-wrap; overflow-wrap: break-word; word-break: normal;
2463
+ }
2464
+
2465
+ /* Quickstart copy button: sits below the multi-line block (Install's inline
2466
+ copy button assumes a single-line row with room beside it; the stacked
2467
+ quickstart block has no such side room). */
2468
+ .ds-247420 .ds-quickstart-cli { flex-direction: column; align-items: stretch; gap: var(--space-2); }
2469
+ .ds-247420 .ds-quickstart-copy {
2470
+ align-self: flex-end;
2471
+ padding: var(--space-2) var(--space-3); background: transparent; color: var(--paper);
2472
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); text-transform: uppercase; letter-spacing: var(--tr-caps);
2473
+ cursor: pointer; box-shadow: inset 0 0 0 1px var(--rule-strong);
2474
+ border-radius: var(--r-pill); border: none;
2210
2475
  }
2476
+ .ds-247420 .ds-quickstart-copy:hover { background: var(--fg-3); }
2477
+ .ds-247420 .ds-quickstart-copy:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2211
2478
 
2212
2479
  /* ============================================================
2213
2480
  Receipt — key/value table
@@ -2257,13 +2524,27 @@
2257
2524
  (was 12/14 x 16 => ~43px rows). Density scales the vertical padding. */
2258
2525
  border-radius: var(--r-0); overflow: hidden;
2259
2526
  }
2527
+ /* Panel heads / KPI labels / table headers were all identical uppercase-
2528
+ tracked-mono weight (600), so a page with several of these side by side
2529
+ (a dashboard) reads as one flat volume with no anchor -- the most
2530
+ repetitive, least-singular label (a column header, one of many per
2531
+ table) drops to a lighter 500 with slightly tighter tracking, keeping the
2532
+ system's uppercase-mono voice but giving panel titles (still 600) a real
2533
+ step up as the "read this first" tier. */
2260
2534
  .ds-247420 table th {
2261
2535
  text-align: left;
2262
2536
  padding: calc(var(--space-2) * var(--density)) var(--space-2-75);
2263
- font-size: var(--fs-tiny); font-weight: 600;
2264
- color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps);
2537
+ font-size: var(--fs-tiny); font-weight: 500;
2538
+ color: var(--fg-3); text-transform: uppercase; letter-spacing: calc(var(--tr-caps) * 0.7);
2265
2539
  background: var(--bg-2);
2266
2540
  }
2541
+ /* letter-spacing adds trailing space AFTER the last glyph too, but the table's
2542
+ own border-radius+overflow:hidden (above) clips flush to the card edge --
2543
+ the last header's positive tracking bleeds past its own padding box and
2544
+ gets cropped mid-word ("STATUS" -> "STATI"). Absorb that bleed with a bit
2545
+ of extra right padding on the last column only, so no header text sits
2546
+ closer to the clip edge than --tr-caps can push it. */
2547
+ .ds-247420 table th:last-child { padding-right: calc(var(--space-2-75) + var(--tr-caps, 0px)); }
2267
2548
  .ds-247420 table td { padding: calc(var(--space-2) * var(--density)) var(--space-2-75); border-top: 1px solid var(--rule); }
2268
2549
  .ds-247420 table tr.clickable { cursor: pointer; }
2269
2550
  .ds-247420 table tr.clickable:hover td { background: var(--bg-2); }
@@ -2272,6 +2553,12 @@
2272
2553
  outline-offset: -2px;
2273
2554
  }
2274
2555
  .ds-247420 table tr.clickable:focus-visible td { background: var(--bg-2); }
2556
+ /* Non-clickable (static) rows still get a subtle hover cue — most dashboard
2557
+ tables (freddie's sessions-by-platform/model, tool distribution, commands)
2558
+ never pass onRowClick, so they had zero row-hover feedback at all. A dim
2559
+ hover on ANY row (not just .clickable) reads as "you're scanning this
2560
+ row" without implying it's actionable. */
2561
+ .ds-247420 table tr:not(.clickable):hover td { background: color-mix(in oklab, var(--bg-2) 60%, transparent); }
2275
2562
 
2276
2563
  /* Table() striped/compact opt-in density modifiers (webgeist g-table parity).
2277
2564
  Scoped under .ds-table-wrap so they never leak onto an unrelated bare
@@ -2279,8 +2566,15 @@
2279
2566
  .ds-247420 .ds-table-wrap.is-striped table tr:nth-child(even) td { background: var(--bg-2); }
2280
2567
  .ds-247420 .ds-table-wrap.is-striped table tr.clickable:nth-child(even):hover td,
2281
2568
  .ds-247420 .ds-table-wrap.is-striped table tr.clickable:nth-child(even):focus-visible td { background: var(--bg-3, var(--bg-2)); }
2569
+ .ds-247420 .ds-table-wrap.is-striped table tr:not(.clickable):nth-child(even):hover td { background: color-mix(in oklab, var(--bg-3, var(--bg-2)) 60%, transparent); }
2282
2570
  .ds-247420 .ds-table-wrap.is-compact table th { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
2283
2571
  .ds-247420 .ds-table-wrap.is-compact table td { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
2572
+ /* Numeric-column right-alignment: Table() (content/table.js) tags a cell
2573
+ td.is-num when its raw content is a plain integer/decimal, so digit
2574
+ columns (session counts, tool counts, etc.) line up on the ones place
2575
+ instead of ragged-left like prose columns. th.is-num mirrors it on the
2576
+ matching header so the label sits over its column. */
2577
+ .ds-247420 table td.is-num, .ds-247420 table th.is-num { text-align: right; font-variant-numeric: tabular-nums; }
2284
2578
 
2285
2579
  /* ============================================================
2286
2580
  Changelog
@@ -2343,7 +2637,10 @@
2343
2637
  .ds-247420 .ds-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; vertical-align: middle; }
2344
2638
  .ds-247420 .ds-dot-on { color: var(--green-2); }
2345
2639
  .ds-247420 .ds-dot-off { color: var(--fg-3); }
2346
- .ds-247420 .ds-dot-live { color: var(--green-2); }
2640
+ /* live is a distinct hue from a plain "on" dot — see .chip.tone-live's
2641
+ comment for the success-vs-live rationale. */
2642
+ .ds-247420 .ds-dot-live { color: var(--sky); }
2643
+ .ds-247420 .ds-dot-warn { color: var(--amber); }
2347
2644
  .ds-247420 .ds-dot-idle { color: var(--fg-3); }
2348
2645
 
2349
2646
  /* ============================================================
@@ -2360,6 +2657,37 @@
2360
2657
  padding: var(--space-3);
2361
2658
  background: var(--bg-2);
2362
2659
  border-radius: var(--r-1);
2660
+ /* A thin border + hover elevation gives the tile real edges and a "this is
2661
+ a distinct object" affordance instead of a flat color patch that blends
2662
+ into the surrounding panel — matches the "no border, no hover state"
2663
+ gap named against the stat tiles. */
2664
+ border: 1px solid var(--rule);
2665
+ position: relative;
2666
+ transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
2667
+ }
2668
+ .ds-247420 .kpi-card:hover {
2669
+ border-color: color-mix(in oklab, var(--accent) 40%, var(--rule));
2670
+ transform: translateY(-1px);
2671
+ box-shadow: var(--shadow-1, 0 2px 8px rgba(0,0,0,.12));
2672
+ }
2673
+ /* Number+caption stack, isolated from .kpi-foot below it. Previously the
2674
+ number and label were plain block children of .kpi-card with the label's
2675
+ own margin-top providing the gap — visually fine for a single card, but
2676
+ across a row of four the caption's WRAPPED height (one line vs two, e.g.
2677
+ "requests · 24h" vs "error rate · 5xx+4xx") pushed .kpi-foot down by a
2678
+ different amount per card, so the deltas/sparklines never lined up on one
2679
+ shared baseline. A dedicated flex column groups exactly the two elements
2680
+ that must stay glued together, independent of whatever sits below. */
2681
+ .ds-247420 .kpi-stack {
2682
+ display: flex; flex-direction: column; align-items: flex-start;
2683
+ }
2684
+ .ds-247420 .kpi-glyph {
2685
+ position: absolute;
2686
+ top: var(--space-2);
2687
+ right: var(--space-2);
2688
+ color: var(--fg-3);
2689
+ opacity: 0.6;
2690
+ display: inline-flex;
2363
2691
  }
2364
2692
  .ds-247420 .kpi-card .num {
2365
2693
  font-family: var(--ff-body); font-weight: 600;
@@ -2383,10 +2711,13 @@
2383
2711
  display: inline-flex; align-items: center; gap: var(--space-hair);
2384
2712
  font-size: var(--fs-xs); font-weight: 600;
2385
2713
  }
2714
+ /* --green, not --success (--green-2): --success measures 3.2:1 on --paper-2,
2715
+ below the 4.5:1 AA floor for text -- --success is tuned for non-text uses
2716
+ (fills/backgrounds, where 3:1 suffices), --green clears 5.2:1 as text. */
2386
2717
  .ds-247420 .kpi-delta-up { color: var(--green); }
2387
2718
  .ds-247420 .kpi-delta-down { color: var(--danger); }
2388
2719
  .ds-247420 .ds-sparkline path { stroke: currentColor; }
2389
- .ds-247420 .ds-sparkline-up { color: var(--success); }
2720
+ .ds-247420 .ds-sparkline-up { color: var(--green); }
2390
2721
  .ds-247420 .ds-sparkline-down { color: var(--danger); }
2391
2722
 
2392
2723
  /* ============================================================
@@ -2417,13 +2748,12 @@
2417
2748
  /* ============================================================
2418
2749
  Empty state
2419
2750
  ============================================================ */
2751
+ /* Shares its base declarations with .ds-empty-state (primitives.css) via the
2752
+ combined selector there -- .empty only adds its own font-size/background/
2753
+ radius modifiers on top. Was --space-8 (96px) padding; --space-6 (48px)
2754
+ still centres a message with air without the empty state dominating the
2755
+ fold. */
2420
2756
  .ds-247420 .empty {
2421
- /* Was --space-8 (96px) — an empty list spent 192px of vertical space on
2422
- nothing. --space-6 (48px) still centres a message with air, without the
2423
- empty state dominating the fold. Mobile drops it further (below). */
2424
- padding: var(--space-6);
2425
- text-align: center;
2426
- color: var(--fg-3);
2427
2757
  background: var(--bg-2);
2428
2758
  border-radius: var(--r-1);
2429
2759
  font-size: var(--fs-sm);
@@ -2433,18 +2763,40 @@
2433
2763
  Form
2434
2764
  ============================================================ */
2435
2765
  .ds-247420 .row-form {
2436
- display: grid; gap: 12px;
2766
+ display: grid; gap: var(--space-2-75);
2437
2767
  padding: var(--space-5);
2438
2768
  background: var(--bg-2);
2439
2769
  border-radius: var(--r-3);
2440
- }
2770
+ /* Grid's default justify-items:stretch was silently stretching the submit
2771
+ * button (a direct grid child, same as every input) to the full column
2772
+ * width -- other primary buttons elsewhere (e.g. session-ui's "new
2773
+ * session") are inline/compact by contrast, since they're not grid
2774
+ * children. justify-self:start keeps inputs/textareas at their natural
2775
+ * grid-stretched width (a form field SHOULD fill its column) while the
2776
+ * submit button sizes to its own content, matching the compact convention
2777
+ * used everywhere else primary buttons appear. */
2778
+ }
2779
+ .ds-247420 .row-form > button[type="submit"] { justify-self: start; }
2441
2780
  .ds-247420 .row-form input,
2442
2781
  .ds-247420 .row-form textarea {
2443
2782
  font-family: inherit; font-size: var(--fs-sm);
2444
2783
  padding: 12px 16px;
2445
- background: var(--bg); color: var(--fg);
2784
+ /* --bg here previously resolved to the SAME color as .row-form's own
2785
+ * --bg-2 background in the dark theme, leaving only a 14%-opacity
2786
+ * currentColor ring (var(--rule)) as the sole edge between an input and
2787
+ * the panel behind it -- effectively invisible. --bg-3 (one step darker
2788
+ * than the panel) plus a stronger border restores a real, panel-distinct
2789
+ * field surface.
2790
+ * LOAD-BEARING !important: `.row-form input` and states-interactions.css's
2791
+ * bare `input[type="text"]:where(...)` rule are both (0,1,1) specificity
2792
+ * (the :where() wrapper there is intentionally zero-specificity) --
2793
+ * states-interactions.css @imports AFTER this file in app-shell.css, so on
2794
+ * this exact tie it wins by source order and silently discarded these two
2795
+ * declarations. Only !important beats that without restructuring the
2796
+ * shared bare-input rule's own selector, which many other consumers rely on. */
2797
+ background: var(--bg-3) !important; color: var(--fg);
2446
2798
  border: 0; border-radius: var(--r-2);
2447
- box-shadow: inset 0 0 0 1px var(--rule);
2799
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, currentColor 28%, transparent) !important;
2448
2800
  }
2449
2801
  .ds-247420 .row-form input:focus-visible,
2450
2802
  .ds-247420 .row-form textarea:focus-visible { box-shadow: var(--focus-ring-inset); }
@@ -2466,6 +2818,19 @@
2466
2818
  Responsive Design — Mobile (480px), Tablet (1024px), Desktop (1440px+)
2467
2819
  ============================================================ */
2468
2820
 
2821
+ /* Breakpoint reference (CSS custom properties can't be used in @media queries,
2822
+ so these are documented here for consistency — always use these exact values):
2823
+ - Mobile/tablet boundary: 768px (max-width) — used for compact nav/sidebar
2824
+ collapse (catalog-theme.css, sidebar-misc.css). Note: kits-appended.css
2825
+ uses 760px for the same kind of collapse in a few places — a near-miss
2826
+ of this value, worth aligning to 768px when touched.
2827
+ - Tablet/desktop boundary: 900px (max-width) / 901px (min-width) — used for
2828
+ main layout reflow (hero-content.css, responsive2-workspace.css)
2829
+ - Tablet landscape band: 768px–1024px (min-width 768px / max-width 1024px) —
2830
+ used in this file (responsive.css) for a specific tablet treatment;
2831
+ aligned to the 768px mobile/tablet boundary above (was 481px, a near-miss).
2832
+ */
2833
+
2469
2834
  /* --------------------------------------------------------------------
2470
2835
  Mobile Portrait Breakpoint (480px and below)
2471
2836
  ---------------------------------------------------------------------- */
@@ -2540,8 +2905,7 @@
2540
2905
  .ds-247420 .app .row.ds-changelog-row { grid-template-columns: minmax(70px, 10ch) minmax(0, 1fr) !important; }
2541
2906
  .ds-247420 .ds-changelog-row .title { grid-column: 1 / -1; }
2542
2907
 
2543
- /* Buttons */
2544
- .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost {
2908
+ .ds-247420 .btn:not(.btn-sm):not(.btn-lg), .ds-247420 .btn-primary:not(.btn-sm):not(.btn-lg), .ds-247420 .btn-ghost:not(.btn-sm):not(.btn-lg) {
2545
2909
  /* 11px vertical is optical, not scale drift: --space-2-75 (12) would tip
2546
2910
  the intrinsic box past the 44px min-height floor below and make the
2547
2911
  floor dead, changing the mobile button rhythm. */
@@ -2644,7 +3008,7 @@
2644
3008
  /* --------------------------------------------------------------------
2645
3009
  Tablet Landscape Breakpoint (768px to 1024px)
2646
3010
  ---------------------------------------------------------------------- */
2647
- @media (min-width: 481px) and (max-width: 1024px) {
3011
+ @media (min-width: 768px) and (max-width: 1024px) {
2648
3012
  /* App Layout — side handled by 900px drawer block; restore for 901-1024 */
2649
3013
  .ds-247420 .app-side a {
2650
3014
  padding: var(--space-2-75) var(--space-2-75); gap: var(--space-2);
@@ -2707,8 +3071,7 @@
2707
3071
  gap: var(--space-3);
2708
3072
  }
2709
3073
 
2710
- /* Buttons */
2711
- .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost {
3074
+ .ds-247420 .btn:not(.btn-sm):not(.btn-lg), .ds-247420 .btn-primary:not(.btn-sm):not(.btn-lg), .ds-247420 .btn-ghost:not(.btn-sm):not(.btn-lg) {
2712
3075
  /* 18px inline has no tier (--space-3 is 16, --space-3-5 is 20); it is the
2713
3076
  tablet CTA width tuned against the 12px vertical, so snapping changes
2714
3077
  the button aspect on the most visible control in the kit. */
@@ -2870,7 +3233,12 @@
2870
3233
  border-color: var(--rule);
2871
3234
  }
2872
3235
  .ds-247420 .ds-file-row:active { transform: translateY(1px); }
2873
- .ds-247420 .ds-file-row.active { background: var(--accent-tint); border-color: var(--accent); }
3236
+ /* file-grid's own header/caption both claim NEVER A BORDER -- border-color
3237
+ here silently broke that promise for exactly the row it's meant to call
3238
+ out (the row a screenshot/manual test would actually look at). Selected
3239
+ state stays legible via the tint alone; border-color stays transparent so
3240
+ the base rule's promise holds for every row, active or not. */
3241
+ .ds-247420 .ds-file-row.active { background: var(--accent-tint); }
2874
3242
  .ds-247420 .ds-file-row[data-file-type="dir"] { border-left-width: 3px; border-left-color: var(--accent); }
2875
3243
  .ds-247420 .ds-file-row[data-file-type="image"],
2876
3244
  .ds-247420 .ds-file-row[data-file-type="video"],
@@ -3036,6 +3404,12 @@
3036
3404
  display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
3037
3405
  gap: var(--space-3); align-items: start;
3038
3406
  }
3407
+ /* Mobile fallback: auto-fill can still pack 2 cramped columns down to ~320px
3408
+ viewports; force a single column below the shared mobile breakpoint
3409
+ (docs/responsive.md) so thumbnails and file rails always read as a list. */
3410
+ @media (max-width: 480px) {
3411
+ .ds-247420 .ds-file-grid-thumb { grid-template-columns: 1fr; }
3412
+ }
3039
3413
  .ds-247420 .ds-file-cell {
3040
3414
  position: relative;
3041
3415
  background: var(--bg); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
@@ -3205,20 +3579,35 @@
3205
3579
  }
3206
3580
  .ds-247420 .ds-error-banner:hover { background: color-mix(in oklab, var(--warn) 20%, var(--bg)); }
3207
3581
 
3208
- /* Dropzone */
3582
+ /* Dropzone — a tonal target, not a dashed-border affordance (preview/
3583
+ dropzone.html's own header/caption both describe this: "NO DASHED
3584
+ BORDERS" / "tonal panel that swaps to --panel-select on dragover"). A
3585
+ solid hairline plus the idle->dragover background swap is the actual
3586
+ design; dashed was this system's OTHER "drop target" vocabulary
3587
+ (.panel-docs, kits-appended.css) leaking in here by copy-paste. */
3209
3588
  .ds-247420 .ds-dropzone {
3210
- border: var(--bw-rule) dashed var(--rule);
3589
+ border: var(--bw-rule) solid var(--rule);
3211
3590
  border-radius: var(--r-3); background: var(--bg);
3212
3591
  transition: border-color var(--dur-base) var(--ease), background var(--dur-base) var(--ease);
3213
3592
  }
3593
+ /* Idle hover: a quiet pre-drag affordance (border firms up, faint tint) so a
3594
+ pointer resting over the zone reads as "droppable" before a drag even
3595
+ starts — distinct from and weaker than the .dragover treatment below. */
3596
+ .ds-247420 .ds-dropzone:hover {
3597
+ border-color: var(--fg-3);
3598
+ background: var(--bg-2);
3599
+ }
3214
3600
  .ds-247420 .ds-dropzone-inner {
3215
3601
  display: flex; align-items: center; justify-content: center;
3216
3602
  gap: var(--space-2); flex-wrap: wrap;
3217
3603
  padding: var(--space-4) var(--space-3); text-align: center;
3218
3604
  }
3219
3605
  /* Icon size, not type — see .ds-file-cell-media .ds-file-icon above. */
3220
- .ds-247420 .ds-dropzone-glyph { font-size: 22px; color: var(--fg-3); }
3606
+ .ds-247420 .ds-dropzone-glyph { font-size: var(--icon-lg, 24px); color: var(--fg-3); }
3221
3607
  .ds-247420 .ds-dropzone-label { font-size: var(--fs-sm); color: var(--fg-3); }
3608
+ .ds-247420 .ds-dropzone:not(.dragover):not(.rejected):hover {
3609
+ border-color: var(--fg-3);
3610
+ }
3222
3611
  .ds-247420 .ds-dropzone.dragover {
3223
3612
  border-color: var(--accent);
3224
3613
  background: var(--accent-tint);
@@ -3232,6 +3621,26 @@
3232
3621
  0%, 100% { border-color: var(--accent); }
3233
3622
  50% { border-color: color-mix(in oklab, var(--accent) 45%, transparent); }
3234
3623
  }
3624
+ /* Rejected — an in-flight drag carrying a disallowed payload (wrong type,
3625
+ over the size cap). Distinct from dragover: warn tone, not accent, so the
3626
+ two "something is happening" states never read as the same affordance. */
3627
+ .ds-247420 .ds-dropzone.rejected {
3628
+ border-color: var(--warn);
3629
+ background: color-mix(in oklab, var(--warn) 10%, var(--bg));
3630
+ }
3631
+ @media (prefers-reduced-motion: no-preference) {
3632
+ .ds-247420 .ds-dropzone.rejected { animation: ds-dropzone-shake 0.4s var(--ease); }
3633
+ }
3634
+ .ds-247420 .ds-dropzone.rejected .ds-dropzone-glyph,
3635
+ .ds-247420 .ds-dropzone.rejected .ds-dropzone-label { color: var(--warn); }
3636
+ @keyframes ds-dropzone-shake {
3637
+ 0%, 100% { transform: translateX(0); }
3638
+ 25% { transform: translateX(-3px); }
3639
+ 75% { transform: translateX(3px); }
3640
+ }
3641
+ .ds-247420 .ds-dropzone--wrap.rejected > .ds-dropzone-inner {
3642
+ outline-color: var(--warn);
3643
+ }
3235
3644
  /* Wrapper form (has children): no permanent chrome - the content renders as if
3236
3645
  the zone were not there, and the dashed affordance overlays it ONLY while a
3237
3646
  drag is in flight. */
@@ -3339,6 +3748,8 @@
3339
3748
  max(var(--space-3), env(safe-area-inset-bottom))
3340
3749
  max(var(--space-3), env(safe-area-inset-left));
3341
3750
  background: color-mix(in oklab, var(--ink) 55%, transparent);
3751
+ backdrop-filter: blur(2px);
3752
+ -webkit-backdrop-filter: blur(2px);
3342
3753
  }
3343
3754
  @media (prefers-reduced-motion: no-preference) {
3344
3755
  .ds-247420 .ds-modal-backdrop { animation: ds-modal-fade var(--dur-base) var(--ease); }
@@ -3413,10 +3824,17 @@
3413
3824
  floor. --ink on the same fill measures 6.27:1. Same principle as the
3414
3825
  --accent/--accent-ink split the token layer documents: a light saturated fill
3415
3826
  takes the dark text tone. Measured in a live browser. */
3827
+ /* Severity is never color-alone (gd-14, color-blindness/grayscale-print safe):
3828
+ the bold weight + widened letter-spacing is a shape cue that survives a
3829
+ colorless render, matching how .btn-primary.danger differs from every
3830
+ other .btn-primary at a glance even with color stripped out. */
3416
3831
  .ds-247420 .btn-primary.danger {
3417
3832
  background: var(--warn);
3418
3833
  border-color: var(--warn);
3419
3834
  color: var(--ink);
3835
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) color-mix(in oklab, var(--ink) 25%, transparent);
3836
+ font-weight: 700;
3837
+ letter-spacing: 0.01em;
3420
3838
  }
3421
3839
  .ds-247420 .btn-primary.danger:hover { filter: brightness(0.92); border-color: var(--warn); }
3422
3840
 
@@ -3497,7 +3915,7 @@
3497
3915
  .ds-247420 .ds-preview-code::-webkit-scrollbar,
3498
3916
  .ds-247420 .ds-preview-text::-webkit-scrollbar,
3499
3917
  .ds-247420 .ds-preview-body::-webkit-scrollbar,
3500
- .ds-247420 .ds-modal-body::-webkit-scrollbar { width: 10px; height: 10px; }
3918
+ .ds-247420 .ds-modal-body::-webkit-scrollbar { width: 8px; height: 8px; }
3501
3919
  .ds-247420 .ds-preview-code::-webkit-scrollbar-thumb,
3502
3920
  .ds-247420 .ds-preview-text::-webkit-scrollbar-thumb {
3503
3921
  background: color-mix(in oklab, var(--paper) 28%, transparent);
@@ -3570,6 +3988,12 @@
3570
3988
  .ds-247420 .ds-demo-label { font-size: var(--fs-xs); font-weight: 500; margin: 0 0 var(--space-1); }
3571
3989
  .ds-247420 .ds-demo-desc { font-size: var(--fs-tiny); color: var(--fg-3); margin: 0 0 var(--space-2); }
3572
3990
 
3991
+ /* Per-preview backlink to the full token map/doc — names which token
3992
+ families the specimen above it demonstrates, then links out to the
3993
+ generated theme-map for the resolved values. */
3994
+ .ds-247420 .tokens-backlink { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); margin-top: 2px; }
3995
+ .ds-247420 .tokens-backlink a { color: inherit; }
3996
+
3573
3997
  @media (max-width: 768px) {
3574
3998
  .ds-247420 .ds-catalog { flex-direction: column; }
3575
3999
  .ds-247420 .ds-catalog-side { width: 100%; height: auto; position: static; border-right: 0; border-bottom: var(--bw-hair) solid var(--rule); }
@@ -3666,13 +4090,25 @@
3666
4090
  font-family: var(--ff-mono); font-size: var(--fs-xs);
3667
4091
  display: inline-flex; align-items: center; gap: 6px;
3668
4092
  }
3669
- /* CSS-drawn half-disc mark on the compact theme toggle: keeps the control
3670
- legible when its text label is hidden (icon-only rail strip). */
4093
+ /* CSS-drawn disc mark on the compact theme toggle: keeps the control
4094
+ legible when its text label is hidden (icon-only rail strip). Per-mode
4095
+ fill gives the icon itself a distinct look for auto/light/dark, so state
4096
+ reads without relying on the title tooltip alone. */
3671
4097
  .ds-247420 .ds-theme-disc {
3672
4098
  flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%;
3673
4099
  border: var(--bw-hair) solid var(--fg-3);
4100
+ /* auto: half-and-half, matches default (no data-mode) for safety */
4101
+ background: linear-gradient(90deg, var(--fg-3) 0 50%, transparent 50% 100%);
4102
+ }
4103
+ .ds-247420 .ds-theme-disc[data-mode="auto"] {
3674
4104
  background: linear-gradient(90deg, var(--fg-3) 0 50%, transparent 50% 100%);
3675
4105
  }
4106
+ .ds-247420 .ds-theme-disc[data-mode="paper"] {
4107
+ background: var(--bg); border-color: var(--fg-3);
4108
+ }
4109
+ .ds-247420 .ds-theme-disc[data-mode="ink"] {
4110
+ background: var(--fg-3); border-color: var(--fg-3);
4111
+ }
3676
4112
 
3677
4113
  /* ============================================================
3678
4114
  Chat kit polish — styles every class emitted by Chat /
@@ -3735,6 +4171,7 @@
3735
4171
  text-transform: lowercase; letter-spacing: 0.02em; user-select: none;
3736
4172
  transition: transform 0.12s ease, box-shadow 0.12s ease;
3737
4173
  position: relative;
4174
+ box-shadow: inset 0 0 0 1px var(--rule);
3738
4175
  }
3739
4176
  .ds-247420 .chat-msg:hover .chat-avatar { transform: scale(1.03); box-shadow: 0 1px 4px color-mix(in oklab, var(--fg) 6%, transparent); }
3740
4177
  .ds-247420 .chat-msg.you .chat-avatar {
@@ -3745,6 +4182,18 @@
3745
4182
  background: color-mix(in oklab, var(--mascot) 26%, var(--bg-2));
3746
4183
  color: var(--mascot-deep);
3747
4184
  }
4185
+ /* Plain assistant turns ('them', no aicat persona) previously fell through to
4186
+ the bare .chat-avatar gray disc with no role identity at all — 'you' and
4187
+ 'aicat' both got a tinted background + ring, the default assistant got
4188
+ nothing. Give it the same tinted-background treatment via --color-success
4189
+ (the conventional assistant/bot association, distinct from the accent hue
4190
+ 'you' owns and the mascot hue 'aicat' owns) plus a thin identity ring so
4191
+ role is readable from the avatar alone, not just bubble position/color. */
4192
+ .ds-247420 .chat-msg.them:not(.aicat) .chat-avatar {
4193
+ background: color-mix(in oklab, var(--color-success) 20%, var(--bg-3));
4194
+ color: color-mix(in oklab, var(--color-success) 65%, var(--fg));
4195
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-success) 35%, transparent);
4196
+ }
3748
4197
 
3749
4198
  .ds-247420 .chat-stack {
3750
4199
  display: flex; flex-direction: column; gap: var(--space-1);
@@ -3764,6 +4213,13 @@
3764
4213
  min-width: 0;
3765
4214
  transition: transform 0.12s ease, box-shadow 0.12s ease;
3766
4215
  }
4216
+ /* Tail corner: the bubble edge nearest its own avatar (bottom-left for
4217
+ 'them', bottom-right for 'you') squares off slightly, matching bubble
4218
+ position to the sender's avatar the way a speech-bubble tail would — a
4219
+ consistent radius on all four corners reads as a generic card, not a
4220
+ turn in a conversation. */
4221
+ .ds-247420 .chat-msg:not(.you) .chat-bubble { border-bottom-left-radius: var(--r-0); }
4222
+ .ds-247420 .chat-msg.you .chat-bubble { border-bottom-right-radius: var(--r-0); }
3767
4223
  .ds-247420 .chat-msg:hover .chat-bubble { transform: translateY(-1px); box-shadow: 0 2px 8px color-mix(in oklab, var(--fg) 6%, transparent); transition: transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease); }
3768
4224
  /* Own bubble — calm tinted lead, not a full electric fill (chat stays readable). */
3769
4225
  .ds-247420 .chat-msg.you .chat-bubble { background: var(--accent-tint); color: var(--fg); }
@@ -4048,9 +4504,27 @@
4048
4504
  .ds-247420 .chat-reactions .rxn .n { font-family: var(--ff-mono); font-weight: 600; font-size: var(--fs-nano); color: var(--fg-2); }
4049
4505
  .ds-247420 .chat-reactions .rxn.you .n { color: var(--accent-ink); }
4050
4506
 
4507
+ /* Typing indicator renders through the same .chat-bubble container as a real
4508
+ message, but must never read as a bubble holding actual content — no
4509
+ border, no hover-lift shadow, no tail-corner cut; just a pill-shaped
4510
+ borderless dot cluster, visually lighter than any card/bubble on the page. */
4511
+ /* Double-class selector (.chat-bubble.chat-bubble-typing) deliberately, not
4512
+ bare .chat-bubble-typing: chat.css (loaded after app-shell.css on every
4513
+ kit that links both) still carries a plain `.chat-bubble { background:
4514
+ var(--bg-2) }` rule at the same (0,1,0) specificity, and cascade order
4515
+ would let that later rule win over an equal-specificity selector here. */
4516
+ .ds-247420 .chat-bubble.chat-bubble-typing {
4517
+ background: transparent;
4518
+ padding: var(--space-2) var(--space-1);
4519
+ border-radius: var(--r-pill);
4520
+ }
4521
+ .ds-247420 .chat-msg:hover .chat-bubble.chat-bubble-typing { transform: none; box-shadow: none; }
4522
+ .ds-247420 .chat-msg:not(.you) .chat-bubble.chat-bubble-typing,
4523
+ .ds-247420 .chat-msg.you .chat-bubble.chat-bubble-typing { border-bottom-left-radius: var(--r-pill); border-bottom-right-radius: var(--r-pill); }
4524
+
4051
4525
  .ds-247420 .chat-typing { display: inline-flex; gap: var(--space-1); align-items: center; }
4052
4526
  .ds-247420 .chat-typing span {
4053
- width: 6px; height: 6px; border-radius: 50%; background: var(--fg-3);
4527
+ width: 8px; height: 8px; border-radius: 50%; background: var(--fg-3);
4054
4528
  }
4055
4529
 
4056
4530
  @media (prefers-reduced-motion: no-preference) {
@@ -4172,8 +4646,18 @@
4172
4646
 
4173
4647
  .ds-247420 .aicat-portrait { display: inline-flex; align-items: center; gap: var(--space-2-5); padding: var(--space-1) 0; }
4174
4648
  .ds-247420 .aicat-face {
4175
- /* 24px is the ICON size inside a 32px round chip — see .chat-file .glyph. */
4176
- width: 32px; height: 32px; font-size: 24px;
4649
+ /* The face is 3 lines of ascii art ( /\_/\ , ( o.o ) , > ^ < ), not a
4650
+ single icon glyph -- the previous 24px/line-height:normal sizing (sized
4651
+ like .chat-file .glyph's single-char icon) ran to ~3 line-heights tall
4652
+ and clipped the ear/whisker rows against this box's fixed 32px height
4653
+ + overflow:hidden. pico (10px) + line-height:1 fits all 3 lines in 30px.
4654
+ padding:0 overrides the global `pre { padding: var(--space-5) }` (32px)
4655
+ -- box-sizing:border-box + that padding alone exceeded the declared
4656
+ 32x32 box, so the box never actually enforced 32x32 and rendered at its
4657
+ forced min-content size instead, clipping the art regardless of font
4658
+ size. */
4659
+ width: 32px; height: 32px; font-size: var(--fs-pico); line-height: 1;
4660
+ padding: 0;
4177
4661
  display: inline-flex; align-items: center; justify-content: center;
4178
4662
  background: color-mix(in oklab, var(--mascot) 22%, var(--bg-2));
4179
4663
  border-radius: 50%; flex-shrink: 0;
@@ -4191,10 +4675,182 @@
4191
4675
  fell back to the paper tone at 3.13:1 on ink. */
4192
4676
  .ds-247420 .aicat-meta .name { color: var(--mascot-deep, var(--mascot)); font-weight: 600; }
4193
4677
 
4678
+ /* ============================================================
4679
+ Composer attachment-detected badge, context line, hint, toolbar,
4680
+ note strip, elapsed counter, dragover state — all referenced from
4681
+ composer.js / composer-affordances.js but previously had no CSS at
4682
+ all (unstyled DOM nodes / plain text falling through to block
4683
+ defaults).
4684
+ ============================================================ */
4685
+ .ds-247420 .chat-composer-context {
4686
+ display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1);
4687
+ width: 100%; order: -1;
4688
+ padding: var(--space-1) var(--space-1) 0;
4689
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3);
4690
+ letter-spacing: 0.01em;
4691
+ }
4692
+ .ds-247420 button.chat-composer-context {
4693
+ background: transparent; border: 0; cursor: pointer; text-align: left;
4694
+ border-radius: var(--r-0);
4695
+ }
4696
+ .ds-247420 button.chat-composer-context:hover { color: var(--fg-2); }
4697
+ .ds-247420 button.chat-composer-context:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4698
+ .ds-247420 .chat-composer-context-sep { color: var(--fg-3); opacity: 0.6; }
4699
+ .ds-247420 .chat-composer-context-bit {
4700
+ background: transparent; border: 0; padding: 0; margin: 0; cursor: pointer;
4701
+ font: inherit; color: var(--fg-3); border-radius: var(--r-0);
4702
+ transition: color var(--dur-snap, .15s) var(--ease, ease);
4703
+ }
4704
+ .ds-247420 .chat-composer-context-bit:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
4705
+ .ds-247420 .chat-composer-context-bit:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4706
+ .ds-247420 .chat-composer-context-text { color: var(--fg-3); }
4707
+
4708
+ .ds-247420 .chat-composer-detected-badge {
4709
+ display: flex; align-items: center; gap: var(--space-2);
4710
+ width: 100%; order: -1;
4711
+ padding: var(--space-1) var(--space-2-5);
4712
+ margin: 0 0 var(--space-1);
4713
+ background: color-mix(in oklab, var(--accent) 12%, var(--bg-2));
4714
+ border: 1px solid color-mix(in oklab, var(--accent) 28%, transparent);
4715
+ border-radius: var(--r-1);
4716
+ font-size: var(--fs-xs); color: var(--fg);
4717
+ }
4718
+ .ds-247420 .chat-composer-detected-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4719
+ .ds-247420 .chat-composer-detected-dismiss {
4720
+ flex-shrink: 0; width: 20px; height: 20px;
4721
+ display: inline-flex; align-items: center; justify-content: center;
4722
+ background: transparent; border: 0; cursor: pointer; border-radius: 50%;
4723
+ color: var(--fg-3); font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1;
4724
+ transition: background var(--dur-snap, .15s) var(--ease, ease), color var(--dur-snap, .15s) var(--ease, ease);
4725
+ }
4726
+ .ds-247420 .chat-composer-detected-dismiss:hover { background: color-mix(in oklab, var(--fg) 10%, transparent); color: var(--fg); }
4727
+ .ds-247420 .chat-composer-detected-dismiss:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4728
+
4729
+ .ds-247420 .chat-composer-hint {
4730
+ width: 100%; order: 2;
4731
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3);
4732
+ padding: 0 var(--space-1);
4733
+ }
4734
+
4735
+ .ds-247420 .chat-composer-toolbar {
4736
+ display: flex; align-items: center; gap: var(--space-1);
4737
+ flex-shrink: 0;
4738
+ }
4739
+ .ds-247420 .chat-composer-toolbar .composer-btn {
4740
+ width: 32px; height: 32px; border-radius: var(--r-1);
4741
+ }
4742
+ @media (pointer: coarse) {
4743
+ .ds-247420 .chat-composer-toolbar .composer-btn { width: 44px; height: 44px; }
4744
+ }
4745
+
4746
+ .ds-247420 .chat-composer-note {
4747
+ width: 100%; order: 3;
4748
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3);
4749
+ padding: var(--space-1) var(--space-1) 0;
4750
+ }
4751
+
4752
+ .ds-247420 .chat-composer-elapsed {
4753
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3);
4754
+ flex-shrink: 0; padding: 0 var(--space-1);
4755
+ }
4756
+
4757
+ .ds-247420 .chat-composer.dragover {
4758
+ border-color: var(--accent);
4759
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 18%, transparent);
4760
+ background: color-mix(in oklab, var(--accent) 4%, var(--bg));
4761
+ }
4762
+
4763
+ /* ============================================================
4764
+ EmojiPicker (ov-emoji-*) — an anchored popover with search input,
4765
+ category tabs, and an emoji grid; previously entirely unstyled.
4766
+ Search input mirrors .ds-search-input's tonal-fill treatment so it
4767
+ reads consistently with the rest of the system's search fields.
4768
+ ============================================================ */
4769
+ .ds-247420 .ov-emoji-root {
4770
+ position: fixed;
4771
+ display: flex; flex-direction: column; gap: var(--space-2);
4772
+ width: 280px; max-height: 320px;
4773
+ padding: var(--space-2-5);
4774
+ background: var(--bg);
4775
+ border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
4776
+ border-radius: var(--r-2);
4777
+ box-shadow: var(--shadow-overlay, var(--shadow-3));
4778
+ z-index: var(--z-dropdown, var(--z-raised));
4779
+ }
4780
+ .ds-247420 .ov-emoji-search {
4781
+ width: 100%; box-sizing: border-box;
4782
+ font: inherit; font-size: var(--fs-sm); color: var(--fg);
4783
+ background: var(--bg-2); border: 0;
4784
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule);
4785
+ border-radius: var(--r-1);
4786
+ height: var(--ctl-sm, 32px);
4787
+ padding: 0 var(--space-2-5);
4788
+ transition: box-shadow var(--dur-snap, .15s) var(--ease, ease);
4789
+ }
4790
+ .ds-247420 .ov-emoji-search::placeholder { color: var(--fg-3); }
4791
+ .ds-247420 .ov-emoji-search:focus-visible,
4792
+ .ds-247420 .ov-emoji-search:focus {
4793
+ outline: none;
4794
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 18%, transparent);
4795
+ }
4796
+ .ds-247420 .ov-emoji-tabs {
4797
+ display: flex; gap: var(--space-1);
4798
+ border-bottom: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
4799
+ padding-bottom: var(--space-1);
4800
+ }
4801
+ .ds-247420 .ov-emoji-tab {
4802
+ flex: 1;
4803
+ display: inline-flex; align-items: center; justify-content: center;
4804
+ height: 30px; font-size: var(--fs-sm);
4805
+ background: transparent; border: 0; border-radius: var(--r-1);
4806
+ cursor: pointer;
4807
+ transition: background var(--dur-snap, .15s) var(--ease, ease);
4808
+ }
4809
+ .ds-247420 .ov-emoji-tab:hover { background: color-mix(in oklab, var(--fg) 8%, transparent); }
4810
+ .ds-247420 .ov-emoji-tab[aria-selected="true"] { background: color-mix(in oklab, var(--accent) 16%, transparent); }
4811
+ .ds-247420 .ov-emoji-tab:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4812
+ .ds-247420 .ov-emoji-grid { overflow-y: auto; flex: 1; min-height: 0; scrollbar-width: thin; scrollbar-color: var(--bg-3) transparent; }
4813
+ .ds-247420 .ov-emoji-grid-inner {
4814
+ display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--space-1);
4815
+ }
4816
+ .ds-247420 .ov-emoji-cell {
4817
+ display: inline-flex; align-items: center; justify-content: center;
4818
+ aspect-ratio: 1; font-size: var(--fs-lg); line-height: 1;
4819
+ background: transparent; border: 0; border-radius: var(--r-1);
4820
+ cursor: pointer;
4821
+ transition: background var(--dur-snap, .15s) var(--ease, ease), transform 0.12s ease;
4822
+ }
4823
+ .ds-247420 .ov-emoji-cell:hover { background: color-mix(in oklab, var(--fg) 10%, transparent); transform: scale(1.12); }
4824
+ .ds-247420 .ov-emoji-cell:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; background: color-mix(in oklab, var(--fg) 10%, transparent); }
4825
+ .ds-247420 .ov-emoji-empty {
4826
+ grid-column: 1 / -1;
4827
+ text-align: center; color: var(--fg-3); font-size: var(--fs-xs);
4828
+ padding: var(--space-3) 0;
4829
+ }
4830
+ @media (prefers-reduced-motion: reduce) {
4831
+ .ds-247420 .ov-emoji-cell { transition: background var(--dur-snap, .15s) var(--ease, ease); }
4832
+ .ds-247420 .ov-emoji-cell:hover { transform: none; }
4833
+ }
4834
+
4194
4835
  /* ============================================================
4195
4836
  Sidebar polish: hide zero-count badges; tonal active state;
4196
4837
  monospace count chips.
4197
4838
  ============================================================ */
4839
+ /* Rail row rhythm: the sidebar rows (bins/labels) previously packed at
4840
+ --space-hair (2px) margin-bottom while the main content column reads at
4841
+ the --space-5/--space-3 outer/inner rhythm (AGENTS.md "panel rhythm").
4842
+ Bumping the inter-row gap to --space-1-5 (5px) — still a tight, dense-app
4843
+ value, but enough that adjacent rows no longer read as glued together —
4844
+ and making it density-aware via [data-density] mirrors the existing
4845
+ compact-density override pattern in files.css (.ds-file-grid[data-density]).
4846
+ [data-density="compact"] keeps the old tighter feel for long rail lists;
4847
+ [data-density="spacious"] opens it further. */
4848
+ .ds-247420 .app-side a { margin-bottom: var(--space-1-5); }
4849
+ .ds-247420 .app-side[data-density="compact"] a,
4850
+ .ds-247420 .app-side-shell[data-density="compact"] .app-side a { margin-bottom: var(--space-hair); }
4851
+ .ds-247420 .app-side[data-density="spacious"] a,
4852
+ .ds-247420 .app-side-shell[data-density="spacious"] .app-side a { margin-bottom: var(--space-2); }
4853
+
4198
4854
  .ds-247420 .app-side .count {
4199
4855
  padding: 1px 8px; font-size: var(--fs-micro); font-weight: 700;
4200
4856
  font-family: var(--ff-mono); letter-spacing: 0.04em;
@@ -4298,12 +4954,32 @@
4298
4954
  :disabled pseudo-class, so a disabled link-styled button relies entirely
4299
4955
  on this class selector for its visual treatment (aria-disabled + tabindex
4300
4956
  are set in JS; this is the paired CSS half). */
4957
+ /* opacity:0.6 alone barely separated from an active .btn-primary against a
4958
+ busy background -- close enough in dark mode that users clicked disabled
4959
+ buttons expecting them to work. grayscale flattens any color signal (no
4960
+ more "still looks clickable-green") and the steeper opacity drop reads as
4961
+ unmistakably inert at a glance, not just slightly dimmed. */
4301
4962
  .ds-247420 .btn:disabled, .ds-247420 .btn-primary:disabled, .ds-247420 .btn-ghost:disabled, .ds-247420 .btn.is-disabled, .ds-247420 .btn-primary.is-disabled, .ds-247420 .btn-ghost.is-disabled, .ds-247420 button:disabled, .ds-247420[disabled] {
4302
- opacity: 0.6;
4963
+ opacity: 0.5;
4964
+ filter: grayscale(60%);
4303
4965
  cursor: not-allowed;
4304
4966
  pointer-events: none;
4305
4967
  }
4306
4968
 
4969
+ /* Systemic hover fallback — button-like elements with no design-system class
4970
+ (a bare <button>, [role="button"], or a generic .row/.chip usage) get a
4971
+ `transition` declared above (line ~106) but nothing to transition to, so
4972
+ they look inert until clicked. This is a low-specificity (0,0,1)/(0,1,1)
4973
+ catch-all: any element that also carries `.btn`/`.btn-primary`/`.btn-ghost`/
4974
+ `.ds-icon-btn`/etc. keeps its own component `:hover` (those selectors are
4975
+ equal-or-higher specificity and, for the shared ones, declared later in
4976
+ the cascade), so this never overrides a deliberate per-component hover —
4977
+ it only fills the gap for elements that don't have one. */
4978
+ .ds-247420 button:not(:disabled):hover,
4979
+ .ds-247420[role="button"]:not([aria-disabled="true"]):hover {
4980
+ background-color: var(--panel-hover, var(--bg-3));
4981
+ }
4982
+
4307
4983
  /* Loading state — spinning indicator */
4308
4984
  @keyframes spinner-rotate {
4309
4985
  from { transform: rotate(0deg); }
@@ -4311,10 +4987,12 @@
4311
4987
  }
4312
4988
  /* Reusable spin utility: rotates the element (e.g. a refresh Icon used as a
4313
4989
  busy indicator) via the shared spinner-rotate keyframe. Respects reduced
4314
- motion. Use instead of a decorative rotation glyph. */
4990
+ motion. Use instead of a decorative rotation glyph.
4991
+ Ring spinner — use for larger blocking/full-region loading states. */
4315
4992
  .ds-247420 .ds-spin { animation: spinner-rotate 0.8s linear infinite; transform-origin: center; }
4316
4993
  @media (prefers-reduced-motion: reduce) { .ds-247420 .ds-spin { animation: none; } }
4317
4994
 
4995
+ /* Ring spinner — use for larger blocking/full-region loading states. */
4318
4996
  .ds-247420 .loading { position: relative; }
4319
4997
  .ds-247420 .loading::after {
4320
4998
  content: '';
@@ -4496,6 +5174,7 @@
4496
5174
  }
4497
5175
  .ds-247420 .ds-context-menu-item.danger {
4498
5176
  color: var(--warn);
5177
+ box-shadow: inset 2px 0 0 var(--warn);
4499
5178
  }
4500
5179
  .ds-247420 .ds-context-menu-item.danger:hover {
4501
5180
  background: color-mix(in oklab, var(--warn) 12%, var(--bg));
@@ -4598,6 +5277,7 @@
4598
5277
  .ds-247420 .prose p { margin: 0 0 var(--space-4) 0; }
4599
5278
  .ds-247420 .prose p:last-child { margin-bottom: 0; }
4600
5279
  .ds-247420 .prose a { color: var(--accent-ink); text-decoration-color: var(--rule-strong); text-underline-offset: 0.15em; }
5280
+ .ds-247420 .prose a:visited { color: color-mix(in oklab, var(--accent-ink) 70%, var(--purple-2, var(--fg-3))); }
4601
5281
  .ds-247420 .prose a:hover { text-decoration-color: currentColor; }
4602
5282
 
4603
5283
  /* Rubber-stamp motif — a bordered, uppercase, letter-spaced badge with a
@@ -4614,7 +5294,13 @@
4614
5294
  transform: rotate(-2deg);
4615
5295
  white-space: nowrap;
4616
5296
  }
4617
- .ds-247420 .stamp.ink { background: var(--ink); border-color: var(--ink); color: var(--paper); }
5297
+ /* border-color previously matched background exactly (var(--ink) on both) --
5298
+ same class of bug as the colors-core "paper" swatch: a border drawn in the
5299
+ identical color as its own fill is invisible, so the ink-toned stamp was
5300
+ the one variant on the page missing its box outline. --paper reads as a
5301
+ visible ring against the ink fill (paper-on-ink is this token pair's
5302
+ whole reason to exist), matching every other .stamp variant's contrast. */
5303
+ .ds-247420 .stamp.ink { background: var(--ink); border-color: var(--paper); color: var(--paper); }
4618
5304
  .ds-247420 .stamp.acid { background: var(--accent); border-color: var(--accent-ink); color: var(--accent-ink); }
4619
5305
  .ds-247420 .stamp.purple { border-color: var(--purple-2); color: var(--purple-2); }
4620
5306
  .ds-247420 .stamp.mascot { border-color: var(--mascot); color: var(--mascot-deep); }
@@ -4629,6 +5315,11 @@
4629
5315
  border: 2px solid var(--fg);
4630
5316
  border-radius: var(--r-hair);
4631
5317
  background: transparent; color: var(--fg);
5318
+ /* .stamp's own caption claims the rubber-stamp motif is "always rotated
5319
+ slightly" -- .btn-stamp is the same visual face as an interactive
5320
+ <button> (preview/stamps-lore.html's second row) and had no rotation at
5321
+ all, breaking that claim for every stamp in that row. */
5322
+ transform: rotate(-2deg);
4632
5323
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
4633
5324
  }
4634
5325
  .ds-247420 .btn-stamp:hover { background: var(--panel-hover); }
@@ -4645,12 +5336,25 @@
4645
5336
  width: 100%; height: 0; border: 0;
4646
5337
  }
4647
5338
  .ds-247420 hr.rule { border-top: 1px solid var(--rule); }
4648
- .ds-247420 hr.rule-double { border-top: 4px double var(--rule-strong); }
4649
- .ds-247420 hr.rule-dotted { border-top: 1px dotted var(--rule-strong); }
4650
-
4651
- /* Hairline-only text input — no box, no fill. A single bottom rule that
4652
- thickens to 2px on focus, matching the row/rule zero-border aesthetic
4653
- instead of a browser-default boxed field. */
5339
+ .ds-247420 hr.rule-double {
5340
+ height: 3px;
5341
+ border-top: 1px solid var(--rule-strong);
5342
+ border-bottom: 1px solid var(--rule-strong);
5343
+ }
5344
+ /* 1px was indistinguishable from .rule -- border-style:dotted needs enough
5345
+ width to actually draw a gap between dots; below ~2px (esp. at DPRs where
5346
+ 1px resolves to a sub-pixel value like 0.8px) Chrome collapses it to a
5347
+ solid line, which is exactly what preview/rules.html's report caught live. */
5348
+ .ds-247420 hr.rule-dotted { border-top: 2px dotted var(--rule-strong); }
5349
+
5350
+ /* Hairline-only text input — no full box, no permanent fill. A single bottom
5351
+ rule that thickens to 2px on focus, matching the row/rule zero-border
5352
+ aesthetic instead of a browser-default boxed field. A completely flat,
5353
+ static hairline reads as a static label rather than an editable control
5354
+ at a glance (no cue at rest, nothing on hover) -- the very faint permanent
5355
+ wash plus a stronger hover tint below signal "this is a field" without
5356
+ reverting to the boxed/filled chrome the hairline design deliberately
5357
+ moved away from. */
4654
5358
  .ds-247420 .input {
4655
5359
  display: block; width: 100%; box-sizing: border-box;
4656
5360
  /* appearance:none alone is not enough: with color-scheme:dark set on the
@@ -4664,11 +5368,13 @@
4664
5368
  color-scheme: normal;
4665
5369
  border: 0; border-bottom: 1px solid var(--rule-strong);
4666
5370
  border-radius: var(--r-0);
4667
- background: transparent;
5371
+ background: color-mix(in oklab, var(--fg) 2.5%, transparent);
4668
5372
  box-shadow: none;
4669
5373
  padding: var(--space-2) var(--space-1);
4670
5374
  font: inherit; color: var(--fg);
5375
+ transition: background var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease);
4671
5376
  }
5377
+ .ds-247420 .input:hover { background: color-mix(in oklab, var(--fg) 5%, transparent); }
4672
5378
  /* Touch floor for the BASE input. Several specific inputs already carried this
4673
5379
  (ds-file-filter-input, ds-modal-input, ds-term-input) but the primitive every
4674
5380
  form actually uses did not, so a plain field measured 34px under a coarse
@@ -4686,6 +5392,15 @@
4686
5392
  border-bottom-color: var(--accent-ink);
4687
5393
  }
4688
5394
 
5395
+ /* Switch primitive (form-primitives.js Toggle) -- track + sliding knob.
5396
+ Canonical definition lives in editor-primitives.css alongside the rest of
5397
+ the .ds-check/.ds-radio/.ds-toggle form-primitive family (that file loads
5398
+ AFTER this one in the build.mjs concat order, so a second .ds-toggle block
5399
+ here was dead weight that silently lost the cascade while still disagreeing
5400
+ on size/colors/knob-offset with the surviving rule — a duplicate-source-of-
5401
+ -truth foot-gun, not an intentional override). Removed; see
5402
+ editor-primitives.css's ".ds-toggle" block for track/knob/disabled/focus. */
5403
+
4689
5404
  /* -- Field controls: themed base for TextField / Select / SearchInput --
4690
5405
  Root fix: previously only `transition` was set, so themed apps got
4691
5406
  browser-default white boxes in dark mode and labels collided with inputs
@@ -4773,6 +5488,15 @@
4773
5488
  button) - transparent to any flex/grid layout the bare input previously
4774
5489
  sat in directly. */
4775
5490
  .ds-247420 .ds-search-input-wrap { display: contents; }
5491
+ /* Leading-icon slot, matching .app-search's icon placement in Topbar so the
5492
+ two search surfaces read as one control family instead of icon-vs-no-icon. */
5493
+ .ds-247420 .ds-search-field { position: relative; display: inline-flex; align-items: center; min-width: 0; }
5494
+ .ds-247420 .ds-search-field .ds-search-icon {
5495
+ position: absolute; left: var(--space-3);
5496
+ display: inline-flex; pointer-events: none;
5497
+ color: var(--fg-3); opacity: 0.6;
5498
+ }
5499
+ .ds-247420 .ds-search-field .ds-search-input { padding-left: calc(var(--space-3) + 15px + var(--space-2)); }
4776
5500
 
4777
5501
  /* Visible clear (X) button for SearchInput - mirrors .ds-alert-dismiss's
4778
5502
  ghost-icon treatment but sized to the kit's 44px touch-target floor
@@ -4794,7 +5518,7 @@
4794
5518
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
4795
5519
  }
4796
5520
  .ds-247420 .ds-search-clear:hover { background: var(--bg-2); color: var(--fg); }
4797
- .ds-247420 .ds-search-clear:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
5521
+ .ds-247420 .ds-search-clear:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4798
5522
 
4799
5523
  .ds-247420 .ds-field input:not(.input):focus-visible,
4800
5524
  .ds-247420 .ds-field textarea:not(.input):focus-visible,
@@ -4809,12 +5533,12 @@
4809
5533
  .ds-247420 .ds-field input:not(.input):disabled,
4810
5534
  .ds-247420 .ds-field textarea:not(.input):disabled,
4811
5535
  .ds-247420 .ds-field .ds-select:disabled {
4812
- opacity: .55;
5536
+ opacity: 0.5;
4813
5537
  cursor: not-allowed;
4814
5538
  }
4815
5539
  .ds-247420 .ds-field input:not(.input)[aria-invalid="true"],
4816
5540
  .ds-247420 .ds-field textarea:not(.input)[aria-invalid="true"] {
4817
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--flame);
5541
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--danger);
4818
5542
  }
4819
5543
 
4820
5544
  /* Clear button for text inputs */
@@ -5021,18 +5745,61 @@
5021
5745
  }
5022
5746
  }
5023
5747
 
5748
+ /* Stacking host for the imperative toast()/<Toast> component
5749
+ (src/components/editor-primitives/toast.js). One fixed-position column;
5750
+ individual toasts are appended/removed as siblings inside it rather than
5751
+ each toast fixed-positioning itself, so more than one toast in flight
5752
+ stacks instead of overlapping at the same bottom-right point. */
5753
+ .ds-247420 .ds-ep-toast-host {
5754
+ position: fixed; z-index: var(--z-toast);
5755
+ bottom: 24px; right: 24px;
5756
+ display: flex; flex-direction: column-reverse; gap: var(--space-2);
5757
+ pointer-events: none;
5758
+ }
5759
+ .ds-247420 .ds-ep-toast {
5760
+ pointer-events: auto;
5761
+ display: flex; align-items: center; gap: var(--space-3);
5762
+ background: var(--bg-2); color: var(--fg);
5763
+ border: 1px solid var(--rule);
5764
+ border-radius: var(--r-2);
5765
+ padding: var(--space-3) var(--space-4);
5766
+ max-width: 320px;
5767
+ box-shadow: var(--shadow-overlay);
5768
+ font-size: var(--fs-sm);
5769
+ }
5770
+ @media (prefers-reduced-motion: no-preference) {
5771
+ .ds-247420 .ds-ep-toast { animation: toast-slide-in 0.3s ease-out; }
5772
+ .ds-247420 .ds-ep-toast.leaving { animation: toast-slide-in 0.2s ease-in reverse; }
5773
+ }
5774
+ .ds-247420 .ds-ep-toast.kind-error { border-color: var(--danger, var(--warn)); }
5775
+ .ds-247420 .ds-ep-toast.kind-success { border-color: var(--green-2); }
5776
+ .ds-247420 .ds-ep-toast.kind-warning { border-color: var(--amber); }
5777
+ .ds-247420 .ds-ep-toast-msg { flex: 1; }
5778
+ .ds-247420 .ds-ep-toast-action {
5779
+ flex-shrink: 0;
5780
+ background: transparent; border: 0; color: var(--accent-ink);
5781
+ font: inherit; font-weight: 600; cursor: pointer; padding: 0;
5782
+ }
5783
+ .ds-247420 .ds-ep-toast-action:hover { text-decoration: underline; }
5784
+
5785
+ @media (max-width: 480px) {
5786
+ .ds-247420 .ds-ep-toast-host { bottom: 16px; right: 16px; left: 16px; }
5787
+ .ds-247420 .ds-ep-toast { max-width: none; }
5788
+ }
5789
+
5024
5790
  /* ============================================================
5025
5791
  Spinner — animated loading indicator
5026
5792
  ============================================================ */
5793
+ /* Dots spinner — use for small inline/compact loading states. */
5027
5794
  .ds-247420 .ds-spinner {
5028
5795
  display: inline-flex;
5029
- gap: 4px;
5796
+ gap: var(--space-1);
5030
5797
  align-items: center;
5031
5798
  height: 16px;
5032
5799
  }
5033
5800
  .ds-247420 .ds-spinner span {
5034
- width: 4px;
5035
- height: 4px;
5801
+ width: var(--space-1);
5802
+ height: var(--space-1);
5036
5803
  border-radius: 50%;
5037
5804
  background: var(--accent);
5038
5805
  }
@@ -5043,23 +5810,28 @@
5043
5810
  .ds-247420 .ds-spinner span:nth-child(3) { animation-delay: 0.36s; }
5044
5811
  }
5045
5812
 
5046
- .ds-247420 .ds-spinner-lg { height: 24px; gap: 6px; }
5047
- .ds-247420 .ds-spinner-lg span { width: 6px; height: 6px; }
5813
+ .ds-247420 .ds-spinner-lg { height: 24px; gap: var(--space-1-75); }
5814
+ .ds-247420 .ds-spinner-lg span { width: var(--space-1-75); height: var(--space-1-75); }
5048
5815
 
5049
- .ds-247420 .ds-spinner-sm { height: 12px; gap: 2px; }
5050
- .ds-247420 .ds-spinner-sm span { width: 2px; height: 2px; }
5816
+ .ds-247420 .ds-spinner-sm { height: var(--space-2-5); gap: var(--space-hair); }
5817
+ .ds-247420 .ds-spinner-sm span { width: var(--space-hair); height: var(--space-hair); }
5051
5818
 
5052
- .ds-247420 .ds-spinner-xs { height: 8px; gap: 1px; }
5819
+ .ds-247420 .ds-spinner-xs { height: var(--space-2); gap: 1px; }
5053
5820
  .ds-247420 .ds-spinner-xs span { width: 1.5px; height: 1.5px; }
5054
5821
 
5055
- .ds-247420 .ds-spinner-xl { height: 32px; gap: 8px; }
5056
- .ds-247420 .ds-spinner-xl span { width: 8px; height: 8px; }
5822
+ .ds-247420 .ds-spinner-xl { height: var(--space-5); gap: var(--space-2); }
5823
+ .ds-247420 .ds-spinner-xl span { width: var(--space-2); height: var(--space-2); }
5057
5824
 
5058
5825
  @keyframes ds-bounce {
5059
5826
  0%, 80%, 100% { transform: translateY(0); }
5060
5827
  40% { transform: translateY(-8px); }
5061
5828
  }
5062
5829
 
5830
+ /* Two spinner visual languages coexist intentionally: dots (.ds-spinner*)
5831
+ are for small inline/compact loading states (buttons, chips, list rows),
5832
+ the ring (.loading::after in states-interactions.css) is for larger
5833
+ blocking/full-region loading states (panels, full views). */
5834
+
5063
5835
  /* ============================================================
5064
5836
  Skeleton — animated loading placeholder
5065
5837
  ============================================================ */
@@ -5071,9 +5843,11 @@
5071
5843
  .ds-247420 .ds-skeleton {
5072
5844
  background: linear-gradient(90deg, var(--bg-2) 25%, var(--bg-3) 50%, var(--bg-2) 75%);
5073
5845
  background-size: 200% 100%;
5074
- animation: ds-shimmer 1.5s infinite;
5075
5846
  border-radius: var(--r-1);
5076
5847
  }
5848
+ @media (prefers-reduced-motion: no-preference) {
5849
+ .ds-247420 .ds-skeleton { animation: ds-shimmer 1.5s infinite; }
5850
+ }
5077
5851
 
5078
5852
  @keyframes ds-shimmer {
5079
5853
  0% { background-position: 200% 0; }
@@ -5103,7 +5877,7 @@
5103
5877
  flex-shrink: 0;
5104
5878
  color: var(--alert-tone);
5105
5879
  font-weight: 600;
5106
- min-width: 20px;
5880
+ min-width: var(--icon-md);
5107
5881
  text-align: center;
5108
5882
  }
5109
5883
 
@@ -5132,13 +5906,14 @@
5132
5906
  cursor: pointer;
5133
5907
  color: var(--fg-3);
5134
5908
  padding: 0;
5135
- font-size: 14px;
5909
+ font-size: var(--fs-xs);
5136
5910
  line-height: 1;
5137
- min-width: 20px;
5911
+ min-width: var(--icon-md);
5138
5912
  text-align: center;
5139
5913
  transition: color var(--dur-snap) var(--ease);
5140
5914
  }
5141
5915
  .ds-247420 .ds-alert-dismiss:hover { color: var(--fg); }
5916
+ .ds-247420 .ds-alert-dismiss:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
5142
5917
 
5143
5918
  /* ============================================================
5144
5919
  Responsive Perfection — Drawer rail, ultrawide cap, mobile compact
@@ -5342,10 +6117,26 @@
5342
6117
  /* Column resize handles: a thin keyboard-accessible separator pinned to the
5343
6118
  right edge of each chrome track; drag (or ArrowLeft/Right) writes a clamped
5344
6119
  inline --ws-*-w. */
5345
- .ds-247420 .ws-resizer { grid-row: 1 / -1; align-self: stretch; justify-self: end; width: 8px; margin-right: -4px; z-index: var(--z-raised); cursor: col-resize; background: transparent; border: none; padding: 0; touch-action: none; }
5346
- .ds-247420 .ws-resizer::after { content: ""; display: block; width: 2px; height: 100%; margin: 0 auto; background: transparent; transition: background var(--dur-snap) var(--ease); }
5347
- .ds-247420 .ws-resizer:hover::after, .ds-247420 .ws-resizer:focus-visible::after { background: var(--accent); }
6120
+ .ds-247420 .ws-resizer { position: relative; grid-row: 1 / -1; align-self: stretch; justify-self: end; width: 8px; margin-right: -4px; z-index: var(--z-raised); cursor: col-resize; background: transparent; border: none; padding: 0; touch-action: none; display: flex; align-items: center; justify-content: center; }
6121
+ .ds-247420 .ws-resizer::after { content: ""; display: block; width: 2px; height: 100%; margin: 0 auto; background: var(--rule); opacity: 0.4; transition: background var(--dur-snap) var(--ease), opacity var(--dur-snap) var(--ease); }
6122
+ .ds-247420 .ws-resizer:hover::after, .ds-247420 .ws-resizer:focus-visible::after { background: var(--accent); opacity: 1; }
6123
+ /* Grip: a small pill of dots centered on the handle, visible at rest (low
6124
+ contrast) so the resizer reads as a draggable affordance rather than an
6125
+ invisible hit zone, then brightens to accent on hover/drag like the line. */
6126
+ .ds-247420 .ws-resizer::before {
6127
+ content: ""; position: absolute; width: 4px; height: 20px; border-radius: var(--r-hair);
6128
+ background-image: radial-gradient(circle, var(--fg-3) 1px, transparent 1px);
6129
+ background-size: 4px 5px; background-repeat: repeat-y; background-position: center;
6130
+ opacity: 0.5; transition: opacity var(--dur-snap) var(--ease), background-image var(--dur-snap) var(--ease);
6131
+ pointer-events: none;
6132
+ }
6133
+ .ds-247420 .ws-resizer:hover::before, .ds-247420 .ws-resizer:focus-visible::before {
6134
+ opacity: 1; background-image: radial-gradient(circle, var(--accent) 1px, transparent 1px);
6135
+ }
5348
6136
  .ds-247420 .ws-resizer:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
6137
+ /* Active drag state: wider + fully opaque accent grip so the handle a
6138
+ fast-moving pointer has left :hover on still visibly reads as "gripped". */
6139
+ .ds-247420 .ws-resizer.ws-resizer-active::after { background: var(--accent); width: 3px; }
5349
6140
  .ds-247420 .ws-resizer.ws-resizer-rail { grid-column: 1; }
5350
6141
  .ds-247420 .ws-resizer.ws-resizer-sessions { grid-column: 2; }
5351
6142
  .ds-247420 .ws-resizer.ws-resizer-pane { grid-column: 3; justify-self: end; }
@@ -5400,7 +6191,10 @@
5400
6191
  .ds-247420 .ws-rail-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
5401
6192
  .ds-247420 .ws-rail-inner { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: var(--space-3) var(--space-2); gap: var(--space-2); }
5402
6193
  .ds-247420 .ws-rail-head { display: flex; align-items: center; min-height: 36px; padding: 0 var(--space-2); }
5403
- .ds-247420 .ws-rail-brand { font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-body); color: var(--fg); white-space: nowrap; overflow: hidden; }
6194
+ /* Wordmark treatment matches .brand (topbar.css) — same display family/weight/
6195
+ tracking — so the rail brand reads as the SAME logotype as every other kit's
6196
+ topbar wordmark instead of a lighter, differently-set imitation. */
6197
+ .ds-247420 .ws-rail-brand { font-family: var(--ff-display); font-weight: 700; font-size: var(--fs-body); letter-spacing: var(--tr-tight); color: var(--fg); white-space: nowrap; overflow: hidden; }
5404
6198
  .ds-247420 .ws-rail-action {
5405
6199
  display: flex; align-items: center; gap: var(--space-2);
5406
6200
  padding: var(--space-2) var(--space-3); margin: var(--space-1) 0;
@@ -5423,7 +6217,9 @@
5423
6217
  .ds-247420 .ws-rail-item:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
5424
6218
  .ds-247420 .ws-rail-item.active { background: var(--accent-tint); color: var(--fg); box-shadow: inset 2px 0 0 var(--accent); }
5425
6219
  .ds-247420 .ws-rail-item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
5426
- .ds-247420 .ws-rail-item-count { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3); }
6220
+ /* Shape/color come from the shared .ds-badge component (see app-shell/primitives.css);
6221
+ this class is now just the rail-item layout hook (spacing, collapsed-state targeting). */
6222
+ .ds-247420 .ws-rail-item-count { flex: none; margin-inline-start: var(--space-1); }
5427
6223
  .ds-247420 .ws-rail-item-flag { width: 6px; height: 6px; border-radius: 50%; flex: none; }
5428
6224
  .ds-247420 .ws-rail-item-flag.tone-flame { background: var(--flame); box-shadow: 0 0 0 2px color-mix(in oklab, var(--flame) 38%, transparent); }
5429
6225
  .ds-247420 .ws-rail-foot { margin-top: auto; padding-top: var(--space-2); border-top: var(--bw-hair) solid var(--bg-3); }
@@ -5466,7 +6262,14 @@
5466
6262
  because the chat thread carries its own --measure gutter. */
5467
6263
  .ds-247420 .ws-main { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; padding: var(--space-4) var(--space-5); }
5468
6264
  .ds-247420 .ws-main.ws-main--flush { padding: 0; }
6265
+ /* Skip-link target (workspace-shell.js's "#ws-main" skip-link, tabindex="-1")
6266
+ — it does receive real keyboard-driven focus when the skip link is
6267
+ activated, so it needs a visible ring rather than a bare outline:none. */
5469
6268
  .ds-247420 .ws-main:focus { outline: none; }
6269
+ .ds-247420 .ws-main:focus-visible {
6270
+ outline: var(--focus-w) solid var(--focus-color);
6271
+ outline-offset: calc(-1 * var(--focus-offset));
6272
+ }
5470
6273
 
5471
6274
  /* Right context pane */
5472
6275
  .ds-247420 .ws-pane {
@@ -5623,7 +6426,7 @@
5623
6426
 
5624
6427
  @media (prefers-reduced-motion: reduce) {
5625
6428
  .ds-247420 .ws-shell, .ds-247420 .ws-rail, .ds-247420 .ws-pane, .ds-247420 .ws-sessions, .ds-247420 .ws-scrim { transition: none; }
5626
- .ds-247420 .ws-resizer::after { transition: none; }
6429
+ .ds-247420 .ws-resizer::after, .ds-247420 .ws-resizer::before { transition: none; }
5627
6430
  }
5628
6431
 
5629
6432
  /* ============================================================
@@ -5659,13 +6462,26 @@
5659
6462
  .ds-247420 .ds-filter-pill.active { background: var(--accent-tint); color: var(--fg); border-color: var(--accent); }
5660
6463
  .ds-247420 .ds-filter-pill:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
5661
6464
 
6465
+ .ds-247420 .ds-filter-pill[class*="tone-"]::before {
6466
+ content: '';
6467
+ display: inline-block; width: 7px; height: 7px; border-radius: 50%;
6468
+ margin-right: var(--space-1);
6469
+ background: currentColor;
6470
+ }
6471
+ .ds-247420 .ds-filter-pill.tone-accent { color: var(--accent-ink); }
6472
+ .ds-247420 .ds-filter-pill.tone-warn { color: var(--amber); }
6473
+ .ds-247420 .ds-filter-pill.tone-live { color: var(--sky); }
6474
+ .ds-247420 .ds-filter-pill.tone-sun { color: var(--sun); }
6475
+ .ds-247420 .ds-filter-pill.tone-success { color: var(--green-deep); }
6476
+ .ds-247420 .ds-filter-pill.tone-dim { color: var(--fg-3); }
6477
+
5662
6478
  /* Touch floor for the new small controls. */
5663
6479
  @media (pointer: coarse) {
5664
6480
  .ds-247420 .row-act, .ds-247420 .ds-filter-pill { min-height: 44px; }
5665
6481
  }
5666
6482
 
5667
6483
  /* Disabled file actions (read-only row / in-flight mutation) stay visible but inert. */
5668
- .ds-247420 .ds-file-act:disabled { opacity: .45; cursor: default; }
6484
+ .ds-247420 .ds-file-act:disabled { opacity: .5; cursor: default; }
5669
6485
  .ds-247420 .ds-file-act:disabled:hover { background: transparent; color: var(--fg-3); }
5670
6486
  .ds-247420 .ds-file-act:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
5671
6487
 
@@ -5698,6 +6514,39 @@
5698
6514
  .ds-247420 .ds-live-log { height: auto; max-height: none; overflow: visible; }
5699
6515
  .ds-247420 .ds-stat, .ds-247420 .kpi-card { border: 1px solid var(--rule); }
5700
6516
  .ds-247420 .ds-session-row { break-inside: avoid; }
6517
+
6518
+ /* Receipt table (Receipt() in src/components/content/panel.js, `.kv`) — the
6519
+ project_page kit's "receipt" section, and any other kv-table consumer.
6520
+ On screen it inherits panel/token colors that can render as near-white
6521
+ text on a printer's assumed-white page background if the browser's
6522
+ "background graphics" print option is off (the common default); force
6523
+ the LIGHT-theme ink/paper tokens (real print-page colors, not a raw
6524
+ literal — --ink/--paper are the same "true black text on true white
6525
+ page" values the light theme already defines) and visible cell borders
6526
+ so it stays legible without relying on background-graphics printing at
6527
+ all. break-inside:avoid keeps a receipt from splitting a row across a
6528
+ page boundary. */
6529
+ .ds-247420 .kv { border-collapse: collapse; width: 100%; color: var(--ink); background: var(--paper); }
6530
+ .ds-247420 .kv tr { break-inside: avoid; }
6531
+ .ds-247420 .kv td { border: 1px solid var(--paper-3); padding: 4px 8px; color: var(--ink); }
6532
+
6533
+ /* Any button/interactive-only affordance still present inside otherwise-
6534
+ printable content (copy buttons, expand/collapse toggles, filter pills,
6535
+ the receipt/doc section's own action row) is chrome, not content. */
6536
+ .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost, .ds-247420 .row-act, .ds-247420 .ds-filter-pills, .ds-247420 .app-search, .ds-247420 input[type="search"] { display: none; }
6537
+
6538
+ /* Force real ink-on-paper for body copy: panel/fg tokens are tuned for
6539
+ screen surfaces (including dark theme, where --panel-text can resolve
6540
+ near-white) and a printer defaults to a white page regardless of
6541
+ data-theme, so an unconverted dark-theme page would print near-invisible
6542
+ text without this override. Same --ink/--paper light-theme pair as the
6543
+ receipt table above, not a second raw literal. */
6544
+ /* No !important: source order already wins here (this rule is the last
6545
+ word inside its own @media print block, same as every other override in
6546
+ this file), and an unconditional !important on `body` would beat even a
6547
+ future print-specific consumer override with higher specificity. */
6548
+ .ds-247420 body, .ds-247420 .app-main, .ds-247420 .page-body, .ds-247420 .ds-lede { color: var(--ink); background: var(--paper); }
6549
+ .ds-247420 a { color: var(--ink); text-decoration: underline; }
5701
6550
  }
5702
6551
 
5703
6552
  /* ============================================================
@@ -6056,7 +6905,17 @@
6056
6905
  aspect-ratio: auto;
6057
6906
  }
6058
6907
  }
6059
- .ds-247420 .ds-deck-slide { flex: 1; display: flex; min-width: 0; }
6908
+ .ds-247420 .ds-deck-slide {
6909
+ flex: 1; display: flex; min-width: 0;
6910
+ animation: ds-deck-slide-in var(--dur-base) var(--ease);
6911
+ }
6912
+ @media (prefers-reduced-motion: reduce) {
6913
+ .ds-247420 .ds-deck-slide { animation: none; }
6914
+ }
6915
+ @keyframes ds-deck-slide-in {
6916
+ from { opacity: 0; transform: translateY(var(--space-1-75)); }
6917
+ to { opacity: 1; transform: none; }
6918
+ }
6060
6919
  .ds-247420 .ds-deck-controls {
6061
6920
  /* 14px sits between --space-2-75 (12) and --space-3 (16) with no tier; it is
6062
6921
  the deck control spacing tuned against the mono glyph run. */
@@ -6067,6 +6926,19 @@
6067
6926
  @media (pointer: coarse) {
6068
6927
  .ds-247420 .ds-deck-controls .btn { min-height: 44px; min-width: 44px; }
6069
6928
  }
6929
+ /* Dot progress indicator: one dot per slide, current slide filled with
6930
+ --accent (same fill token .ds-dot-on uses elsewhere in the system), rest
6931
+ dim at --fg-3 so the row reads as a track, not a second counter. */
6932
+ .ds-247420 .ds-deck-progress {
6933
+ display: flex; align-items: center; justify-content: center;
6934
+ gap: var(--space-1-5); padding-bottom: var(--space-2);
6935
+ }
6936
+ .ds-247420 .ds-deck-dot {
6937
+ width: var(--space-1-5); height: var(--space-1-5); border-radius: 50%;
6938
+ background: var(--fg-3); flex: 0 0 auto;
6939
+ transition: background var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
6940
+ }
6941
+ .ds-247420 .ds-deck-dot--active { background: var(--accent); transform: scale(1.3); }
6070
6942
  /* Kit small-label voice — one local composition of the house `.t-label` recipe
6071
6943
  (base.css) for every eyebrow/label in the appended kit blocks below. These
6072
6944
  previously each restated the four declarations with drifted tracking
@@ -6177,7 +7049,12 @@
6177
7049
  }
6178
7050
  .ds-247420 .ds-gallery-tile {
6179
7051
  cursor: pointer; display: flex; flex-direction: column; gap: var(--space-1-75);
6180
- padding: var(--space-2-75); border-radius: var(--r-1); min-height: clamp(120px, 18vw, 160px);
7052
+ padding: var(--space-2-75); border-radius: var(--r-1);
7053
+ /* Fixed aspect-ratio (not just a min-height floor) so every tile in the grid
7054
+ occupies the same shape regardless of caption length — the ascii specimen
7055
+ is centered inside via .ds-tile-cap's flex alignment, the equivalent of
7056
+ object-fit: cover for a text tile with no bitmap to crop. */
7057
+ aspect-ratio: 4 / 3;
6181
7058
  border: 0; text-align: left; font: inherit; color: inherit;
6182
7059
  background: var(--tile-tone, var(--panel-1));
6183
7060
  transition: transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease);
@@ -6187,7 +7064,7 @@
6187
7064
  translateY + shadow idiom .chat-msg:hover .chat-bubble already uses. */
6188
7065
  .ds-247420 .ds-gallery-tile:hover { transform: translateY(-2px); box-shadow: 0 2px 8px color-mix(in oklab, var(--fg) 10%, transparent); }
6189
7066
  .ds-247420 .ds-gallery-tile:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
6190
- .ds-247420 .ds-gallery-tile--tight { min-height: clamp(96px, 14vw, 120px); }
7067
+ .ds-247420 .ds-gallery-tile--tight { aspect-ratio: 3 / 2; }
6191
7068
  /* The lightbox scrim/card chrome itself is now Dialog's (.ds-ep-dialog /
6192
7069
  .ds-ep-dialog-backdrop in editor-primitives.css) — Dialog already ships
6193
7070
  role="dialog", aria-modal, Tab-trap and focus-restore-on-close, which the
@@ -6211,13 +7088,21 @@
6211
7088
  @media (max-width: 480px) {
6212
7089
  .ds-247420 .ds-topbar-search { flex-basis: 100%; }
6213
7090
  }
6214
- .ds-247420 .ds-empty-state { padding: clamp(var(--space-3), 5vw, var(--space-4)); text-align: center; color: var(--panel-text-3); }
7091
+ /* Namespaced .ds-kit-empty-state (not .ds-empty-state) -- .ds-empty-state is
7092
+ primitives.css's shared base (padding var(--space-6), color var(--fg-3));
7093
+ this sheet loads after it in app-shell.css's import order, so reusing the
7094
+ same class name silently overrode that base's padding/color for every
7095
+ consumer, defeating the "shared base, one place" contract primitives.css
7096
+ documents. This block is unused by any component today, but kept
7097
+ namespaced so a future consumer gets this sheet's fluid clamp() treatment
7098
+ deliberately rather than by cascade accident. */
7099
+ .ds-247420 .ds-kit-empty-state { padding: clamp(var(--space-3), 5vw, var(--space-4)); text-align: center; color: var(--panel-text-3); }
6215
7100
  /* Fluid ICON size (empty-state mark), not a type tier: it scales with the
6216
7101
  viewport between 24 and 32px. Every fluid --fs-* tier is a heading clamp
6217
7102
  tuned to cqi + heading weight, so none of them substitutes here. */
6218
- .ds-247420 .ds-empty-state-glyph { font-size: clamp(24px, 6vw, 32px); }
6219
- .ds-247420 .ds-empty-state-msg { margin: var(--space-1-75) 0; }
6220
- .ds-247420 .ds-empty-state-hint { margin: 0; font-size: var(--fs-sm); }
7103
+ .ds-247420 .ds-kit-empty-state-glyph { font-size: clamp(24px, 6vw, 32px); }
7104
+ .ds-247420 .ds-kit-empty-state-msg { margin: var(--space-1-75) 0; }
7105
+ .ds-247420 .ds-kit-empty-state-hint { margin: 0; font-size: var(--fs-sm); }
6221
7106
 
6222
7107
  /* --- system primer ------------------------------------------ */
6223
7108
  /* The three primer panels below share one fluid inset,
@@ -6289,12 +7174,21 @@
6289
7174
  gap: var(--space-1-75, 6px);
6290
7175
  }
6291
7176
  /* Table() scroll containment — wide tables scroll inside their own box,
6292
- never the page body. */
7177
+ never the page body. overflow-x:auto alone works but gives no visible cue
7178
+ that a table is wider than its box (macOS overlay scrollbars stay hidden
7179
+ until an active hover/scroll gesture) -- columns beyond the fold read as
7180
+ simply missing rather than reachable. An always-visible thin scrollbar
7181
+ track makes the overflow legible without a gesture first. */
6293
7182
  .ds-247420 .ds-table-wrap {
6294
7183
  overflow-x: auto;
6295
7184
  -webkit-overflow-scrolling: touch;
6296
7185
  max-width: 100%;
7186
+ scrollbar-width: thin;
7187
+ scrollbar-color: var(--rule-strong) transparent;
6297
7188
  }
7189
+ .ds-247420 .ds-table-wrap::-webkit-scrollbar { height: 8px; }
7190
+ .ds-247420 .ds-table-wrap::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: var(--r-pill, 999px); }
7191
+ .ds-247420 .ds-table-wrap::-webkit-scrollbar-track { background: transparent; }
6298
7192
  .ds-247420 .ds-table-wrap > table { min-width: 100%; }
6299
7193
  /* Sortable column headers (Table's opt-in sortable prop) — a docstudio-cue
6300
7194
  addition: color stays neutral at rest (this is a structural control, not a
@@ -6341,6 +7235,11 @@
6341
7235
  .ds-247420 .ds-btn-row-tight { gap: var(--space-1-75, 6px); }
6342
7236
  .ds-247420 .ds-key-input { flex: 1 1 160px; min-width: 0; font-family: var(--ff-mono); }
6343
7237
  .ds-247420 .ds-toggle-btn { min-width: 78px; }
7238
+ /* Side-by-side checked/unchecked reference pair (settings kit theme panel) --
7239
+ documents the visual difference between states in one static, non-live
7240
+ spot rather than requiring a viewer to toggle a real control back and
7241
+ forth to compare. */
7242
+ .ds-247420 .ds-toggle-state-sample { display: flex; align-items: center; gap: var(--space-1-5, 6px); }
6344
7243
  .ds-247420 .ds-toggle-label { margin-left: var(--space-2, 8px); color: inherit; opacity: 0.7; }
6345
7244
  .ds-247420 .ds-btn-warn { color: var(--warn); }
6346
7245
  /* Raw --mascot text measures 3.11:1 on paper (fails 4.5:1); --mascot-deep is
@@ -6356,6 +7255,14 @@
6356
7255
  justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap;
6357
7256
  padding: calc(var(--space-2-75) * var(--density)) calc(var(--space-3) * var(--density));
6358
7257
  background: var(--panel-2); border-radius: var(--r-1);
7258
+ /* --panel-1 (this bar's parent .panel surface) and --panel-2 are adjacent
7259
+ lightness steps that measure only ~1.1-1.5:1 apart (both themes) -- too
7260
+ close to read as a distinct nesting depth by background tone alone.
7261
+ A hairline border is the same edge-definition device .panel itself
7262
+ already uses (see the top of this file) rather than a token-lightness
7263
+ change, which would risk the fg-3-on-panel-2 text contrast that is
7264
+ already right at its AA floor in the dark theme. */
7265
+ border: var(--bw-hair) solid var(--rule);
6359
7266
  margin: calc(var(--space-5) * var(--density)) 0 var(--space-2);
6360
7267
  box-shadow: var(--shadow-2, var(--shadow-1));
6361
7268
  }
@@ -6401,13 +7308,37 @@
6401
7308
  /* .ds-auth-field-label joins the kit small-label voice above, no overrides. */
6402
7309
  .ds-247420 .ds-auth-field-input-row { position: relative; display: flex; align-items: center; }
6403
7310
  /* Reserves room for the absolutely-positioned toggle: its --space-1 inset plus
6404
- the --ds-icon-btn-sm 26px box, so the input text never sits under it. */
7311
+ the --ds-icon-btn-sm 26px box, so the input text never sits under it. This
7312
+ only holds at the pointer:fine 26px box size, though -- the coarse-pointer
7313
+ touch-floor rule (states-interactions.css) grows .ds-icon-btn-sm to a
7314
+ min-width/height of 44px without this padding following it, so on a
7315
+ touchscreen the enlarged hit target actually overlapped 14px into the
7316
+ typed password text (44 + 4 inset > 34 reserved). Reserve for the coarse
7317
+ floor too, gated behind the same media query, so clearance holds at both
7318
+ sizes instead of only the desktop one. */
6405
7319
  .ds-247420 .ds-auth-field-input-row .input { flex: 1 1 auto; padding-right: calc(26px + var(--space-2)); }
7320
+ @media (pointer: coarse) {
7321
+ .ds-247420 .ds-auth-field-input-row .input { padding-right: calc(44px + var(--space-2)); }
7322
+ }
6406
7323
  .ds-247420 .ds-auth-pw-toggle { position: absolute; right: var(--space-1); }
6407
7324
  .ds-247420 .ds-auth-row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
6408
7325
  .ds-247420 .ds-auth-remember { display: flex; align-items: center; gap: var(--space-1-75); cursor: pointer; }
7326
+ /* The native checkbox is already re-tinted site-wide via `accent-color:
7327
+ var(--accent-ink)` in states-interactions.css (that rule's own comment
7328
+ names this exact control) -- reused here rather than hand-drawing a new
7329
+ checkbox component. What that global rule doesn't cover is size: the UA
7330
+ default box renders at ~13px, which is under the 24px comfortable-tap
7331
+ floor other controls in this row (the link text, the input fields) sit
7332
+ above. Sized up on the existing --space-2-5 token, no new primitive. */
7333
+ .ds-247420 .ds-auth-remember input[type="checkbox"] { width: var(--space-2-5); height: var(--space-2-5); margin: 0; }
6409
7334
  .ds-247420 .ds-auth-remember-text { color: var(--panel-text-2); font-size: var(--fs-tiny); }
6410
- .ds-247420 .ds-auth-forgot { font-size: var(--fs-tiny); }
7335
+ /* --accent-ink, not --accent, per the design system's text-vs-fill split
7336
+ (AGENTS.md): --accent is a background/border-color lead only, --accent-ink
7337
+ is the tone actually verified for text contrast. The link was previously
7338
+ unstyled prose color at --fs-tiny, reading as inert grey next to the
7339
+ checkbox label it shares a row with. */
7340
+ .ds-247420 .ds-auth-forgot { font-size: var(--fs-tiny); color: var(--accent-ink); font-weight: 500; }
7341
+ .ds-247420 .ds-auth-forgot:hover { text-decoration: underline; }
6411
7342
  /* Error and status share layout but not color: a color-only distinction
6412
7343
  between role="alert" (something is wrong) and role="status" (informational)
6413
7344
  fails WCAG 1.4.1, so each also carries a distinct ASCII marker via ::before —
@@ -6417,6 +7348,28 @@
6417
7348
  .ds-247420 .ds-auth-error::before { content: '[!]'; flex: none; }
6418
7349
  .ds-247420 .ds-auth-status { color: var(--panel-text-2); }
6419
7350
  .ds-247420 .ds-auth-status::before { content: '[i]'; flex: none; }
7351
+ /* The shared Divider() label defaults to --fs-tiny/--panel-text-3 (editor-
7352
+ primitives.css), tuned for a dense editor toolbar rule -- inside the auth
7353
+ form it sat at the same weight as fine print two rows below it and read
7354
+ as barely-there rather than a section break between the password form and
7355
+ the OAuth row. Scoped to this form only (never edit the shared component
7356
+ sheet for one consumer): borrows the kit small-label voice above (mono,
7357
+ --fs-nano, --tr-label, uppercase) at --panel-text-2 instead of the
7358
+ dimmer -3 tier, plus the ::before/::after rule lines pushed to full
7359
+ --panel-text opacity so the break itself, not just the label, is visible. */
7360
+ .ds-247420 .ds-auth-form .ds-ep-divider-labeled { margin: var(--space-1) 0; }
7361
+ .ds-247420 .ds-auth-form .ds-ep-divider-labeled::before,
7362
+ .ds-247420 .ds-auth-form .ds-ep-divider-labeled::after {
7363
+ border-top-color: var(--panel-text-3);
7364
+ }
7365
+ .ds-247420 .ds-auth-form .ds-ep-divider-label {
7366
+ font-family: var(--ff-mono);
7367
+ font-size: var(--fs-nano);
7368
+ font-weight: 600;
7369
+ letter-spacing: var(--tr-label);
7370
+ text-transform: uppercase;
7371
+ color: var(--panel-text-2);
7372
+ }
6420
7373
  .ds-247420 .ds-auth-providers { display: flex; gap: var(--space-2); flex-wrap: wrap; }
6421
7374
  .ds-247420 .ds-auth-provider-btn { flex: 1 1 100px; min-width: 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2-5); }
6422
7375
  .ds-247420 .ds-auth-provider-btn--loading { opacity: 0.7; pointer-events: none; }
@@ -6483,6 +7436,14 @@
6483
7436
  display: flex; flex-direction: column; gap: 0;
6484
7437
  background: var(--ink); border-radius: var(--r-1);
6485
7438
  font-family: var(--ff-mono); font-size: var(--fs-micro); line-height: 1.65;
7439
+ /* Responsive strategy for long logs: cap the scrollback height and scroll
7440
+ vertically rather than growing the page indefinitely; long command/output
7441
+ lines wrap (see .cli .cmd's overflow-wrap: anywhere below) instead of
7442
+ scrolling horizontally, so wrapping is the chosen default over
7443
+ horizontal scroll for this kit. */
7444
+ max-height: 60vh;
7445
+ overflow-y: auto;
7446
+ overflow-wrap: anywhere;
6486
7447
  }
6487
7448
  /* .cli is authored in hero-content.css as a STANDALONE hero code block --
6488
7449
  20px/24px padding, --fs-lg type, --r-3 radius and its own --ink background
@@ -6584,6 +7545,7 @@
6584
7545
  border-radius: 50%; background: var(--bg-3); color: var(--fg);
6585
7546
  font-family: var(--ff-mono); font-weight: 600; text-transform: lowercase;
6586
7547
  letter-spacing: 0.02em; user-select: none; flex-shrink: 0; object-fit: cover;
7548
+ box-shadow: inset 0 0 0 1px var(--rule);
6587
7549
  }
6588
7550
  .ds-247420 .ds-avatar-sm { width: 24px; height: 24px; font-size: var(--fs-pico); }
6589
7551
  .ds-247420 .ds-avatar-md { width: 36px; height: 36px; font-size: var(--fs-micro); }
@@ -6711,6 +7673,7 @@
6711
7673
  font-weight: 600;
6712
7674
  line-height: 1;
6713
7675
  user-select: none;
7676
+ box-shadow: inset 0 0 0 1px var(--rule);
6714
7677
  }
6715
7678
 
6716
7679
  /* Body stack — name + time on one baseline, text beneath. .chat-stack's
@@ -7102,12 +8065,16 @@
7102
8065
  }
7103
8066
  .ds-247420 .ds-plugins-version { font-size: var(--fs-micro); color: var(--fg-3); }
7104
8067
 
8068
+ /* Same track/knob proportions as the shared .ds-toggle primitive
8069
+ (states-interactions.css) -- kept as its own class since ds-plugins-toggle
8070
+ is a plain <button>+.on instead of .ds-toggle's [aria-checked] contract,
8071
+ but sized and colored to match it exactly rather than drifting. */
7105
8072
  .ds-247420 .ds-plugins-toggle {
7106
- flex: 0 0 auto; width: 40px; height: 22px; border-radius: 11px; border: none; padding: 0;
8073
+ flex: 0 0 auto; width: 40px; height: 22px; border-radius: var(--r-pill, 999px); border: none; padding: 0;
7107
8074
  background: var(--rule); position: relative; cursor: pointer;
7108
8075
  transition: background var(--dur-snap) var(--ease);
7109
8076
  }
7110
- .ds-247420 .ds-plugins-toggle.on { background: var(--accent); }
8077
+ .ds-247420 .ds-plugins-toggle.on { background: var(--accent-ink); }
7111
8078
  .ds-247420 .ds-plugins-toggle[disabled] { cursor: wait; opacity: 0.65; }
7112
8079
  .ds-247420 .ds-plugins-toggle-knob {
7113
8080
  position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
@@ -7344,11 +8311,11 @@
7344
8311
  .ds-247420 .ds-otp-box:focus-visible {
7345
8312
  outline: none;
7346
8313
  border-color: var(--accent);
7347
- box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 40%, transparent);
8314
+ box-shadow: var(--focus-ring-inset);
7348
8315
  }
7349
8316
  .ds-247420 .ds-otp-box-filled { border-color: var(--rule-strong); background: var(--bg-3); }
7350
8317
  .ds-247420 .ds-otp-box-error { border-color: var(--red); }
7351
- .ds-247420 .ds-otp-box-error:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--red) 40%, transparent); }
8318
+ .ds-247420 .ds-otp-box-error:focus-visible { box-shadow: var(--focus-ring-inset); }
7352
8319
  .ds-247420 .ds-otp-box:disabled { opacity: 0.5; cursor: default; }
7353
8320
 
7354
8321
  @media (prefers-reduced-motion: reduce) {
@@ -7612,6 +8579,11 @@
7612
8579
  border-radius: var(--r-pill);
7613
8580
  background: var(--bg-2);
7614
8581
  }
8582
+ /* .ds-collab-chip-avatar (24px identity circle) and .ds-collab-chip-status
8583
+ (8px status dot) below are both circular via border-radius: 50% but are
8584
+ intentionally distinct: different sizes, different content (initial vs.
8585
+ plain fill), different positioning role (layout box vs. absolute badge).
8586
+ Not extracting a shared class -- there's nothing substantive to share. */
7615
8587
  .ds-247420 .ds-collab-chip-avatar {
7616
8588
  position: relative;
7617
8589
  width: 24px;
@@ -7726,7 +8698,7 @@
7726
8698
  justify-content: center;
7727
8699
  font-family: var(--ff-body);
7728
8700
  font-size: var(--fs-lg);
7729
- font-weight: 600;
8701
+ font-weight: var(--fw-medium);
7730
8702
  letter-spacing: var(--tr-tight);
7731
8703
  cursor: pointer;
7732
8704
  overflow: hidden;
@@ -7776,11 +8748,14 @@
7776
8748
  min-width: 18px;
7777
8749
  height: 18px;
7778
8750
  padding: 0 var(--space-1-5);
8751
+ /* --ink, not --paper: --paper is a fixed near-white, which measures ~3:1 on
8752
+ --warn (#FF5A52) -- below the 4.5:1 AA floor. --ink clears it (same fix
8753
+ as .btn-primary.danger in app-shell/files.css). */
7779
8754
  background: var(--warn);
7780
- color: var(--paper);
8755
+ color: var(--ink);
7781
8756
  font-family: var(--ff-mono);
7782
8757
  font-size: var(--fs-micro);
7783
- font-weight: 700;
8758
+ font-weight: var(--fw-bold);
7784
8759
  border-radius: var(--r-pill);
7785
8760
  display: flex;
7786
8761
  align-items: center;
@@ -7799,7 +8774,7 @@
7799
8774
  align-items: center;
7800
8775
  justify-content: center;
7801
8776
  font-size: var(--fs-xl);
7802
- font-weight: 400;
8777
+ font-weight: var(--fw-regular);
7803
8778
  cursor: pointer;
7804
8779
  border: 0;
7805
8780
  transition:
@@ -7845,7 +8820,7 @@
7845
8820
  padding: var(--space-2-75) var(--space-3);
7846
8821
  background: var(--bg-2);
7847
8822
  box-shadow: 0 1px 0 color-mix(in oklab, var(--fg) 8%, transparent);
7848
- font-weight: 600;
8823
+ font-weight: var(--fw-medium);
7849
8824
  font-size: var(--fs-sm);
7850
8825
  letter-spacing: -0.01em;
7851
8826
  /* No cursor:pointer, no :hover background, no user-select:none. This is a
@@ -7900,7 +8875,7 @@
7900
8875
  padding: var(--space-1) var(--space-2);
7901
8876
  font-family: var(--ff-body);
7902
8877
  font-size: var(--fs-micro);
7903
- font-weight: 600;
8878
+ font-weight: var(--fw-medium);
7904
8879
  color: var(--fg-3);
7905
8880
  letter-spacing: var(--tr-caps);
7906
8881
  text-transform: uppercase;
@@ -7939,7 +8914,7 @@
7939
8914
  font-family: var(--ff-mono);
7940
8915
  font-size: var(--fs-micro);
7941
8916
  color: var(--fg-3);
7942
- font-weight: 500;
8917
+ font-weight: var(--fw-medium);
7943
8918
  letter-spacing: 0;
7944
8919
  text-transform: none;
7945
8920
  }
@@ -8009,7 +8984,7 @@
8009
8984
  .ds-247420 .cm-channel-item.active {
8010
8985
  background: color-mix(in oklab, var(--fg) 10%, transparent);
8011
8986
  color: var(--fg);
8012
- font-weight: 500;
8987
+ font-weight: var(--fw-medium);
8013
8988
  }
8014
8989
  .ds-247420 .cm-channel-item.active::before {
8015
8990
  content: '';
@@ -8108,11 +9083,14 @@
8108
9083
  min-width: 16px;
8109
9084
  height: 16px;
8110
9085
  padding: 0 var(--space-1-5);
9086
+ /* --ink, not --paper: --paper is a fixed near-white, which measures ~3:1 on
9087
+ --warn (#FF5A52) -- below the 4.5:1 AA floor. --ink clears it (same fix
9088
+ as .btn-primary.danger in app-shell/files.css). */
8111
9089
  background: var(--warn);
8112
- color: var(--paper);
9090
+ color: var(--ink);
8113
9091
  font-family: var(--ff-mono);
8114
9092
  font-size: var(--fs-micro);
8115
- font-weight: 700;
9093
+ font-weight: var(--fw-bold);
8116
9094
  border-radius: var(--r-pill);
8117
9095
  display: inline-flex;
8118
9096
  align-items: center;
@@ -8191,13 +9169,16 @@
8191
9169
  height: 20px;
8192
9170
  border-radius: 50%;
8193
9171
  background: var(--avatar-bg, var(--bg-3));
8194
- color: var(--fg-2);
9172
+ color: var(--avatar-fg, var(--fg-2));
8195
9173
  display: inline-flex;
8196
9174
  align-items: center;
8197
9175
  justify-content: center;
8198
9176
  font-size: var(--fs-pico);
8199
- font-weight: 600;
9177
+ font-weight: var(--fw-medium);
8200
9178
  overflow: hidden;
9179
+ /* Depth over a flat solid-color disc: a hairline inset ring so avatars
9180
+ read as a real surface element, not a wireframe placeholder. */
9181
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 10%, transparent);
8201
9182
  transition: box-shadow var(--dur-snap) var(--ease);
8202
9183
  }
8203
9184
  .ds-247420 .cm-ch-voice-user-avatar img {
@@ -8235,7 +9216,7 @@
8235
9216
  a pulsing dot and must read as the loudest thing in the strip). */
8236
9217
  .ds-247420 .cm-vs-label {
8237
9218
  color: var(--green);
8238
- font-weight: 700;
9219
+ font-weight: var(--fw-bold);
8239
9220
  display: inline-flex;
8240
9221
  align-items: center;
8241
9222
  gap: var(--space-1-75);
@@ -8261,7 +9242,7 @@
8261
9242
 
8262
9243
  .ds-247420 .cm-vs-channel {
8263
9244
  font-size: var(--fs-sm);
8264
- font-weight: 600;
9245
+ font-weight: var(--fw-medium);
8265
9246
  color: var(--fg);
8266
9247
  overflow: hidden;
8267
9248
  text-overflow: ellipsis;
@@ -8289,7 +9270,7 @@
8289
9270
  border-radius: var(--r-1);
8290
9271
  font-family: var(--ff-body);
8291
9272
  font-size: var(--fs-xs);
8292
- font-weight: 600;
9273
+ font-weight: var(--fw-medium);
8293
9274
  cursor: pointer;
8294
9275
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
8295
9276
  }
@@ -8297,8 +9278,11 @@
8297
9278
  background: color-mix(in oklab, var(--fg) 18%, transparent);
8298
9279
  }
8299
9280
  .ds-247420 .cm-vs-btn.danger {
9281
+ /* --ink, not --paper: --paper is a fixed near-white, which measures ~3:1 on
9282
+ --warn (#FF5A52) -- below the 4.5:1 AA floor. Same fix pattern as
9283
+ .btn-primary.danger in app-shell/files.css. */
8300
9284
  background: var(--warn);
8301
- color: var(--paper);
9285
+ color: var(--ink);
8302
9286
  }
8303
9287
  .ds-247420 .cm-vs-btn.danger:hover {
8304
9288
  background: color-mix(in oklab, var(--warn) 80%, var(--ink));
@@ -8323,13 +9307,14 @@
8323
9307
  height: 32px;
8324
9308
  border-radius: 50%;
8325
9309
  background: var(--avatar-bg, var(--bg-3));
8326
- color: var(--fg-2);
9310
+ color: var(--avatar-fg, var(--fg-2));
8327
9311
  display: inline-flex;
8328
9312
  align-items: center;
8329
9313
  justify-content: center;
8330
9314
  font-size: var(--fs-xs);
8331
- font-weight: 600;
9315
+ font-weight: var(--fw-medium);
8332
9316
  overflow: hidden;
9317
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 10%, transparent);
8333
9318
  }
8334
9319
  .ds-247420 .cm-user-avatar img {
8335
9320
  width: 100%;
@@ -8358,7 +9343,7 @@
8358
9343
  }
8359
9344
  .ds-247420 .cm-user-name {
8360
9345
  font-size: var(--fs-xs);
8361
- font-weight: 600;
9346
+ font-weight: var(--fw-medium);
8362
9347
  color: var(--fg);
8363
9348
  overflow: hidden;
8364
9349
  text-overflow: ellipsis;
@@ -8439,7 +9424,7 @@
8439
9424
  pinned weight/size/colour, but a bare h1 also brings a UA block margin
8440
9425
  that would break this flex row — so the margin is zeroed explicitly. */
8441
9426
  margin: 0;
8442
- font-weight: 600;
9427
+ font-weight: var(--fw-medium);
8443
9428
  font-size: var(--fs-sm);
8444
9429
  color: var(--fg);
8445
9430
  letter-spacing: -0.01em;
@@ -8503,7 +9488,7 @@
8503
9488
  padding: var(--space-2-75) var(--space-3) var(--space-1-75);
8504
9489
  font-family: var(--ff-body);
8505
9490
  font-size: var(--fs-micro);
8506
- font-weight: 600;
9491
+ font-weight: var(--fw-medium);
8507
9492
  color: var(--fg-3);
8508
9493
  letter-spacing: var(--tr-caps);
8509
9494
  text-transform: uppercase;
@@ -8534,13 +9519,14 @@
8534
9519
  height: 28px;
8535
9520
  border-radius: 50%;
8536
9521
  background: var(--avatar-bg, var(--bg-3));
8537
- color: var(--fg-2);
9522
+ color: var(--avatar-fg, var(--fg-2));
8538
9523
  display: inline-flex;
8539
9524
  align-items: center;
8540
9525
  justify-content: center;
8541
9526
  font-size: var(--fs-micro);
8542
- font-weight: 600;
9527
+ font-weight: var(--fw-medium);
8543
9528
  overflow: hidden;
9529
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 10%, transparent);
8544
9530
  }
8545
9531
  .ds-247420 .cm-member-avatar img {
8546
9532
  width: 100%;
@@ -8595,13 +9581,14 @@
8595
9581
  height: 56px;
8596
9582
  border-radius: 50%;
8597
9583
  background: var(--avatar-bg, var(--bg-3));
8598
- color: var(--fg-2);
9584
+ color: var(--avatar-fg, var(--fg-2));
8599
9585
  display: inline-flex;
8600
9586
  align-items: center;
8601
9587
  justify-content: center;
8602
9588
  font-size: var(--fs-lg);
8603
- font-weight: 600;
9589
+ font-weight: var(--fw-medium);
8604
9590
  overflow: hidden;
9591
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 10%, transparent);
8605
9592
  }
8606
9593
  .ds-247420 .cm-voice-user-avatar img {
8607
9594
  width: 100%;
@@ -8616,7 +9603,7 @@
8616
9603
  .ds-247420 .cm-voice-user-name {
8617
9604
  font-size: var(--fs-xs);
8618
9605
  color: var(--fg);
8619
- font-weight: 500;
9606
+ font-weight: var(--fw-medium);
8620
9607
  max-width: 100px;
8621
9608
  overflow: hidden;
8622
9609
  text-overflow: ellipsis;
@@ -8811,7 +9798,7 @@
8811
9798
  flex: 1 1 auto;
8812
9799
  min-width: 0;
8813
9800
  font-family: var(--font-mono, monospace);
8814
- font-weight: 600;
9801
+ font-weight: var(--fw-medium);
8815
9802
  color: var(--fg);
8816
9803
  white-space: nowrap;
8817
9804
  overflow: hidden;
@@ -8834,7 +9821,7 @@
8834
9821
  font-size: var(--fs-xs);
8835
9822
  }
8836
9823
  .ds-247420 .cm-rb-label { flex: none; color: var(--fg-2); }
8837
- .ds-247420 .cm-rb-author { color: var(--accent); font-weight: 600; }
9824
+ .ds-247420 .cm-rb-author { color: var(--accent); font-weight: var(--fw-medium); }
8838
9825
  .ds-247420 .cm-rb-preview {
8839
9826
  flex: 1 1 auto;
8840
9827
  min-width: 0;
@@ -8866,7 +9853,7 @@
8866
9853
  width: 100%;
8867
9854
  padding: var(--space-2) var(--space-3);
8868
9855
  font-size: var(--fs-xs);
8869
- font-weight: 500;
9856
+ font-weight: var(--fw-medium);
8870
9857
  }
8871
9858
  .ds-247420 .cm-banner.clickable { cursor: pointer; }
8872
9859
  .ds-247420 .cm-banner.clickable:hover { background: color-mix(in oklab, currentColor 10%, transparent); }
@@ -8877,7 +9864,7 @@
8877
9864
  background: transparent;
8878
9865
  color: inherit;
8879
9866
  font: inherit;
8880
- font-weight: 600;
9867
+ font-weight: var(--fw-semibold);
8881
9868
  padding: var(--space-hair) var(--space-2);
8882
9869
  border-radius: var(--r-1);
8883
9870
  cursor: pointer;
@@ -8939,7 +9926,7 @@
8939
9926
  color: var(--fg);
8940
9927
  font-family: var(--ff-body);
8941
9928
  font-size: var(--fs-sm);
8942
- font-weight: 600;
9929
+ font-weight: var(--fw-medium);
8943
9930
  cursor: pointer;
8944
9931
  user-select: none;
8945
9932
  -webkit-user-select: none;
@@ -9061,7 +10048,7 @@
9061
10048
  color: var(--fg);
9062
10049
  font-family: var(--ff-body);
9063
10050
  font-size: var(--fs-sm);
9064
- font-weight: 600;
10051
+ font-weight: var(--fw-semibold);
9065
10052
  cursor: pointer;
9066
10053
  transition: background var(--dur-base) var(--ease);
9067
10054
  }
@@ -9091,7 +10078,7 @@
9091
10078
  padding: var(--space-3);
9092
10079
  border-bottom: var(--bw-hair) solid var(--rule);
9093
10080
  }
9094
- .ds-247420 .vx-modal-title { margin: 0; font-size: var(--fs-lg); font-weight: 600; }
10081
+ .ds-247420 .vx-modal-title { margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
9095
10082
  .ds-247420 .vx-modal-x {
9096
10083
  border: 0; background: transparent; color: var(--fg-3);
9097
10084
  font-size: var(--fs-xl); line-height: 1; cursor: pointer; padding: 0 var(--space-1);
@@ -9115,7 +10102,7 @@
9115
10102
  color: var(--fg);
9116
10103
  font-family: var(--ff-body);
9117
10104
  font-size: var(--fs-tiny);
9118
- font-weight: 600;
10105
+ font-weight: var(--fw-medium);
9119
10106
  letter-spacing: var(--tr-caps);
9120
10107
  cursor: pointer;
9121
10108
  }
@@ -9128,6 +10115,9 @@
9128
10115
  font-size: var(--fs-sm);
9129
10116
  }
9130
10117
  .ds-247420 .vx-toggle { accent-color: var(--accent); width: 18px; height: 18px; cursor: pointer; }
10118
+ @media (pointer: coarse) {
10119
+ .ds-247420 .vx-toggle { width: 24px; height: 24px; }
10120
+ }
9131
10121
 
9132
10122
  .ds-247420 .vx-queue {
9133
10123
  display: flex; align-items: center; gap: var(--space-2);
@@ -9170,13 +10160,13 @@
9170
10160
  .ds-247420 .vx-chip-current { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 16%, var(--bg-3)); }
9171
10161
  .ds-247420 .vx-chip-live { border-color: var(--green-2); }
9172
10162
  .ds-247420 .vx-chip-dot { width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--fg-3); flex-shrink: 0; }
9173
- .ds-247420 .vx-chip-name { font-weight: 600; }
10163
+ .ds-247420 .vx-chip-name { font-weight: var(--fw-medium); }
9174
10164
  .ds-247420 .vx-chip-dur { font-family: var(--ff-mono); color: var(--fg-3); }
9175
10165
  /* Joins the small-label voice above. Deliberate overrides: --green + weight 700
9176
10166
  because this is the "LIVE" badge on a session chip, same signal role as
9177
10167
  .cm-vs-label. */
9178
10168
  .ds-247420 .vx-chip-tag {
9179
- color: var(--green); font-weight: 700;
10169
+ color: var(--green); font-weight: var(--fw-bold);
9180
10170
  }
9181
10171
 
9182
10172
  /* VoiceControls — vx-vc-* */
@@ -9222,7 +10212,7 @@
9222
10212
  display: flex; align-items: center; justify-content: space-between;
9223
10213
  padding: var(--space-2); border-bottom: var(--bw-hair) solid var(--rule);
9224
10214
  }
9225
- .ds-247420 .cm-tp-title { font-weight: 700; font-size: var(--fs-xs); }
10215
+ .ds-247420 .cm-tp-title { font-weight: var(--fw-bold); font-size: var(--fs-xs); }
9226
10216
  .ds-247420 .cm-tp-head-actions { display: flex; gap: var(--space-1); }
9227
10217
  .ds-247420 .cm-tp-new, .ds-247420 .cm-tp-close {
9228
10218
  border: none; background: transparent; color: var(--fg-2, var(--fg));
@@ -9240,7 +10230,7 @@
9240
10230
  }
9241
10231
  .ds-247420 .cm-tp-item:hover { background: var(--bg-3); }
9242
10232
  .ds-247420 .cm-tp-item.is-active { background: var(--bg-3); box-shadow: inset 3px 0 0 var(--accent); }
9243
- .ds-247420 .cm-tp-item.is-unread .cm-tp-item-title { font-weight: 700; }
10233
+ .ds-247420 .cm-tp-item.is-unread .cm-tp-item-title { font-weight: var(--fw-bold); }
9244
10234
  .ds-247420 .cm-tp-dot { position: absolute; left: 4px; top: 50%; transform: translateY(-50%); width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
9245
10235
  .ds-247420 .cm-tp-item-title { font-size: var(--fs-xs); }
9246
10236
  .ds-247420 .cm-tp-item-snippet { font-size: var(--fs-micro); color: var(--fg-2, var(--fg)); opacity: .8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -9294,7 +10284,7 @@
9294
10284
  .ds-247420 .cm-forum-item { display: flex; flex-direction: column; gap: var(--space-1); width: 100%; text-align: left; cursor: pointer; padding: var(--space-2) var(--space-3); border: none; background: transparent; border-bottom: var(--bw-hair) solid var(--rule); color: var(--fg); }
9295
10285
  .ds-247420 .cm-forum-item:hover { background: var(--bg-3); }
9296
10286
  .ds-247420 .cm-forum-item-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
9297
- .ds-247420 .cm-forum-item-title { font-weight: 700; font-size: var(--fs-xs); }
10287
+ .ds-247420 .cm-forum-item-title { font-weight: var(--fw-bold); font-size: var(--fs-xs); }
9298
10288
  .ds-247420 .cm-forum-item-replies { font-size: var(--fs-micro); color: var(--fg-2, var(--fg)); opacity: .8; white-space: nowrap; }
9299
10289
  .ds-247420 .cm-forum-item-snippet { font-size: var(--fs-micro); color: var(--fg-2, var(--fg)); opacity: .85; }
9300
10290
  .ds-247420 .cm-forum-item-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; font-size: var(--fs-micro); color: var(--fg-2, var(--fg)); opacity: .7; }
@@ -9306,10 +10296,14 @@
9306
10296
  /* PageView — cm-page / cm-page-* */
9307
10297
  .ds-247420 .cm-page { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow-y: auto; background: var(--bg-1, var(--bg-2)); color: var(--fg); }
9308
10298
  .ds-247420 .cm-page-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-3); border-bottom: var(--bw-hair) solid var(--rule); }
9309
- .ds-247420 .cm-page-title { margin: 0; font-size: var(--fs-xl); font-weight: 700; }
10299
+ .ds-247420 .cm-page-head-title { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
10300
+ .ds-247420 .cm-page-title { margin: 0; font-size: var(--fs-xl); font-weight: var(--fw-bold); }
10301
+ .ds-247420 .cm-page-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-xs); color: var(--fg-2, var(--fg)); }
10302
+ .ds-247420 .cm-page-author { font-weight: var(--fw-medium); }
10303
+ .ds-247420 .cm-page-time { opacity: .75; }
9310
10304
  .ds-247420 .cm-page-edit { padding: var(--space-1) var(--space-2); border: var(--bw-hair) solid var(--rule-strong); border-radius: var(--r-1); background: var(--bg-3); color: var(--fg); cursor: pointer; }
9311
10305
  .ds-247420 .cm-page-body { padding: var(--space-3); line-height: 1.6; }
9312
- .ds-247420 .cm-page-empty { color: var(--fg-2, var(--fg)); opacity: .7; }
10306
+ .ds-247420 .cm-page-empty { color: var(--fg-2, var(--fg)); opacity: .82; }
9313
10307
 
9314
10308
  /* ============================================================
9315
10309
  freddie pages — layout for FREDDIE_PAGES renderers.
@@ -9319,12 +10313,15 @@
9319
10313
  .ds-247420 .fd-page-inner { display: flex; flex-direction: column; gap: var(--space-3, 16px); padding: var(--space-3, 16px); max-width: 1100px; }
9320
10314
  .ds-247420 .fd-page-inner > * { min-width: 0; }
9321
10315
  .ds-247420 .fd-loading { display: flex; align-items: center; gap: var(--space-2, 10px); padding: var(--space-4, 24px); color: var(--fg-2, var(--fg)); }
9322
- .ds-247420 .fd-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2, 10px); padding: var(--space-5, 32px) var(--space-3, 16px); color: var(--fg-2, var(--fg)); text-align: center; }
10316
+ /* Base layout shared with .ds-empty-state (primitives.css); overrides padding
10317
+ (--space-5, denser than the shared --space-6) and color for this context. */
10318
+ .ds-247420 .fd-empty { padding: var(--space-5, 32px) var(--space-3, 16px); color: var(--fg-2, var(--fg)); }
9323
10319
  /* 28px is an ICON size (empty-state mark), not a type tier — the --fs-* ladder
9324
10320
  tops out at --fs-xl (21px) before every remaining rung becomes a fluid
9325
- heading clamp, so neither substitutes without visibly resizing the mark. */
9326
- .ds-247420 .fd-empty-glyph { font-size: 28px; opacity: .55; line-height: 1; }
9327
- .ds-247420 .fd-pre { margin: 0; padding: var(--space-2, 10px); background: var(--bg-3, var(--panel-bg-2)); border: var(--bw-hair, 1px) solid var(--rule, var(--panel-accent)); border-radius: var(--r-1, 6px); overflow: auto; max-height: 460px; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-micro); line-height: 1.5; }
10321
+ heading clamp, so neither substitutes without visibly resizing the mark.
10322
+ --fs-2xl bridges that gap as a fixed 28px token. */
10323
+ .ds-247420 .fd-empty-glyph { font-size: var(--fs-2xl); opacity: .55; line-height: 1; }
10324
+ .ds-247420 .fd-pre { margin: 0; padding: var(--space-2, 10px); background: var(--bg-3, var(--panel-bg-2)); border: var(--bw-hair, 1px) solid var(--rule, var(--panel-accent)); border-radius: var(--r-1, 8px); overflow: auto; max-height: 460px; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-micro); line-height: 1.5; }
9328
10325
  .ds-247420 .fd-skill-body { max-height: 320px; }
9329
10326
  .ds-247420 .fd-row-actions { display: inline-flex; gap: var(--space-1, 6px); align-items: center; }
9330
10327
  .ds-247420 .fd-chat { display: flex; flex-direction: column; gap: var(--space-2, 10px); height: 100%; min-height: 0; }
@@ -9341,9 +10338,105 @@
9341
10338
  }
9342
10339
 
9343
10340
  /* chat.css */
9344
- /* chat.css — chat-specific styles. Most chat styles live in app-shell.css under
9345
- the .chat-* prefix. This file carries the AgentChat kit styles (the reusable
9346
- multi-agent orchestration chat surface). */
10341
+ /* chat.css — chat-specific styles, including the base .chat-* rules used by
10342
+ Chat()/ChatMessage() (chat/threads.js, chat/message.js): .chat, .chat-head,
10343
+ .chat-thread, .chat-msg, .chat-avatar, .chat-bubble, .chat-stack,
10344
+ .chat-empty. This file also carries the AgentChat kit styles (the reusable
10345
+ multi-agent orchestration chat surface, .agentchat-*). */
10346
+
10347
+ /* Base chat surface — the default (non-flat) messenger layout: left/right
10348
+ avatar-disc + colored-bubble turns. .chat-msg-flat further down overrides
10349
+ these to the claude.ai/code full-width turn style; these are the rules it
10350
+ overrides FROM, and were previously entirely undefined, so the default
10351
+ layout rendered on raw flex/browser defaults (unstyled bubble shape, no
10352
+ avatar depth, no defined thread padding) until a Chat() consumer supplied
10353
+ its own colliding class names. */
10354
+ .ds-247420 .chat {
10355
+ display: flex;
10356
+ flex-direction: column;
10357
+ flex: 1;
10358
+ min-height: 0;
10359
+ }
10360
+ .ds-247420 .chat-head {
10361
+ display: flex;
10362
+ align-items: center;
10363
+ gap: var(--space-2);
10364
+ padding: var(--space-2-75) var(--space-3);
10365
+ border-bottom: 1px solid var(--panel-3, color-mix(in oklab, var(--fg) 10%, transparent));
10366
+ flex-shrink: 0;
10367
+ }
10368
+ .ds-247420 .chat-head .ds-chat-title { font-size: var(--fs-sm); font-weight: var(--fw-medium); margin: 0; }
10369
+ .ds-247420 .chat-head .sub { font-size: var(--fs-tiny); color: var(--fg-3); }
10370
+ .ds-247420 .chat-head .spread { flex: 1; }
10371
+ .ds-247420 .chat-thread {
10372
+ flex: 1;
10373
+ min-height: 0;
10374
+ overflow-y: auto;
10375
+ display: flex;
10376
+ flex-direction: column;
10377
+ gap: var(--space-3);
10378
+ padding: var(--space-3) var(--space-3) var(--space-4);
10379
+ /* A short thread must not stretch its one message across the whole
10380
+ viewport height — pack content to the bottom (where the composer is)
10381
+ instead of centering it in a sea of empty space above and below. */
10382
+ justify-content: flex-end;
10383
+ }
10384
+ .ds-247420 .chat-empty {
10385
+ margin: auto;
10386
+ max-width: var(--measure-narrow, 60ch);
10387
+ text-align: center;
10388
+ padding: var(--space-5) var(--space-3);
10389
+ color: var(--fg-3);
10390
+ }
10391
+ .ds-247420 .chat-empty-title { font-size: var(--fs-lg); font-weight: var(--fw-medium); color: var(--fg-2); margin: 0 0 var(--space-1); }
10392
+ .ds-247420 .chat-empty-sub { font-size: var(--fs-sm); margin: 0; }
10393
+ .ds-247420 .chat-msg {
10394
+ display: flex;
10395
+ gap: var(--space-2);
10396
+ align-items: flex-start;
10397
+ max-width: 100%;
10398
+ }
10399
+ .ds-247420 .chat-msg.you { flex-direction: row-reverse; }
10400
+ .ds-247420 .chat-msg.centered { justify-content: center; text-align: center; }
10401
+ .ds-247420 .chat-avatar {
10402
+ width: 32px;
10403
+ height: 32px;
10404
+ flex-shrink: 0;
10405
+ border-radius: 50%;
10406
+ background: var(--bg-3);
10407
+ color: var(--fg);
10408
+ display: inline-flex;
10409
+ align-items: center;
10410
+ justify-content: center;
10411
+ font-size: var(--fs-tiny);
10412
+ font-weight: var(--fw-medium);
10413
+ /* Depth over a flat solid-color disc: a hairline inset ring plus the same
10414
+ soft shadow --panel-shadow uses elsewhere, so avatars read as a real
10415
+ surface element instead of a wireframe placeholder. */
10416
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 10%, transparent), 0 1px 2px color-mix(in oklab, var(--fg) 10%, transparent);
10417
+ }
10418
+ .ds-247420 .chat-stack {
10419
+ display: flex;
10420
+ flex-direction: column;
10421
+ gap: var(--space-1);
10422
+ max-width: 70%;
10423
+ min-width: 0;
10424
+ }
10425
+ .ds-247420 .chat-msg.you .chat-stack { align-items: flex-end; }
10426
+ .ds-247420 .chat-bubble {
10427
+ padding: var(--space-2) var(--space-2-75);
10428
+ background: var(--bg-2);
10429
+ color: var(--fg);
10430
+ /* r-2, not a pill — a pill-capsule bubble read as a generic chat-template
10431
+ shape inconsistent with every other rounded element in the app (which
10432
+ all use the r-0..r-3 scale, never 999px). */
10433
+ border-radius: var(--r-2);
10434
+ line-height: var(--lh-base, 1.5);
10435
+ word-break: break-word;
10436
+ font-size: var(--fs-sm);
10437
+ }
10438
+ .ds-247420 .chat-msg.you .chat-bubble { background: var(--accent); color: var(--accent-fg); }
10439
+ .ds-247420 .chat-meta { display: flex; gap: var(--space-1-5); font-size: var(--fs-nano); color: var(--fg-3); padding: 0 var(--space-1); }
9347
10440
 
9348
10441
  .ds-247420 .agentchat {
9349
10442
  display: flex;
@@ -9587,7 +10680,7 @@
9587
10680
  .ds-247420 .chat-msg-action {
9588
10681
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1);
9589
10682
  min-width: 26px; height: 26px; padding: 0 var(--space-1-75); cursor: pointer;
9590
- border: none; border-radius: var(--r-1, 4px); background: none; color: var(--fg-3);
10683
+ border: none; border-radius: var(--r-1, 8px); background: none; color: var(--fg-3);
9591
10684
  font-family: var(--ff-body); font-size: var(--fs-tiny);
9592
10685
  }
9593
10686
  .ds-247420 .chat-msg-action-label { font-size: var(--fs-tiny); }
@@ -9655,7 +10748,7 @@
9655
10748
  display: flex; align-items: center; gap: var(--space-2, 8px);
9656
10749
  padding: var(--space-1, 4px) var(--space-2, 8px);
9657
10750
  border: var(--bw-hair) solid var(--rule);
9658
- border-radius: var(--r-1, 4px);
10751
+ border-radius: var(--r-1, 8px);
9659
10752
  background: var(--bg-2);
9660
10753
  }
9661
10754
  .ds-247420 .agentchat-install-agent { flex: 0 0 auto; font-size: var(--fs-tiny); color: var(--fg-3); min-width: 8ch; }
@@ -9665,7 +10758,7 @@
9665
10758
  }
9666
10759
  .ds-247420 .agentchat-install-copy {
9667
10760
  flex: 0 0 auto; cursor: pointer; padding: var(--space-hair) var(--space-2); min-height: 24px;
9668
- border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 4px);
10761
+ border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 8px);
9669
10762
  background: none; color: var(--fg-3);
9670
10763
  font-family: var(--ff-body); font-size: var(--fs-tiny);
9671
10764
  }
@@ -9676,7 +10769,7 @@
9676
10769
  /* Host-supplied transcript export actions riding the controls row. */
9677
10770
  .ds-247420 .agentchat-export-act {
9678
10771
  cursor: pointer; padding: var(--space-1) var(--space-2-5); min-height: 32px;
9679
- border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 4px);
10772
+ border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 8px);
9680
10773
  background: none; color: var(--fg-3);
9681
10774
  font-family: var(--ff-body); font-size: var(--fs-tiny);
9682
10775
  }
@@ -9872,7 +10965,7 @@
9872
10965
  .ds-247420 .ds-session-agent { font-size: var(--fs-tiny); color: var(--fg-3); }
9873
10966
  /* New-activity cue is a hollow accent RING so it stays shape-distinct from the
9874
10967
  running live disc (a solid pulsing accent dot) for colour-blind / reduced-motion users. */
9875
- .ds-247420 .ds-session-unread { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-2); display: inline-block; }
10968
+ .ds-247420 .ds-session-unread { width: 8px; height: 8px; border-radius: 50%; background: transparent; box-shadow: inset 0 0 0 1.5px var(--accent); display: inline-block; flex: none; }
9876
10969
  .ds-247420 .ds-session-state { padding: var(--space-5) var(--space-3); text-align: center; color: var(--fg-3); font-size: var(--fs-sm); }
9877
10970
  .ds-247420 .ds-session-state-error { color: var(--flame); }
9878
10971
 
@@ -9909,7 +11002,7 @@
9909
11002
  background: var(--bg); border: var(--border-w) solid var(--accent); border-radius: var(--r-1);
9910
11003
  padding: var(--space-1) var(--space-2);
9911
11004
  }
9912
- .ds-247420 .ds-session-rename-input:focus { outline: none; }
11005
+ .ds-247420 .ds-session-rename-input:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }
9913
11006
 
9914
11007
  /* Inline delete confirm — same row height, two flat buttons, no modal;
9915
11008
  mirrors SessionDashboard's arm-then-confirm bulk-stop control. */
@@ -9951,7 +11044,7 @@
9951
11044
  .ds-247420 .ds-dash-header .btn-primary.danger.is-armed { background: var(--warn); color: var(--ink); border-color: var(--warn); }
9952
11045
  .ds-247420 .ds-dash-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--space-2); padding: var(--space-3); }
9953
11046
  @media (min-width: 1500px) { .ds-247420 .ds-dash-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); } }
9954
- .ds-247420 .ds-dash-card { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-2); }
11047
+ .ds-247420 .ds-dash-card { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1); }
9955
11048
 
9956
11049
  /* Compact density — one line per session. The comfortable card stacks title +
9957
11050
  head + meta + actions as four flex rows and measured 208px tall on a real
@@ -10119,7 +11212,7 @@
10119
11212
  position: absolute; top: 6px; right: 6px;
10120
11213
  display: inline-flex; align-items: center; justify-content: center;
10121
11214
  min-width: 48px; min-height: 24px; padding: var(--space-hair) var(--space-2);
10122
- border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 4px);
11215
+ border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 8px);
10123
11216
  background: var(--bg-2); color: var(--fg-2); cursor: pointer;
10124
11217
  font-family: var(--ff-body); font-size: var(--fs-tiny);
10125
11218
  opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease);
@@ -10786,7 +11879,7 @@
10786
11879
  /* Minimap internal order: centerline < viewport < dot < tooltip. All four
10787
11880
  live inside the one raised widget, so they share its rung and differ
10788
11881
  only by a local offset. */
10789
- z-index: calc(var(--z-raised) + 1);
11882
+ z-index: var(--z-raised-1);
10790
11883
  }
10791
11884
  .ds-247420 .chat-minimap-centerline {
10792
11885
  position: absolute;
@@ -10806,7 +11899,7 @@
10806
11899
  border-radius: var(--r-pill, 999px);
10807
11900
  transform: translate(-50%, -50%) scale(1);
10808
11901
  transition: transform 0.1s;
10809
- z-index: calc(var(--z-raised) + 2);
11902
+ z-index: var(--z-raised-2);
10810
11903
  }
10811
11904
  .ds-247420 .chat-minimap-dot.is-user {
10812
11905
  background: color-mix(in oklab, var(--accent) 18%, transparent);
@@ -10831,7 +11924,7 @@
10831
11924
  /* Top of the minimap's internal order. Deliberately NOT --z-tooltip: it is
10832
11925
  clipped to the minimap rail rather than floating over the app, so it must
10833
11926
  not outrank real overlays that may cover the rail. */
10834
- z-index: calc(var(--z-raised) + 3);
11927
+ z-index: var(--z-raised-3);
10835
11928
  pointer-events: none;
10836
11929
  opacity: 0.45;
10837
11930
  font-size: var(--fs-tiny);
@@ -10913,7 +12006,7 @@
10913
12006
  /* Buttons inside the dense strip shrink to fit the 26px band. */
10914
12007
  .ds-247420 .ds-ep-statusbar button {
10915
12008
  min-height: 0; height: 20px; padding: 0 var(--space-2);
10916
- border-radius: var(--r-1, 4px);
12009
+ border-radius: var(--r-1, 8px);
10917
12010
  font: var(--fs-micro, 11px)/1 var(--ff-mono, monospace);
10918
12011
  }
10919
12012
 
@@ -10945,7 +12038,7 @@
10945
12038
  border: 1px solid var(--rule);
10946
12039
  color: var(--panel-text, var(--fg));
10947
12040
  padding: var(--space-1-5, 5px) var(--space-2-5, 10px);
10948
- border-radius: var(--r-1, 4px);
12041
+ border-radius: var(--r-1, 8px);
10949
12042
  font-size: var(--fs-tiny, 12px);
10950
12043
  font-family: inherit;
10951
12044
  }
@@ -11063,7 +12156,7 @@
11063
12156
  .ds-247420 .ds-ep-tree-row {
11064
12157
  display: flex; align-items: center; gap: var(--space-1-75);
11065
12158
  padding: var(--space-1) var(--space-2) var(--space-1) 0;
11066
- border-radius: var(--r-1, 10px);
12159
+ border-radius: var(--r-1, 8px);
11067
12160
  cursor: pointer;
11068
12161
  color: var(--panel-text-2);
11069
12162
  transition: background var(--dur-snap, 80ms) var(--ease, ease), color var(--dur-snap, 80ms) var(--ease, ease);
@@ -11125,7 +12218,7 @@
11125
12218
  background: var(--panel-2);
11126
12219
  color: var(--panel-text);
11127
12220
  border: 1px solid var(--rule);
11128
- border-radius: var(--r-1, 10px);
12221
+ border-radius: var(--r-1, 8px);
11129
12222
  padding: var(--space-1) var(--space-2);
11130
12223
  font: inherit;
11131
12224
  min-width: 0; width: 100%;
@@ -11166,7 +12259,7 @@
11166
12259
  .ds-247420 .ds-ep-propfield-value input,
11167
12260
  .ds-247420 .ds-ep-propfield-value .ds-input-bare {
11168
12261
  padding: var(--space-hair) var(--space-1-75);
11169
- border-radius: var(--r-1, 4px);
12262
+ border-radius: var(--r-1, 8px);
11170
12263
  font: var(--fs-tiny, 12px)/1.3 var(--ff-mono, monospace);
11171
12264
  }
11172
12265
  .ds-247420 .ds-input-bare {
@@ -11174,7 +12267,7 @@
11174
12267
  background: var(--panel-2);
11175
12268
  color: var(--panel-text);
11176
12269
  border: 1px solid var(--rule);
11177
- border-radius: var(--r-1, 4px);
12270
+ border-radius: var(--r-1, 8px);
11178
12271
  padding: var(--space-hair) var(--space-1-75);
11179
12272
  font: var(--fs-tiny, 12px)/1.3 var(--ff-mono, monospace);
11180
12273
  min-width: 0;
@@ -11216,7 +12309,7 @@
11216
12309
  border: 1px solid transparent;
11217
12310
  color: inherit; font: inherit;
11218
12311
  width: 100%;
11219
- border-radius: var(--r-1, 4px);
12312
+ border-radius: var(--r-1, 8px);
11220
12313
  padding: var(--space-hair, 2px) var(--space-1, 4px);
11221
12314
  }
11222
12315
  .ds-247420 .ds-ep-inline-input.ds-ep-inline-input:focus-visible {
@@ -11243,7 +12336,7 @@
11243
12336
  width: 100%;
11244
12337
  /* Snapped to --ctl-sm (was a lone 28px literal that happened to match). */
11245
12338
  min-height: var(--ctl-sm); height: var(--ctl-sm); padding: 0 var(--space-2-5);
11246
- border-radius: var(--r-1, 4px);
12339
+ border-radius: var(--r-1, 8px);
11247
12340
  font: var(--fs-tiny, 12px)/1 var(--ff-mono, monospace);
11248
12341
  }
11249
12342
 
@@ -11253,7 +12346,7 @@
11253
12346
  .ds-247420 .ds-ep-tree-row button:not(.ds-ep-btngrp-btn),
11254
12347
  .ds-247420 .ds-ep-toolbar button:not(.ds-ep-btngrp-btn) {
11255
12348
  min-height: 24px; height: 24px; padding: 0 var(--space-2);
11256
- border-radius: var(--r-1, 4px);
12349
+ border-radius: var(--r-1, 8px);
11257
12350
  font: var(--fs-tiny, 12px)/1 var(--ff-mono, monospace);
11258
12351
  }
11259
12352
  @media (pointer: coarse) {
@@ -11280,7 +12373,7 @@
11280
12373
  border: 1px solid var(--rule);
11281
12374
  color: var(--panel-text, var(--fg));
11282
12375
  padding: var(--space-half, 3px) var(--space-2-5, 10px);
11283
- border-radius: var(--r-1, 4px);
12376
+ border-radius: var(--r-1, 8px);
11284
12377
  font: inherit;
11285
12378
  }
11286
12379
  .ds-247420 .ds-ep-pager-btn:hover:not([disabled]) { background: color-mix(in oklab, var(--accent) 10%, transparent); }
@@ -11298,7 +12391,7 @@
11298
12391
  computed to 23px -- just under the target floor. */
11299
12392
  min-width: max(24px, 1.75em);
11300
12393
  padding: var(--space-half, 3px) var(--space-1, 4px);
11301
- border-radius: var(--r-1, 4px);
12394
+ border-radius: var(--r-1, 8px);
11302
12395
  font: inherit;
11303
12396
  text-align: center;
11304
12397
  }
@@ -11377,7 +12470,7 @@
11377
12470
  background: var(--panel-2, var(--bg-2));
11378
12471
  color: var(--panel-text, var(--fg));
11379
12472
  padding: var(--space-2, 8px);
11380
- border-radius: var(--r-1, var(--r-2, 4px));
12473
+ border-radius: var(--r-1, 8px);
11381
12474
  max-height: 300px;
11382
12475
  overflow-y: auto;
11383
12476
  margin: 0;
@@ -11405,7 +12498,7 @@
11405
12498
  .ds-247420 .ds-ep-json-sum {
11406
12499
  cursor: pointer; user-select: none;
11407
12500
  list-style: none;
11408
- border-radius: var(--r-1, 4px);
12501
+ border-radius: var(--r-1, 8px);
11409
12502
  }
11410
12503
  .ds-247420 .ds-ep-json-sum::-webkit-details-marker { display: none; }
11411
12504
  .ds-247420 .ds-ep-json-sum::before {
@@ -11435,7 +12528,7 @@
11435
12528
  color: var(--panel-text-2, var(--fg-2));
11436
12529
  font: var(--fs-micro, 11px)/1 var(--ff-mono, monospace);
11437
12530
  padding: var(--space-half, 3px) var(--space-2, 8px);
11438
- border-radius: var(--r-1, 4px);
12531
+ border-radius: var(--r-1, 8px);
11439
12532
  opacity: 0.75;
11440
12533
  }
11441
12534
  .ds-247420 .ds-ep-json-copy:hover { opacity: 1; border-color: var(--accent); }
@@ -11482,7 +12575,7 @@
11482
12575
  .ds-247420 .ds-ep-btngrp {
11483
12576
  display: inline-flex;
11484
12577
  border: 1px solid var(--rule);
11485
- border-radius: var(--r-1, 10px);
12578
+ border-radius: var(--r-1, 8px);
11486
12579
  overflow: hidden;
11487
12580
  background: var(--panel-2);
11488
12581
  }
@@ -11569,7 +12662,7 @@
11569
12662
  padding: var(--space-2) var(--space-2-5);
11570
12663
  background: transparent; border: 0;
11571
12664
  color: inherit; font: inherit; text-align: left;
11572
- border-radius: var(--r-1, 4px);
12665
+ border-radius: var(--r-1, 8px);
11573
12666
  cursor: pointer;
11574
12667
  }
11575
12668
  .ds-247420 .ds-ep-ctxmenu-item:hover:not(.disabled),
@@ -11644,7 +12737,7 @@
11644
12737
  min-height: var(--ctl-sm); padding: 0 var(--space-3);
11645
12738
  display: inline-flex; align-items: center; justify-content: center;
11646
12739
  border: 1px solid var(--rule);
11647
- border-radius: var(--r-1, 6px);
12740
+ border-radius: var(--r-1, 8px);
11648
12741
  background: var(--panel-2, transparent);
11649
12742
  color: inherit; font: inherit; cursor: pointer;
11650
12743
  }
@@ -11681,7 +12774,7 @@
11681
12774
  .kind-error below recolour border-left-color and nothing else. Do not
11682
12775
  demote to a hairline; that would erase the status signal. */
11683
12776
  border-left: var(--bw-chunk) solid var(--accent);
11684
- border-radius: var(--r-1, 6px);
12777
+ border-radius: var(--r-1, 8px);
11685
12778
  padding: var(--space-2) var(--space-2-75);
11686
12779
  box-shadow: var(--shadow-3);
11687
12780
  min-width: 220px; max-width: 360px;
@@ -11704,7 +12797,7 @@
11704
12797
  font: inherit;
11705
12798
  font-weight: 600;
11706
12799
  padding: var(--space-1) var(--space-1-75);
11707
- border-radius: var(--r-1, 6px);
12800
+ border-radius: var(--r-1, 8px);
11708
12801
  cursor: pointer;
11709
12802
  }
11710
12803
  .ds-247420 .ds-ep-toast-action:hover { background: var(--panel-2, var(--panel-bg)); }
@@ -11864,7 +12957,7 @@
11864
12957
  outline: var(--focus-w) solid var(--focus-color);
11865
12958
  outline-offset: var(--focus-offset);
11866
12959
  }
11867
- .ds-247420 .ds-toggle:disabled { cursor: not-allowed; }
12960
+ .ds-247420 .ds-toggle:disabled { cursor: not-allowed; opacity: 0.5; }
11868
12961
 
11869
12962
  @media (prefers-reduced-motion: no-preference) {
11870
12963
  .ds-247420 .ds-toggle { transition: background var(--dur-base, 160ms) var(--ease), border-color var(--dur-base, 160ms) var(--ease); }
@@ -11907,7 +13000,7 @@
11907
13000
  background: var(--panel-1, var(--panel-bg));
11908
13001
  color: var(--panel-text, var(--fg-text));
11909
13002
  border: 1px solid var(--rule);
11910
- border-radius: var(--r-1, 6px);
13003
+ border-radius: var(--r-1, 8px);
11911
13004
  padding: var(--space-1-75) var(--space-2-5);
11912
13005
  font-size: var(--fs-micro);
11913
13006
  line-height: 1.4;
@@ -11960,7 +13053,7 @@
11960
13053
  padding: var(--space-1-5) var(--space-2-75);
11961
13054
  background: transparent; border: 0;
11962
13055
  color: inherit; font: inherit; text-align: left;
11963
- border-radius: var(--r-1, 4px);
13056
+ border-radius: var(--r-1, 8px);
11964
13057
  cursor: pointer;
11965
13058
  }
11966
13059
  .ds-247420 .ds-dropdown-item:hover,
@@ -11989,7 +13082,7 @@
11989
13082
  background: var(--panel-2, transparent);
11990
13083
  color: inherit; font: inherit;
11991
13084
  border: 1px solid var(--rule);
11992
- border-radius: var(--r-1, 6px);
13085
+ border-radius: var(--r-1, 8px);
11993
13086
  padding: 0 var(--space-2-75);
11994
13087
  cursor: pointer;
11995
13088
  /* Snapped to the --ctl-sm rung so this small control shares the height
@@ -12012,7 +13105,7 @@
12012
13105
  padding: var(--space-1-5) var(--space-2-75);
12013
13106
  background: transparent; border: 0;
12014
13107
  color: inherit; font: inherit; text-align: left;
12015
- border-radius: var(--r-1, 4px);
13108
+ border-radius: var(--r-1, 8px);
12016
13109
  cursor: pointer;
12017
13110
  width: 100%;
12018
13111
  }
@@ -12039,7 +13132,7 @@
12039
13132
  }
12040
13133
  .ds-247420 .ov-menubutton-retry {
12041
13134
  appearance: none; background: transparent; border: 1px solid var(--rule);
12042
- border-radius: var(--r-1, 4px); color: inherit; font: inherit; font-size: var(--fs-sm);
13135
+ border-radius: var(--r-1, 8px); color: inherit; font: inherit; font-size: var(--fs-sm);
12043
13136
  padding: var(--space-hair) var(--space-1-5); cursor: pointer;
12044
13137
  }
12045
13138
  .ds-247420 .ov-menubutton-retry:hover, .ds-247420 .ov-menubutton-retry:focus-visible { background: var(--panel-2, var(--rule)); outline: none; }
@@ -12048,7 +13141,7 @@
12048
13141
  background: var(--panel-2, transparent);
12049
13142
  color: inherit; font: inherit;
12050
13143
  border: 1px solid var(--rule);
12051
- border-radius: var(--r-1, 6px);
13144
+ border-radius: var(--r-1, 8px);
12052
13145
  padding: 0 var(--space-2-75);
12053
13146
  cursor: pointer;
12054
13147
  min-height: var(--ctl-sm); height: var(--ctl-sm);
@@ -12066,7 +13159,7 @@
12066
13159
  .ds-247420 .ds-ep-diagnostics-title { margin: 0; font-size: var(--fs-lg); }
12067
13160
  .ds-247420 .ds-ep-diagnostics-refresh {
12068
13161
  appearance: none; background: var(--panel-2, transparent); color: inherit; font: inherit;
12069
- border: 1px solid var(--rule); border-radius: var(--r-1, 6px);
13162
+ border: 1px solid var(--rule); border-radius: var(--r-1, 8px);
12070
13163
  padding: var(--space-1) var(--space-2-75); cursor: pointer;
12071
13164
  }
12072
13165
  .ds-247420 .ds-ep-diagnostics-refresh:disabled { opacity: 0.6; cursor: not-allowed; }
@@ -12088,7 +13181,7 @@
12088
13181
  .ds-247420 .ds-reorderable-horizontal { flex-direction: row; }
12089
13182
  .ds-247420 .ds-reorder-item {
12090
13183
  display: flex; align-items: center; gap: var(--space-2, 8px);
12091
- padding: var(--space-1, 4px); border-radius: var(--r-1, 10px);
13184
+ padding: var(--space-1, 4px); border-radius: var(--r-1, 8px);
12092
13185
  background: var(--panel-1); color: var(--panel-text);
12093
13186
  transition: transform var(--dur-snap, 120ms) var(--ease, ease), opacity var(--dur-snap, 120ms) var(--ease, ease);
12094
13187
  }
@@ -12096,7 +13189,7 @@
12096
13189
  min-width: 44px; min-height: 44px;
12097
13190
  display: inline-flex; align-items: center; justify-content: center;
12098
13191
  appearance: none; background: transparent; color: var(--panel-text-2, var(--panel-text));
12099
- border: 0; border-radius: var(--r-1, 10px);
13192
+ border: 0; border-radius: var(--r-1, 8px);
12100
13193
  cursor: grab; font: 14px/1 var(--ff-mono, monospace); letter-spacing: -2px;
12101
13194
  touch-action: none; user-select: none;
12102
13195
  }
@@ -12120,7 +13213,7 @@
12120
13213
  display: inline-block; font: 0.85em/1 var(--ff-mono, monospace);
12121
13214
  padding: var(--space-1, 4px) calc(var(--space-1, 4px) * 1.5);
12122
13215
  border: 1px solid var(--rule, var(--panel-text-3));
12123
- border-radius: var(--r-1, 10px);
13216
+ border-radius: var(--r-1, 8px);
12124
13217
  background: var(--panel-2, var(--panel-1)); color: var(--panel-text);
12125
13218
  }
12126
13219
  .ds-247420 .ds-kbd-group { margin: var(--space-3, 16px) 0; }
@@ -12174,7 +13267,7 @@
12174
13267
  padding: var(--space-2, 8px) var(--space-3, 16px);
12175
13268
  background: transparent; border: 0;
12176
13269
  color: var(--fg); font: inherit; text-align: left;
12177
- border-radius: var(--r-1, 10px); cursor: pointer;
13270
+ border-radius: var(--r-1, 8px); cursor: pointer;
12178
13271
  }
12179
13272
  .ds-247420 .ov-cmd-item:hover { background: var(--panel-2, var(--rule)); }
12180
13273
  .ds-247420 .ov-cmd-item.is-active { background: var(--accent); color: var(--accent-fg); }
@@ -12202,6 +13295,14 @@
12202
13295
  box-shadow: var(--shadow-3);
12203
13296
  outline: none; overflow: hidden;
12204
13297
  }
13298
+ .ds-247420 .ov-emoji-search {
13299
+ flex-shrink: 0; margin: var(--space-2, 8px); margin-bottom: 0;
13300
+ padding: var(--space-1-5, 6px) var(--space-2, 8px);
13301
+ border: 1px solid var(--rule); border-radius: var(--r-1, 8px);
13302
+ background: var(--bg-1, var(--bg-3)); color: var(--fg);
13303
+ font: inherit; font-size: var(--fs-sm, 13px);
13304
+ }
13305
+ .ds-247420 .ov-emoji-search:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; }
12205
13306
  .ds-247420 .ov-emoji-tabs {
12206
13307
  display: flex; flex-shrink: 0;
12207
13308
  border-bottom: 1px solid var(--rule);
@@ -12214,16 +13315,21 @@
12214
13315
  }
12215
13316
  .ds-247420 .ov-emoji-tab:hover { background: var(--bg-2); }
12216
13317
  .ds-247420 .ov-emoji-tab[aria-selected="true"] { border-bottom-color: var(--accent); background: var(--bg-2); }
13318
+ .ds-247420 .ov-emoji-tab:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
12217
13319
  .ds-247420 .ov-emoji-grid { overflow: auto; max-height: 220px; padding: var(--space-2, 8px); }
12218
13320
  .ds-247420 .ov-emoji-grid-inner { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--space-1, 4px); }
12219
13321
  .ds-247420 .ov-emoji-cell {
12220
13322
  appearance: none; border: 0; background: transparent;
12221
13323
  aspect-ratio: 1; font-size: var(--fs-lg, 18px);
12222
- border-radius: var(--r-1, 10px); cursor: pointer;
13324
+ border-radius: var(--r-1, 8px); cursor: pointer;
12223
13325
  display: inline-flex; align-items: center; justify-content: center;
12224
13326
  }
12225
- .ds-247420 .ov-emoji-cell:hover, .ds-247420 .ov-emoji-cell:focus-visible {
12226
- background: color-mix(in oklab, var(--accent) 18%, transparent); outline: none;
13327
+ .ds-247420 .ov-emoji-cell:hover {
13328
+ background: color-mix(in oklab, var(--accent) 18%, transparent);
13329
+ }
13330
+ .ds-247420 .ov-emoji-cell:focus-visible {
13331
+ background: color-mix(in oklab, var(--accent) 18%, transparent);
13332
+ outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px;
12227
13333
  }
12228
13334
 
12229
13335
  /* ---------------------------------------------------------------
@@ -12309,16 +13415,19 @@
12309
13415
  .ds-247420 .ov-set-control {
12310
13416
  appearance: none; flex-shrink: 0; max-width: 55%;
12311
13417
  background: var(--bg-3); color: var(--fg);
12312
- border: 1px solid var(--rule); border-radius: var(--r-1, 10px);
13418
+ border: 1px solid var(--rule); border-radius: var(--r-1, 8px);
12313
13419
  padding: var(--space-1, 4px) var(--space-2, 8px); font: inherit;
12314
13420
  }
12315
13421
  .ds-247420 .ov-set-control:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; border-color: var(--accent-ink); }
12316
13422
  .ds-247420 input.ov-set-control[type="range"] { padding: 0; accent-color: var(--accent); }
12317
13423
  .ds-247420 .ov-set-toggle { width: 18px; height: 18px; accent-color: var(--accent); flex-shrink: 0; cursor: pointer; }
13424
+ @media (pointer: coarse) {
13425
+ .ds-247420 .ov-set-toggle { width: 24px; height: 24px; }
13426
+ }
12318
13427
  .ds-247420 .ov-set-btn {
12319
13428
  width: 100%; appearance: none; cursor: pointer;
12320
13429
  background: var(--accent); color: var(--accent-fg);
12321
- border: 0; border-radius: var(--r-1, 10px);
13430
+ border: 0; border-radius: var(--r-1, 8px);
12322
13431
  padding: var(--space-2, 8px); font: inherit;
12323
13432
  }
12324
13433
 
@@ -12360,7 +13469,7 @@
12360
13469
  .ds-247420 .ov-lightbox-x { position: absolute; top: 16px; right: 16px; width: 40px; height: 40px; border: none; border-radius: 50%; background: color-mix(in oklab, var(--on-color) 12%, transparent); color: var(--on-color); font-size: 24px; line-height: 1; cursor: pointer; }
12361
13470
  .ds-247420 .ov-lightbox-x:hover { background: color-mix(in oklab, var(--on-color) 24%, transparent); }
12362
13471
  .ds-247420 .ov-lightbox-stage { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); max-width: 92vw; max-height: 90vh; }
12363
- .ds-247420 .ov-lightbox-video { max-width: 92vw; max-height: 82vh; border-radius: var(--r-1); background: #000; /* true black media letterbox, intentional */ }
13472
+ .ds-247420 .ov-lightbox-video { max-width: 92vw; max-height: 82vh; border-radius: var(--r-1); background: var(--media-letterbox); /* true black media letterbox, intentional */ }
12364
13473
  .ds-247420 .ov-lightbox-label { color: var(--on-color); font-size: var(--fs-tiny); opacity: .85; }
12365
13474
 
12366
13475
  /* ---------------------------------------------------------------
@@ -12404,7 +13513,7 @@
12404
13513
  .ds-247420 .ds-input-color {
12405
13514
  appearance: none; -webkit-appearance: none;
12406
13515
  width: 28px; height: 22px; padding: 0;
12407
- border: 1px solid var(--rule); border-radius: var(--r-1, 4px);
13516
+ border: 1px solid var(--rule); border-radius: var(--r-1, 8px);
12408
13517
  background: var(--panel-2); cursor: pointer; flex-shrink: 0;
12409
13518
  }
12410
13519
  .ds-247420 .ds-input-color::-webkit-color-swatch-wrapper { padding: var(--space-hair); }
@@ -12413,7 +13522,7 @@
12413
13522
  .ds-247420 .ds-input-check {
12414
13523
  appearance: none; -webkit-appearance: none;
12415
13524
  width: 16px; height: 16px; flex-shrink: 0;
12416
- border: 1px solid var(--rule); border-radius: var(--r-1, 4px);
13525
+ border: 1px solid var(--rule); border-radius: var(--r-1, 8px);
12417
13526
  background: var(--panel-2); cursor: pointer; position: relative;
12418
13527
  }
12419
13528
  .ds-247420 .ds-input-check:checked { background: var(--accent); border-color: var(--accent); }
@@ -12499,7 +13608,7 @@
12499
13608
  /* Collapse / CollapseGroup — progressive-disclosure toggle panel. */
12500
13609
  .ds-247420 .ds-ep-collapse {
12501
13610
  border: 1px solid var(--rule);
12502
- border-radius: var(--r-1, 4px);
13611
+ border-radius: var(--r-1, 8px);
12503
13612
  background: var(--panel-2, var(--bg-2));
12504
13613
  }
12505
13614
  .ds-247420 .ds-ep-collapse + .ds-ep-collapse { margin-top: var(--space-1, 4px); }
@@ -12562,7 +13671,7 @@
12562
13671
  padding: var(--space-2, 8px) var(--space-3, 16px);
12563
13672
  background: transparent; border: 0;
12564
13673
  color: var(--fg); font: inherit; text-align: left;
12565
- border-radius: var(--r-1, 10px); cursor: pointer;
13674
+ border-radius: var(--r-1, 8px); cursor: pointer;
12566
13675
  }
12567
13676
  .ds-247420 .ov-perm-item:hover { background: var(--panel-2, var(--rule)); }
12568
13677
  .ds-247420 .ov-perm-item[aria-checked="true"] { color: var(--accent-ink); }
@@ -12577,7 +13686,7 @@
12577
13686
  .ds-247420 .ov-perm-action {
12578
13687
  flex: 1; appearance: none; border: 1px solid var(--rule); background: transparent;
12579
13688
  color: var(--fg-2); font: inherit; font-size: var(--fs-tiny, 12px);
12580
- padding: var(--space-1) var(--space-2); border-radius: var(--r-1, 10px); cursor: pointer;
13689
+ padding: var(--space-1) var(--space-2); border-radius: var(--r-1, 8px); cursor: pointer;
12581
13690
  }
12582
13691
  .ds-247420 .ov-perm-action:hover { background: var(--panel-2, var(--rule)); }
12583
13692
 
@@ -12600,14 +13709,14 @@
12600
13709
  .ds-247420 .ov-approval-cat { color: var(--fg-3); font-weight: 400; }
12601
13710
  .ds-247420 .ov-approval-args {
12602
13711
  margin: 0; padding: var(--space-2, 8px);
12603
- background: var(--panel-2, var(--rule)); border-radius: var(--r-1, 10px);
13712
+ background: var(--panel-2, var(--rule)); border-radius: var(--r-1, 8px);
12604
13713
  font: var(--fs-tiny, 12px)/1.4 var(--ff-mono, monospace);
12605
13714
  color: var(--fg-3); max-height: 100px; overflow: auto; white-space: pre-wrap; word-break: break-all;
12606
13715
  }
12607
13716
  .ds-247420 .ov-approval-note {
12608
13717
  width: 100%; min-height: 48px; resize: vertical;
12609
13718
  padding: var(--space-2, 8px) var(--space-2-75, 12px);
12610
- border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1, 10px);
13719
+ border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1, 8px);
12611
13720
  background: var(--bg); color: var(--fg); font: inherit; box-sizing: border-box;
12612
13721
  }
12613
13722
  .ds-247420 .ov-approval-note:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
@@ -12691,6 +13800,48 @@
12691
13800
  padding: 0;
12692
13801
  }
12693
13802
 
13803
+ /* app topbar — mountCommunityApp's own sole chrome above the chat-head; had
13804
+ no rules anywhere (relying on raw flex defaults), which read as unaligned
13805
+ default spacing rather than a designed header row. */
13806
+ .ds-247420.ds-247420 .app-topbar {
13807
+ display: flex;
13808
+ align-items: center;
13809
+ gap: var(--space-3);
13810
+ height: 48px;
13811
+ flex-shrink: 0;
13812
+ padding: 0 var(--space-3);
13813
+ background: var(--bg-2);
13814
+ border-bottom: 1px solid var(--panel-3, color-mix(in oklab, var(--fg) 10%, transparent));
13815
+ }
13816
+ .ds-247420.ds-247420 .app-topbar .brand {
13817
+ display: flex;
13818
+ align-items: baseline;
13819
+ gap: var(--space-1);
13820
+ min-width: 0;
13821
+ font-weight: var(--fw-medium, 500);
13822
+ font-size: var(--fs-sm);
13823
+ color: var(--fg);
13824
+ overflow: hidden;
13825
+ white-space: nowrap;
13826
+ text-overflow: ellipsis;
13827
+ }
13828
+ .ds-247420.ds-247420 .app-topbar .brand .slash { color: var(--fg-3); }
13829
+ .ds-247420.ds-247420 .app-topbar nav {
13830
+ display: flex;
13831
+ align-items: center;
13832
+ gap: var(--space-2);
13833
+ }
13834
+ .ds-247420.ds-247420 .app-topbar nav a {
13835
+ color: var(--fg-2);
13836
+ font-size: var(--fs-tiny);
13837
+ text-decoration: none;
13838
+ padding: var(--space-1) var(--space-1-5);
13839
+ border-radius: var(--r-1);
13840
+ transition: background var(--dur-snap, .12s) var(--ease, ease), color var(--dur-snap, .12s) var(--ease, ease);
13841
+ }
13842
+ .ds-247420.ds-247420 .app-topbar nav a:hover { color: var(--fg); background: color-mix(in oklab, var(--fg) 8%, transparent); }
13843
+ .ds-247420.ds-247420 .app-topbar nav a:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 2px; }
13844
+
12694
13845
  /* server + channel rail */
12695
13846
  .ds-247420.ds-247420 .ca-rail {
12696
13847
  display: flex;
@@ -12706,7 +13857,7 @@
12706
13857
  }
12707
13858
  .ds-247420.ds-247420 .ca-rail .group {
12708
13859
  font-size: 11px;
12709
- font-weight: 600;
13860
+ font-weight: var(--fw-semibold, 600);
12710
13861
  letter-spacing: 0.04em;
12711
13862
  text-transform: uppercase;
12712
13863
  color: var(--fg-3, color-mix(in oklab, var(--fg) 55%, transparent));
@@ -12766,7 +13917,7 @@
12766
13917
  background: var(--accent);
12767
13918
  color: var(--on-accent);
12768
13919
  font-size: 11px;
12769
- font-weight: 600;
13920
+ font-weight: var(--fw-semibold, 600);
12770
13921
  text-align: center;
12771
13922
  }
12772
13923
  .ds-247420.ds-247420 .ca-rail .rail-empty {
@@ -12977,7 +14128,7 @@
12977
14128
  .ds-247420 .health-summary { display: flex; flex-wrap: wrap; gap: .4em; margin: .6em 0; }
12978
14129
  .ds-247420 .health-chip {
12979
14130
  font-family: var(--ff-mono, ui-monospace, monospace);
12980
- font-size: .8rem; padding: .15em .55em; border-radius: var(--r-1, 6px);
14131
+ font-size: .8rem; padding: .15em .55em; border-radius: var(--r-1, 8px);
12981
14132
  background: color-mix(in srgb, var(--fg) 8%, transparent);
12982
14133
  color: var(--fg-2);
12983
14134
  border: var(--bw-hair, 1px) solid color-mix(in srgb, var(--fg) 12%, transparent);
@@ -12995,7 +14146,7 @@
12995
14146
  .ds-247420 .ds-status-chip {
12996
14147
  display: inline-flex; align-items: center; gap: .35em;
12997
14148
  font-family: var(--ff-mono, ui-monospace, monospace);
12998
- font-size: .8rem; padding: .15em .55em; border-radius: var(--r-1, 6px);
14149
+ font-size: .8rem; padding: .15em .55em; border-radius: var(--r-1, 8px);
12999
14150
  background: color-mix(in srgb, var(--fg) 8%, transparent);
13000
14151
  color: var(--fg-2);
13001
14152
  border: var(--bw-hair, 1px) solid color-mix(in srgb, var(--fg) 12%, transparent);
@@ -13023,7 +14174,7 @@
13023
14174
  }
13024
14175
  .ds-247420 .cwd-bar-btn {
13025
14176
  cursor: pointer; font: inherit; line-height: 1.3;
13026
- padding: .15em .55em; border-radius: var(--r-1, 6px);
14177
+ padding: .15em .55em; border-radius: var(--r-1, 8px);
13027
14178
  background: color-mix(in srgb, var(--fg) 8%, transparent);
13028
14179
  border: var(--bw-hair, 1px) solid color-mix(in srgb, var(--fg) 14%, transparent);
13029
14180
  color: var(--fg-2);
@@ -13111,7 +14262,7 @@
13111
14262
  /* Re-assert the paper-tuned signal tokens so token-derived foregrounds
13112
14263
  (status discs, error text, tool cards) print with light-theme colours
13113
14264
  rather than the dark-theme-derived values inherited under auto-dark. */
13114
- .ds-247420 { --flame:#C53E00; --amber:#7C570F; --warn:#E0241A; --sky:#3A6EFF; --bg:var(--paper); --fg:var(--ink); }
14265
+ .ds-247420 { --flame:#C53E00; --amber:#7C570F; --warn:#C41C0C; --sky:#3A6EFF; --bg:var(--paper); --fg:var(--ink); }
13115
14266
  .ds-247420 #app { min-height: auto; display: block; height: auto; }
13116
14267
  .ds-247420 .skip-link, .ds-247420 .status-dot, .ds-247420 .history-actions, .ds-247420 .chat-composer { display: none !important; }
13117
14268
  .ds-247420 .app, .ds-247420 .app-main, .ds-247420 .panel, .ds-247420 .chat, .ds-247420 .chat-thread {
@@ -13143,20 +14294,12 @@
13143
14294
  .ds-247420 .fd-page-error { max-height: 60vh; overflow: auto; white-space: pre-wrap; word-break: break-all; }
13144
14295
 
13145
14296
  /* gm-prose.css */
13146
- /* ============================================================
13147
- gm-prose -- long-form article + doc-site prose surfaces
13148
- Extracted-article host, sticky table-of-contents rail, CLI
13149
- transcript block, definition-panel rows, repo cards, tag
13150
- chips, and attributed callouts. Consumed pro-rata by doc
13151
- sites (the gm paper) that mount the SDK and inject a flatspace
13152
- article into #ds-article-host. Every color is a token from
13153
- colors_and_type.css; no raw literals.
13154
- ============================================================ */
13155
-
13156
14297
  .ds-247420 .ds-prose { line-height: 1.65; }
13157
14298
  .ds-247420 .ds-prose a { color: var(--panel-accent); }
13158
14299
  .ds-247420 .ds-prose a:hover { background: none; color: var(--panel-accent); text-decoration-color: transparent; }
13159
14300
  .ds-247420 .ds-prose pre, .ds-247420 .ds-prose table { overflow-x: auto; max-width: 100%; }
14301
+ .ds-247420 .ds-prose .mermaid { max-width: 100%; }
14302
+ .ds-247420 .ds-prose .mermaid svg { max-width: 100%; height: auto; cursor: zoom-in; }
13160
14303
  .ds-247420 .ds-prose canvas {
13161
14304
  display: block;
13162
14305
  width: 100%;
@@ -13172,33 +14315,33 @@
13172
14315
  min-height: 0;
13173
14316
  }
13174
14317
 
14318
+ .ds-247420 {
14319
+ --article-toc-rail-w: 256px;
14320
+ --article-toc-gutter-nonrhythm: 44px;
14321
+ --article-toc-header-gap: 16px;
14322
+ }
13175
14323
  .ds-247420 #ds-article-host.has-toc {
13176
14324
  display: flex;
13177
14325
  align-items: flex-start;
13178
- /* 44px is OFF-SCALE ON PURPOSE — it is not a rhythm step but one term of the
13179
- three-term width equation on the next line (256px rail + 44px gutter +
13180
- measure). It sits between --space-6 (48) and --space-5 (32); snapping it
13181
- would silently change the host's computed max-width unless the calc were
13182
- re-derived, so the literal and the calc stay coupled and explicit. */
13183
- column-gap: 44px;
13184
- gap: 44px;
13185
- max-width: calc(256px + 44px + var(--measure-narrow));
14326
+ column-gap: var(--article-toc-gutter-nonrhythm);
14327
+ gap: var(--article-toc-gutter-nonrhythm);
14328
+ max-width: calc(var(--article-toc-rail-w) + var(--article-toc-gutter-nonrhythm) + var(--measure));
13186
14329
  margin-left: auto;
13187
14330
  margin-right: auto;
13188
14331
  }
13189
14332
  .ds-247420 #ds-article-host.has-toc > .article-col {
13190
14333
  flex: 1 1 0;
13191
14334
  min-width: 0;
13192
- max-width: var(--measure-narrow);
14335
+ max-width: var(--measure);
13193
14336
  }
13194
14337
  .ds-247420 #ds-article-host.has-toc > .toc {
13195
- flex: 0 0 256px;
14338
+ flex: 0 0 var(--article-toc-rail-w);
13196
14339
  align-self: flex-start;
13197
14340
  position: sticky;
13198
- top: 24px;
14341
+ top: calc(var(--article-header-h, calc(var(--app-topbar-h, 0px) + var(--app-crumb-h, 0px))) + var(--article-toc-header-gap));
13199
14342
  align-self: start;
13200
- max-height: calc(100vh - 48px);
13201
- max-height: calc(100dvh - 48px);
14343
+ max-height: calc(100vh - var(--article-header-h, calc(var(--app-topbar-h, 0px) + var(--app-crumb-h, 0px))) - 32px);
14344
+ max-height: calc(100dvh - var(--article-header-h, calc(var(--app-topbar-h, 0px) + var(--app-crumb-h, 0px))) - 32px);
13202
14345
  overflow-y: auto;
13203
14346
  overscroll-behavior: contain;
13204
14347
  margin: 0;
@@ -13222,15 +14365,6 @@
13222
14365
  background: var(--panel-text-3);
13223
14366
  background-clip: padding-box;
13224
14367
  }
13225
- /* Small-label voice — the local composition of the house `.t-label` recipe
13226
- (src/css/app-shell/base.css). This sheet is a standalone published export
13227
- with its own <link>, so it cannot join the `.t-label` selector list; it
13228
- composes by referencing the identical token set. Both members are gm-
13229
- generated markup (injected into #ds-article-host), so the class list is not
13230
- reachable from src/ — the rule, not the markup, does the composing.
13231
- Deliberate override for both: --panel-text-2, because prose surfaces use the
13232
- panel text ramp rather than the app --fg ramp. Sized at --fs-nano: these
13233
- labels ride above dense article chrome, not UI text. */
13234
14368
  .ds-247420 #ds-article-host .toc .toc-title,
13235
14369
  .ds-247420 .ds-prose .gm-callout .who {
13236
14370
  font-family: var(--ff-mono);
@@ -13244,10 +14378,37 @@
13244
14378
  margin: 0 0 var(--space-3) 0;
13245
14379
  }
13246
14380
  .ds-247420 #ds-article-host .toc ol { margin: 0; padding-left: var(--space-3-5); }
13247
- .ds-247420 #ds-article-host .toc li { font-size: var(--fs-tiny); line-height: 1.85; color: var(--panel-text); }
14381
+ .ds-247420 #ds-article-host .toc li {
14382
+ font-size: var(--fs-tiny);
14383
+ line-height: 1.85;
14384
+ color: var(--panel-text);
14385
+ min-height: 1.85em;
14386
+ }
14387
+ .ds-247420 #ds-article-host .toc li:nth-child(5n) { margin-bottom: var(--space-2); }
13248
14388
  .ds-247420 #ds-article-host .toc li::marker { color: var(--panel-text-3); font-variant-numeric: tabular-nums; }
13249
14389
  .ds-247420 #ds-article-host .toc a { color: var(--panel-text-2); text-decoration: none; transition: color .12s ease; }
13250
14390
  .ds-247420 #ds-article-host .toc a:hover { color: var(--panel-accent); }
14391
+ .ds-247420 #ds-article-host .toc li.toc-group {
14392
+ margin-top: var(--space-3);
14393
+ padding-top: var(--space-2);
14394
+ border-top: 1px solid var(--panel-3);
14395
+ list-style: none;
14396
+ font-family: var(--ff-mono);
14397
+ font-size: var(--fs-nano);
14398
+ text-transform: uppercase;
14399
+ letter-spacing: var(--tr-label);
14400
+ color: var(--panel-text-3);
14401
+ }
14402
+ .ds-247420 #ds-article-host .toc li.toc-group > ol {
14403
+ margin-top: var(--space-1-5);
14404
+ padding-left: var(--space-3);
14405
+ }
14406
+ .ds-247420 #ds-article-host .toc li.toc-group li {
14407
+ font-size: var(--fs-nano);
14408
+ line-height: 1.7;
14409
+ min-height: 1.7em;
14410
+ }
14411
+ .ds-247420 #ds-article-host .toc li.toc-group li:nth-child(5n) { margin-bottom: 0; }
13251
14412
  .ds-247420 #ds-article-host.has-toc > .article-col > * { min-width: 0; }
13252
14413
  @media (max-width: 900px) {
13253
14414
  .ds-247420 #ds-article-host.has-toc { display: block; }
@@ -13260,28 +14421,15 @@
13260
14421
  }
13261
14422
  }
13262
14423
 
13263
- /* .ds-cli-block — the multi-row article/transcript container (CliBlock()'s
13264
- outer wrapper holding stacked .ds-cli-row / .ds-cli-comment children).
13265
- Distinct from the bare `.cli` single prompt+cmd row primitive owned by
13266
- app-shell.css (Install(), HeroFromPageData(), terminal/site quickstart
13267
- lines) — the two used to share the `.cli` selector with incompatible
13268
- display models (flex row vs block transcript), so whichever stylesheet
13269
- loaded last silently won for both consumers. Renamed here to remove the
13270
- collision; see content.js CliBlock(). */
13271
14424
  .ds-247420 .ds-cli-block {
14425
+ --ds-cli-block-fs-mono-halfrung: 12.5px;
13272
14426
  display: block;
13273
14427
  background: var(--panel-1);
13274
14428
  border-radius: var(--r-0);
13275
14429
  padding: var(--space-3) var(--space-3-5);
13276
14430
  margin: var(--space-3) 0 var(--space-5) 0;
13277
14431
  font-family: var(--ff-mono);
13278
- /* Deliberate half-step, NOT un-migrated drift: this is a transcript block set
13279
- in the mono face, which runs optically ~1 rung larger than the system
13280
- stack at the same px. --fs-micro (12) reads thin against the surrounding
13281
- prose and --fs-tiny (13) reads heavy; 12.5 is the tuned midpoint. There is
13282
- no half-rung on the --fs-* ladder and one must not be invented for a
13283
- single block, so this literal stays. */
13284
- font-size: 12.5px;
14432
+ font-size: var(--ds-cli-block-fs-mono-halfrung);
13285
14433
  line-height: 1.6;
13286
14434
  color: var(--panel-text);
13287
14435
  box-shadow: var(--panel-shadow);
@@ -13354,18 +14502,11 @@
13354
14502
  color: var(--panel-text-2);
13355
14503
  border: 1px solid var(--panel-3);
13356
14504
  font-size: 0.7rem;
13357
- /* rem, not --space-*: the chip's inset is deliberately tied to the root type
13358
- scale alongside its own 0.7rem size, so the capsule keeps its proportions
13359
- when a doc site scales root font-size. A px rung would decouple them. */
13360
- padding: 0.15rem 0.5rem;
14505
+ --tag-inset-rem-coupled-to-root-fontsize: 0.15rem 0.5rem;
14506
+ padding: var(--tag-inset-rem-coupled-to-root-fontsize);
13361
14507
  border-radius: var(--r-hair);
13362
14508
  font-family: var(--ff-mono);
13363
14509
  }
13364
- /* Attributed callout — an editorial pull-quote (it carries a .who byline), so
13365
- the left accent is DELIBERATE, not the generic alert stripe. Expressed via
13366
- the house .panel-spine idiom (absolute ::before rail at --bw-chunk, squared
13367
- leading corners) instead of a raw 3px border-left, so it stays on the border
13368
- scale and matches the spine primitive in panel-row.css. */
13369
14510
  .ds-247420 .ds-prose .gm-callout {
13370
14511
  position: relative;
13371
14512
  margin: var(--space-3-5) 0;
@@ -13380,23 +14521,11 @@
13380
14521
  width: var(--bw-chunk); background: var(--panel-accent);
13381
14522
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
13382
14523
  }
13383
- /* Joins the small-label voice above; only its own box properties here. */
13384
14524
  .ds-247420 .ds-prose .gm-callout .who {
13385
14525
  display: block;
13386
14526
  margin-bottom: var(--space-1);
13387
14527
  }
13388
14528
 
13389
- /* .ds-prose-endnote — the closing line of an article ("end. reply by opening a
13390
- pr."), set off from the body it terminates. Quieter and smaller than prose:
13391
- it takes the mono face and the --panel-text-2 tone that this sheet's other
13392
- ancillary voices use (.toc a, .gm-callout .who, .card .desc), so the endnote
13393
- reads as chrome around the article rather than a final paragraph of it. The
13394
- generous --space-5 lead is what does the "set off" — it breaks the paragraph
13395
- rhythm so the eye registers the piece has ended before reading the words.
13396
- Sized at --fs-xs rather than the --fs-nano label rung: this is a readable
13397
- sentence with a link in it, not an uppercase micro-label.
13398
- Replaces the inline style ui_kits/blog carried before the inline-style
13399
- sweep, which named this class without ever defining it. */
13400
14529
  .ds-247420 .ds-prose .ds-prose-endnote,
13401
14530
  .ds-247420 .ds-prose-endnote {
13402
14531
  margin-top: var(--space-5);
@@ -13406,9 +14535,6 @@
13406
14535
  line-height: 1.6;
13407
14536
  color: var(--panel-text-2);
13408
14537
  }
13409
- /* The endnote's link keeps the prose accent but stays in the quiet register —
13410
- --panel-accent is the panel-ramp counterpart of --accent-ink (a readable
13411
- TEXT tone), never the bare --accent fill, which is ~1.07:1 on paper. */
13412
14538
  .ds-247420 .ds-prose-endnote a { color: var(--panel-accent); text-decoration: none; }
13413
14539
  .ds-247420 .ds-prose-endnote a:hover { text-decoration: underline; }
13414
14540
 
@@ -13419,14 +14545,14 @@
13419
14545
  * prefixer leaves it untouched (consumers mount on <html class="ds-247420">).
13420
14546
  * All values read kit tokens — no hardcoded hex, no inline design in the app. */
13421
14547
 
13422
- .ds-247420 .site-panel { margin: var(--space-2); }
14548
+ .ds-247420 .site-panel { margin: var(--space-2); max-width: 1100px; padding: var(--space-3, 16px); }
13423
14549
 
13424
14550
  /* Hero block */
13425
14551
  .ds-247420 .site-hero { padding: var(--space-4); }
13426
14552
  .ds-247420 .site-hero-h { margin: 0 0 var(--space-2); }
13427
14553
  .ds-247420 .site-hero-body { margin: var(--space-2) 0 var(--space-3); color: var(--fg-2); max-width: 64ch; }
13428
- .ds-247420 .site-chip-row { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 var(--space-3); }
13429
- .ds-247420 .site-cta-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }
14554
+ .ds-247420 .site-chip-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 0 0 var(--space-3); }
14555
+ .ds-247420 .site-cta-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
13430
14556
 
13431
14557
  /* Quickstart CLI block — these .cli/.prompt/.cmd nodes were previously unstyled. */
13432
14558
  .ds-247420 .site-cli { padding: var(--space-3); background: var(--bg); border: var(--bw-hair, 1px) solid var(--rule); border-radius: var(--r-1); }
@@ -13594,8 +14720,8 @@
13594
14720
  width: min(360px, 88vw);
13595
14721
  display: flex;
13596
14722
  flex-direction: column;
13597
- gap: 12px;
13598
- padding: 24px;
14723
+ gap: var(--space-2-75);
14724
+ padding: var(--space-4);
13599
14725
  background: var(--bg-2);
13600
14726
  border: 1px solid var(--bw-rule, var(--rule));
13601
14727
  border-radius: var(--r-2, 10px);
@@ -13624,7 +14750,7 @@
13624
14750
  font-family: var(--ff-mono);
13625
14751
  font-size: var(--fs-body);
13626
14752
  padding: 10px 14px;
13627
- border-radius: var(--r-1, 6px);
14753
+ border-radius: var(--r-1, 8px);
13628
14754
  cursor: pointer;
13629
14755
  }
13630
14756
  .ds-247420 .sp-lobby-btn:hover { background: var(--bg-2); }
@@ -13641,12 +14767,12 @@
13641
14767
  background: var(--bg);
13642
14768
  color: var(--fg);
13643
14769
  border: 1px solid var(--bw-rule, var(--rule));
13644
- border-radius: var(--r-1, 6px);
13645
- padding: 8px 10px;
14770
+ border-radius: var(--r-1, 8px);
14771
+ padding: var(--space-2) var(--space-2-5);
13646
14772
  font: var(--fs-body) var(--ff-mono);
13647
14773
  outline: none;
13648
14774
  }
13649
- .ds-247420 .sp-lobby-input:focus { border-color: var(--accent); }
14775
+ .ds-247420 .sp-lobby-input:focus-visible { border-color: var(--accent); box-shadow: var(--focus-ring-inset); }
13650
14776
  .ds-247420 .sp-lobby-link { font-size: var(--fs-small, 11px); }
13651
14777
  .ds-247420 .sp-lobby-code {
13652
14778
  font-family: var(--ff-display);
@@ -13656,7 +14782,7 @@
13656
14782
  color: var(--accent);
13657
14783
  padding: 8px;
13658
14784
  background: var(--bg);
13659
- border-radius: var(--r-1, 6px);
14785
+ border-radius: var(--r-1, 8px);
13660
14786
  }
13661
14787
  .ds-247420 .sp-lobby-err { min-height: 16px; color: var(--danger); font-size: var(--fs-small, 11px); }
13662
14788
  .ds-247420 .sp-lobby-close {