@tangle-network/brand 1.27.0 → 1.28.0

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.
@@ -6,6 +6,11 @@
6
6
  * hairline (1.5:1 against the card in dark) because shadow does no work on a dark
7
7
  * canvas. Light mirrors it: white paper on a grey canvas, never white-on-white.
8
8
  *
9
+ * Each colour is declared ONCE, as a role (a plane, an ink tier, one of four
10
+ * indigos, one ink per status hue), and every other name of that colour is an
11
+ * alias of it, so a retune or a named theme moves one value and every family
12
+ * follows: hsl-*, md3-*, depth-*, bg-*, status-*, surface-*, code-* and brand-*.
13
+ *
9
14
  * Apps consume this spine as-is. If a product needs its own accent identity,
10
15
  * add a named theme in named-themes.css (`data-theme="<name>"`) — do NOT re-declare
11
16
  * these tokens in the app, or its surfaces silently stop tracking the brand. */
@@ -56,96 +61,179 @@
56
61
  .light,
57
62
  .dark {
58
63
  color-scheme: dark;
59
- --md3-surface: #0a0a14; /* L0 page canvas — indigo-lifted off black */
60
- --md3-surface-dim: #040408; /* recessed page: 4.1 L under the #0a0a14 panel (the inset workspace surface needs >= 3) */
61
- --md3-surface-bright: #232238;
62
- /* Same value as --md3-surface-dim by design, not by drift: both name the floor
63
- * of the ladder, and dark has no plane below it to give them. They stay two
64
- * tokens because the roles diverge in light, where the well is paper (#ffffff)
65
- * and the recessed page is grey (#dcdee6) — a consumer binding to the wrong one
66
- * there gets the wrong surface. */
67
- --md3-surface-container-lowest: #040408; /* L-1 terminal/code well */
68
- --md3-surface-container-low: #12111f; /* L1 chrome, field wells */
69
- --md3-surface-container: #191826; /* L2 resting card */
70
- --md3-surface-container-high: #221f33; /* L3 nested / hover */
71
- --md3-surface-container-highest: #2c2942; /* L4 overlay */
72
- --md3-surface-variant: #221f33;
73
- --md3-on-surface: #e6e6e6;
74
- --md3-on-surface-variant: #b5b5b5;
75
-
76
- --md3-primary: #A5B4FC;
77
- --md3-primary-dim: #7C6CF6;
78
- --md3-primary-container: #4C3FC5;
79
- --md3-on-primary: #ffffff;
80
- --md3-on-primary-container: #E0E7FF;
81
- --md3-primary-fixed: #A5AAFC;
82
- --md3-primary-fixed-dim: #818CF8;
83
-
84
- --md3-secondary: #C7D2FE;
85
- --md3-secondary-dim: #A5B4FC;
86
- --md3-secondary-container: #312E81;
87
- --md3-on-secondary: #E8E5FF;
88
- --md3-on-secondary-container: #E8E5FF;
89
-
90
- --md3-tertiary: #10B981;
91
- --md3-tertiary-container: var(--surface-success-bg);
92
- --md3-outline: #8e8e8e;
93
- --md3-outline-variant: #2a293d;
94
-
95
- --md3-error: #ff5277;
96
- --md3-error-dim: #eb2855;
97
- --md3-error-container: #82042b;
98
- --md3-on-error: #ffffff;
99
- --md3-on-error-container: #ffbdf2;
100
64
 
65
+ /* ── Planes ─────────────────────────────────────────────────────────────
66
+ * One ladder, authored once as HSL channels because the shadcn layer reads
67
+ * hsl(var(--hsl-*)); every hex plane below is derived from it, so a step
68
+ * moves in one place. L0 is lifted off black toward indigo; L1 chrome sits
69
+ * below it and the cards step up from it in even increments. */
70
+ --hsl-background: 240 33% 6%; /* L0 canvas #0a0a14 */
71
+ --hsl-input: 244 29% 9%; /* L1 chrome and field wells #11101e */
72
+ --hsl-card: 244 23% 12%; /* L2 resting card #191826 */
73
+ --hsl-secondary: 249 24% 16%; /* L3 nested plane and hover well #221f33 */
74
+ --hsl-popover: 247 23% 21%; /* L4 overlay #2c2942: a popover carries body text, so it sits on the top plane the ink ramp reaches */
75
+ --hsl-muted: var(--hsl-secondary);
76
+ --hsl-accent: var(--hsl-secondary); /* neutral hover; indigo lives in --accent-surface-strong */
77
+ /* The floor of the ladder: the recessed page and the terminal well share it
78
+ * because dark has no plane below L0 to give them. They stay two tokens
79
+ * because they diverge in light, where the well is paper and the page grey. */
80
+ --md3-surface-dim: #040408; /* 4.1 L under the L0 panel: the inset workspace surface needs >= 3 */
81
+ --md3-surface-container-lowest: var(--md3-surface-dim);
82
+ --md3-surface-bright: #232238;
83
+ --md3-surface: hsl(var(--hsl-background));
84
+ --md3-surface-container-low: hsl(var(--hsl-input));
85
+ --md3-surface-container: hsl(var(--hsl-card));
86
+ --md3-surface-container-high: hsl(var(--hsl-secondary));
87
+ --md3-surface-container-highest: hsl(var(--hsl-popover));
88
+ --md3-surface-variant: var(--md3-surface-container-high);
89
+ --depth-1: var(--md3-surface);
90
+ --depth-2: var(--md3-surface-container);
91
+ --depth-3: var(--md3-surface-container-high);
92
+ --depth-4: var(--md3-surface-container-highest);
93
+ --bg-root: var(--md3-surface);
94
+ --bg-dark: var(--depth-1);
95
+ --bg-card: var(--depth-2);
96
+ --bg-elevated: var(--depth-4);
97
+ --bg-section: var(--depth-1);
98
+ --bg-input: hsl(var(--hsl-input));
99
+ /* Sidebar: L1 chrome, one step BELOW the canvas, so the nav reads as what the
100
+ * content sits in front of rather than as another card. */
101
+ --sidebar-background: var(--hsl-input);
102
+ --sidebar-accent: var(--hsl-secondary);
101
103
  --md3-inverse-surface: #fdfdfd;
102
- --md3-inverse-primary: #4C3FC5;
103
104
  --md3-inverse-on-surface: #000000;
104
- --md3-surface-tint: #A5B4FC;
105
105
 
106
- /* Semantic HSL bridge — the indigo-lifted surface ladder (GTM's, now every product's) */
107
- --hsl-background: 240 33% 6%; /* L0 canvas #0a0a14 */
108
- /* Near-white ink on the canvas is the glare end of the range, and the tiers
109
- * below it need room to separate. This neutral ink keeps the original 10.6:1
110
- * contrast on the card and leaves four steps the eye can tell apart. */
106
+ /* ── Ink ────────────────────────────────────────────────────────────────
107
+ * Neutral on purpose: colour in the ink would compete with the one accent.
108
+ * Near-white on the canvas is the glare end of the range, so the tiers sit
109
+ * below it: 10.6 / 7.8 / 6.4 / 5.4 against the L2 card, solved against the
110
+ * WHOLE ladder so the faintest tier still clears the 4.5:1 body floor on the
111
+ * overlay plane. tokens.test.ts asserts every tier on every plane. */
111
112
  --hsl-foreground: 0 0% 90.1961%; /* #e6e6e6 */
112
- --hsl-card: 244 23% 12%; /* L2 card #191826 */
113
- --hsl-card-foreground: 0 0% 90.1961%;
114
- --hsl-popover: 247 23% 21%; /* #2c2942 — the NESTED plane despite
115
- the name; the overlay plane is --md3-surface-container-highest. A popover
116
- carries body text, and the ladder above L3 is reserved for planes that lift
117
- by shadow, so it sits on L3 and the ink ramp reaches it. */
118
- --hsl-popover-foreground: 0 0% 90.1961%;
119
- /* The fill indigo, NOT the bright one: `.btn-primary` puts white on this, and
120
- * #6366F1 carries white at only 4.47:1. This clears the floor at 7.4:1. The
121
- * bright indigo stays available for accent TEXT via --accent-text. */
122
- --hsl-primary: 246 54% 51%; /* #4c3fc6 */
113
+ --hsl-muted-foreground: 240 5% 68%; /* #a9a9b1: the /70 and /60 caption derivatives still clear the large-text floor on the card */
114
+ --text-primary: hsl(var(--hsl-foreground));
115
+ --text-secondary: #c7c7c7;
116
+ --text-muted: #b5b5b5;
117
+ --text-dim: #a5a5a5;
118
+ --hsl-card-foreground: var(--hsl-foreground);
119
+ --hsl-popover-foreground: var(--hsl-foreground);
120
+ --hsl-secondary-foreground: var(--hsl-foreground);
121
+ --hsl-accent-foreground: var(--hsl-foreground);
122
+ --sidebar-foreground: var(--hsl-foreground);
123
+ --sidebar-accent-foreground: var(--hsl-foreground);
124
+ --md3-on-surface: var(--text-primary);
125
+ --md3-on-surface-variant: var(--text-muted);
126
+ --md3-outline: #8e8e8e;
127
+
128
+ /* ── Hairlines ──────────────────────────────────────────────────────────
129
+ * 1.5:1 against the resting card, quiet by design: shadow does no work on a
130
+ * dark canvas, so the hairline states where a plane ends without drawing a
131
+ * box around every panel. */
132
+ --hsl-border: 243 20% 20%; /* #2a293d */
133
+ --md3-outline-variant: hsl(var(--hsl-border));
134
+ --sidebar-border: var(--hsl-border);
135
+ --border-subtle: rgba(255, 255, 255, 0.10);
136
+ --border-default: rgba(255, 255, 255, 0.16);
137
+ --border-hover: rgba(255, 255, 255, 0.28);
138
+
139
+ /* ── Accent: four indigos, each with one job ────────────────────────────
140
+ * fill --hsl-primary carries white at 7.4:1: buttons, the CTA, the selected row at 30%
141
+ * mark --brand-primary the brand indigo of the logo and marketing fills; never under text
142
+ * line --brand-cool edges, chart accents, the focus ring family
143
+ * ink --accent-text the bright indigo as TEXT and icon only: 6.7:1 on the card
144
+ * Every other indigo name is one of these four, so a named theme that retints
145
+ * the accent retints all of them. The ring is brighter than the fill on
146
+ * purpose: it is non-text and must clear 3:1 on the canvas AND the card,
147
+ * while the fill is tuned to carry white. */
148
+ --hsl-primary: 246 54% 51%; /* fill #4c3fc6 */
123
149
  --hsl-primary-foreground: 0 0% 100%;
124
- --hsl-secondary: 249 24% 16%; /* L3 #221f33 */
125
- --hsl-secondary-foreground: 0 0% 90.1961%;
126
- --hsl-muted: 249 24% 16%; /* L3 hover well */
127
- --hsl-muted-foreground: 240 5% 68%; /* #a9a9b1 — high enough that the
128
- reduced-opacity caption derivatives (/70, /60) still clear the large-text
129
- contrast floor against the L2 card. */
130
- --hsl-accent: 249 24% 16%; /* neutral hover (indigo lives in accent-surface-strong) */
131
- --hsl-accent-foreground: 0 0% 90.1961%;
132
- /* Light red carrying dark ink, as the dark primary does: it must read as
133
- * error TEXT on every dark plane (AA) and still carry its foreground as a fill. */
134
- --hsl-destructive: 0 90% 77%;
135
- --hsl-destructive-foreground: 0 60% 10%;
136
- /* Quiet on purpose: 1.5:1 against the resting card. Shadow does no work on a
137
- * dark canvas, so the hairline is what states where a plane ends. */
138
- --hsl-border: 243 20% 20%; /* #2a293d = --md3-outline-variant */
139
- --hsl-input: 244 29% 9%; /* recessed field well #11101e */
140
- /* Brighter than --primary on purpose: the ring is NON-TEXT and has to clear
141
- * 3:1 against BOTH canvas and card. The fill indigo is tuned to carry white
142
- * text instead, so the two cannot be the same value. */
143
150
  --hsl-ring: 233 100% 87%;
144
- --hsl-success: 160 84% 39%;
145
- --hsl-warning: 40 94% 56%;
146
- --hsl-info: 200 88% 56%;
147
-
148
- /* Tailwind/shadcn aliases */
151
+ --brand-primary: #7C6CF6;
152
+ --brand-cool: #818CF8;
153
+ --accent-text: #A5B4FC;
154
+ --brand-purple: #C7D2FE; /* the ink, softened: gradient text and ink on the deep field */
155
+ --brand-strong: #2E2A5E; /* the deep field the softened ink sits on */
156
+ --brand-strong-text: #ffffff;
157
+ --brand-strong-text-muted: rgba(255, 255, 255, 0.7);
158
+ --brand-strong-text-dim: rgba(255, 255, 255, 0.5);
159
+ --brand-glow: var(--accent-text);
160
+ --brand-vibrant: var(--brand-primary);
161
+ --btn-primary-bg: hsl(var(--hsl-primary));
162
+ --btn-primary-hover: #5a50d6;
163
+ --btn-primary-text: #ffffff;
164
+ --btn-cta-bg: var(--btn-primary-bg);
165
+ --btn-cta-text: var(--btn-primary-text);
166
+ --sidebar-primary: var(--hsl-primary);
167
+ --sidebar-primary-foreground: var(--hsl-primary-foreground);
168
+ --sidebar-ring: var(--hsl-ring);
169
+ --md3-primary: var(--accent-text);
170
+ --md3-primary-dim: var(--brand-primary);
171
+ --md3-primary-container: var(--btn-primary-bg);
172
+ --md3-on-primary: #ffffff;
173
+ --md3-on-primary-container: var(--brand-purple);
174
+ --md3-primary-fixed: var(--accent-text);
175
+ --md3-primary-fixed-dim: var(--brand-cool);
176
+ --md3-secondary: var(--brand-purple);
177
+ --md3-secondary-dim: var(--accent-text);
178
+ --md3-secondary-container: var(--brand-strong);
179
+ --md3-on-secondary: var(--brand-purple);
180
+ --md3-on-secondary-container: var(--brand-purple);
181
+ --md3-inverse-primary: var(--btn-primary-bg);
182
+ --md3-surface-tint: var(--accent-text);
183
+ /* Accent wells: soft is the hover neutral, strong the active or selected row. */
184
+ --accent-surface-soft: color-mix(in srgb, var(--accent-text) 7%, transparent);
185
+ --accent-surface-strong: color-mix(in srgb, var(--btn-primary-bg) 30%, transparent);
186
+ --bg-hover: var(--accent-surface-soft);
187
+ --bg-selection: color-mix(in srgb, var(--brand-primary) 28%, transparent);
188
+ --border-accent: color-mix(in srgb, var(--brand-cool) 35%, transparent);
189
+ --border-accent-hover: color-mix(in srgb, var(--brand-cool) 50%, transparent);
190
+ /* The indigo spark, kept ONLY for the logo mark and the send button. */
191
+ --tangle-gradient: linear-gradient(135deg, var(--btn-primary-bg), var(--accent-text));
192
+ --tangle-gradient-text: linear-gradient(135deg, var(--brand-purple), var(--accent-text));
193
+ --accent-gradient-strong: linear-gradient(135deg, var(--btn-primary-bg), var(--brand-primary));
194
+
195
+ /* ── Status: one ink, one wash, one border per hue ──────────────────────
196
+ * The ink is pastel on purpose. A saturated fill vibrates on a 6% canvas,
197
+ * and the ink already clears AA on every plane, so it is also the fill: a
198
+ * status fill carries dark text, as the destructive one always has. The
199
+ * shadcn channels are the source; the surface, status, md3, code and brand
200
+ * names of each hue alias them. */
201
+ --hsl-success: 154.7 60.2% 60.6%; /* #5ed7a4 */
202
+ --hsl-warning: 41.5 88.2% 63.3%; /* #f4c14f */
203
+ --hsl-destructive: 2.8 100% 83.1%; /* #ffada9 */
204
+ --hsl-destructive-foreground: 0 60% 10%;
205
+ --hsl-info: 199.7 100% 70.2%; /* #67cdff */
206
+ --surface-success-text: hsl(var(--hsl-success));
207
+ --surface-success-bg: #2f5146;
208
+ --surface-success-border: #3c7760;
209
+ --surface-warning-text: hsl(var(--hsl-warning));
210
+ --surface-warning-bg: #4b4232;
211
+ --surface-warning-border: #7a6236;
212
+ --surface-danger-text: hsl(var(--hsl-destructive));
213
+ --surface-danger-bg: #5a3b40;
214
+ --surface-danger-border: #7d4d53;
215
+ --surface-info-text: hsl(var(--hsl-info));
216
+ --surface-info-bg: #2d4e67;
217
+ --surface-info-border: #3d7292;
218
+ --status-running: var(--surface-success-text);
219
+ --status-creating: var(--accent-text);
220
+ --status-stopped: var(--surface-warning-text);
221
+ --status-warm: var(--surface-orange-text);
222
+ --status-cold: var(--surface-info-text);
223
+ --status-error: var(--surface-danger-text);
224
+ --status-deleted: var(--text-dim);
225
+ --brand-emerald: var(--surface-success-text);
226
+ --md3-tertiary: var(--surface-success-text);
227
+ --md3-tertiary-container: var(--surface-success-bg);
228
+ --md3-error: var(--surface-danger-text);
229
+ --md3-error-dim: color-mix(in srgb, var(--md3-error) 85%, black);
230
+ --md3-error-container: var(--surface-danger-bg);
231
+ --md3-on-error: hsl(var(--hsl-destructive-foreground));
232
+ --md3-on-error-container: var(--surface-danger-text);
233
+ /* ── shadcn aliases: the names @tangle-network/ui and Tailwind read ─────
234
+ * Declared once; they re-resolve per theme through the --hsl-* spine. --input
235
+ * is the input BORDER and the Switch off-track, a mid-tone that must stay
236
+ * visible against a surface, so it is NOT the field fill (--bg-input). */
149
237
  --background: var(--hsl-background);
150
238
  --foreground: var(--hsl-foreground);
151
239
  --card: var(--hsl-card);
@@ -163,11 +251,6 @@
163
251
  --destructive: var(--hsl-destructive);
164
252
  --destructive-foreground: var(--hsl-destructive-foreground);
165
253
  --border: var(--hsl-border);
166
- /* shadcn's `--input` is the input BORDER and the Switch's off-track — a
167
- * mid-tone that has to stay visible AGAINST a surface. It is deliberately NOT
168
- * the field fill: pointing it at the recessed well would tint a border and a
169
- * track the same color as the plane behind them, and both would vanish. The
170
- * field fill is --hsl-input / --bg-input below. */
171
254
  --input: var(--hsl-muted-foreground);
172
255
  --ring: var(--hsl-ring);
173
256
  --success: var(--hsl-success);
@@ -175,45 +258,6 @@
175
258
  --error: var(--hsl-destructive);
176
259
  --info: var(--hsl-info);
177
260
 
178
- /* Sidebar tokens — L1 chrome: one step BELOW the canvas here, so the nav reads
179
- * as chrome the content sits in front of rather than as another card. */
180
- --sidebar-background: 244 29% 9%; /* L1 chrome #11101e */
181
- --sidebar-foreground: 0 0% 90.1961%;
182
- --sidebar-primary: 246 54% 51%;
183
- --sidebar-primary-foreground: 0 0% 100%;
184
- --sidebar-accent: 0 0% 20.7843%; /* L3 hover well */
185
- --sidebar-accent-foreground: 0 0% 90.1961%;
186
- --sidebar-border: 0 0% 29.8039%;
187
- --sidebar-ring: 233 100% 87%;
188
-
189
- /* Depth scale — tracks the MD3 ladder (canvas / card / nested / overlay) */
190
- --depth-1: #0a0a14;
191
- --depth-2: #191826;
192
- --depth-3: #221f33;
193
- --depth-4: #2c2942;
194
-
195
- /* Status colors */
196
- --status-running: #10B981;
197
- --status-creating: #A5B4FC;
198
- --status-stopped: #FFB800;
199
- --status-warm: #FF8A4C;
200
- --status-cold: #4AABFF;
201
- --status-error: #FF4D6D;
202
- --status-deleted: #6B7094;
203
-
204
- /* Status surface tokens — bg / border / text for each semantic color */
205
- --surface-success-bg: #2f5146;
206
- --surface-success-border: #3c7760;
207
- --surface-success-text: #5ed7a4;
208
- --surface-warning-bg: #4b4232;
209
- --surface-warning-border: #7a6236;
210
- --surface-warning-text: #f4c14f;
211
- --surface-danger-bg: #5a3b40;
212
- --surface-danger-border: #7d4d53;
213
- --surface-danger-text: #ffada9;
214
- --surface-info-bg: #2d4e67;
215
- --surface-info-border: #3d7292;
216
- --surface-info-text: #67cdff;
217
261
  /* tones:begin — generated by scripts/gen-ladders.mjs; edit its CATEGORY map, not these lines. */
218
262
  --tone-violet-bg: #392751;
219
263
  --tone-violet-bg-hover: #432b60;
@@ -281,7 +325,6 @@
281
325
  --surface-teal-border: #207e73;
282
326
  --surface-teal-text: #0bd8b6;
283
327
  /* tones:end */
284
-
285
328
  /* Run-mix ramp — the fills of a proportional status bar.
286
329
  *
287
330
  * Separate from the `--surface-*-text` tones because a 6px bar is read
@@ -322,79 +365,28 @@
322
365
  --mesh-violet: transparent;
323
366
  --mesh-blue: transparent;
324
367
 
325
- /* Brand gradients — the indigo spark, kept ONLY for the logo mark + send button */
326
- --tangle-gradient: linear-gradient(135deg, #4C3FC5, #A5B4FC);
327
- --tangle-gradient-text: linear-gradient(135deg, #C7D2FE, #A5B4FC);
328
- --accent-gradient-strong: linear-gradient(135deg, #4C3FC5, #7C6CF6);
329
- /* Accent wells — soft = hover neutral, strong = active/selected row (indigo) */
330
- --accent-surface-soft: rgba(165, 180, 252, 0.07);
331
- --accent-surface-strong: rgba(76, 63, 197, 0.30);
332
- /* The BRIGHT indigo, and only ever as text: --primary is the fill (it carries
333
- * white), and at 6.7:1 on the card this is the one that stays legible as ink. */
334
- --accent-text: #A5B4FC;
335
-
336
- /* Surfaces — solid fills off the ladder (no radial mesh) */
337
- --bg-root: #0a0a14;
338
- --bg-dark: var(--depth-1);
339
- --bg-card: var(--depth-2);
340
- --bg-elevated: var(--depth-4);
341
- --bg-section: var(--depth-1);
342
- --bg-input: hsl(var(--hsl-input)); /* the field well — same color, hex-free */
343
- --bg-hover: rgba(165, 180, 252, 0.07);
344
- --bg-selection: rgba(124, 108, 246, 0.28);
345
-
346
- /* Text — neutral */
347
- /* Four steps the eye can actually separate: 10.6 / 7.8 / 6.4 / 5.4 against the
348
- * L2 card. --text-dim is the faintest tier but it is still TEXT, so it clears
349
- * the 4.5:1 body floor rather than only the 3:1 large-text one.
350
- *
351
- * Solved against the WHOLE ladder, not against one plane. A faint tier tuned
352
- * on the card alone still has to clear the floor on the nested and overlay
353
- * planes above it, which carry the modals, the drawers and every dropdown
354
- * menu — so both ends meet in the middle: the faint tiers sit high enough for
355
- * the overlay, and the planes above the card sit low enough for them. The
356
- * card, the chrome and the canvas are fixed points. tokens.test.ts asserts
357
- * every tier on every plane, so a retune cannot open a gap on one plane while
358
- * looking correct on another. */
359
- --text-primary: #e6e6e6;
360
- --text-secondary: #c7c7c7;
361
- --text-muted: #b5b5b5;
362
- --text-dim: #a5a5a5;
363
-
364
- /* Brand accents — Tangle */
365
- --brand-primary: #7C6CF6;
366
- --brand-strong: #2E2A5E;
367
- --brand-strong-text: #ffffff;
368
- --brand-strong-text-muted: rgba(255, 255, 255, 0.7);
369
- --brand-strong-text-dim: rgba(255, 255, 255, 0.5);
370
- --brand-cool: #818CF8;
371
- --brand-glow: #A5AAFC;
372
- --brand-purple: #C7D2FE;
373
- --brand-vibrant: #6366F1;
374
- --brand-emerald: #10B981;
375
-
376
- /* Borders — neutral hairlines */
377
- --border-subtle: rgba(255, 255, 255, 0.10);
378
- --border-default: rgba(255, 255, 255, 0.16);
379
- --border-hover: rgba(255, 255, 255, 0.28);
380
- --border-accent: rgba(129, 140, 248, 0.35);
381
- --border-accent-hover: rgba(129, 140, 248, 0.50);
382
-
383
- /* Buttons */
384
- --btn-primary-bg: #4C3FC5;
385
- --btn-primary-hover: #5A50D6;
386
- --btn-primary-text: #ffffff;
387
- --btn-cta-bg: #4C3FC5;
388
- --btn-cta-text: #FFFFFF;
389
-
390
- /* Code / runtime syntax */
391
- --code-keyword: #A5AAFC;
392
- --code-string: #34D399;
393
- --code-function: #6D9FFF;
394
- --code-number: #FFB347;
395
- --code-success: #10B981;
396
- --code-comment: #606294;
397
- --code-error: #FF4D6D;
368
+ /* ── Code ───────────────────────────────────────────────────────────────
369
+ * The same inks as the status family and the accent, so a keyword, a
370
+ * string and a warning never carry three different purples or ambers.
371
+ * Keywords take the categorical violet, not the accent: a keyword is not a
372
+ * link. --code-* is the older name for the same roles. */
373
+ --code-header-bg: hsl(var(--hsl-background));
374
+ --syntax-foreground: var(--text-primary);
375
+ --syntax-variable: var(--text-secondary);
376
+ --syntax-comment: var(--text-dim);
377
+ --syntax-keyword: var(--tone-violet-icon);
378
+ --syntax-meta: var(--accent-text);
379
+ --syntax-function: var(--surface-info-text);
380
+ --syntax-string: var(--surface-success-text);
381
+ --syntax-number: var(--surface-warning-text);
382
+ --syntax-error: var(--surface-danger-text);
383
+ --code-keyword: var(--syntax-keyword);
384
+ --code-string: var(--syntax-string);
385
+ --code-function: var(--syntax-function);
386
+ --code-number: var(--syntax-number);
387
+ --code-success: var(--surface-success-text);
388
+ --code-comment: var(--syntax-comment);
389
+ --code-error: var(--syntax-error);
398
390
 
399
391
  /* Shadows — flat, no inner ring, no indigo glow */
400
392
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3);
@@ -531,18 +523,6 @@
531
523
  --tool-card-py: 0.5rem;
532
524
  --tool-icon-size: 1.75rem;
533
525
 
534
- /* Syntax highlighting — the light palette is authored below. */
535
- --code-header-bg: hsl(var(--hsl-background));
536
- --syntax-comment: #9ea2cc;
537
- --syntax-keyword: #A78FFF;
538
- --syntax-string: #10b981;
539
- --syntax-function: #6D9FFF;
540
- --syntax-number: #FFB347;
541
- --syntax-meta: #8263FF;
542
- --syntax-error: #FF4D6D;
543
- --syntax-variable: #C4C0D8;
544
- --syntax-foreground: #E8E6F6;
545
-
546
526
  /* Shared motion vocabulary, previously authored by the light-default Agent
547
527
  * App consumer. These are tokens only, not automatic animation classes. */
548
528
  --duration-instant: 90ms;
@@ -599,130 +579,85 @@
599
579
  [data-theme="agents-light"],
600
580
  .light {
601
581
  color-scheme: light;
602
- /* Light theme — neutral surfaces with purple accents */
603
- --code-header-bg: color-mix(in srgb, hsl(var(--hsl-muted)) 50%, transparent);
604
- --syntax-foreground: #24243a;
605
- --syntax-comment: #6b7280;
606
- --syntax-keyword: #7c3aed;
607
- --syntax-string: #047857;
608
- --syntax-function: #2563eb;
609
- --syntax-number: #b45309;
610
- --syntax-meta: #6d28d9;
611
- --syntax-variable: #334155;
612
- --syntax-error: #dc2626;
613
582
 
614
- /* MD3 surface hierarchy */
615
- /* The light ladder ALTERNATES; it is not monotonic, and that is deliberate.
616
- * On a neutral canvas, a raised plane is PAPER (white) and a recessed one is a
617
- * neutral WELL. So card and overlay are both white and separate from each other
618
- * by shadow and ring, not by fill — which is how light-mode elevation actually
619
- * reads. Forcing a strictly-darkening ladder here is what produced the
620
- * white-on-white flatness this palette replaced. Pinned in tokens.test.ts. */
621
- --md3-surface: #eceef3; /* L0 page canvas — cool grey, BELOW the wells */
622
- --md3-surface-dim: #dcdee6;
583
+ /* ── Planes: white paper on a cool grey canvas ───────────────────────────
584
+ * The light ladder ALTERNATES, and that is deliberate: a raised plane is
585
+ * PAPER and a recessed one is a neutral WELL, so the card and the overlay
586
+ * are both white and separate by shadow and ring, not by fill. Forcing a
587
+ * strictly darkening ladder here is what produced the white-on-white
588
+ * flatness this palette replaced. Pinned in tokens.test.ts. */
589
+ --hsl-background: 223 23% 94%; /* L0 canvas #eceef3, BELOW the wells */
590
+ --hsl-input: 225 29% 97%; /* L1 chrome and field wells #f5f6fa */
591
+ --hsl-card: 0 0% 100%; /* L2 card: paper */
592
+ --hsl-secondary: 230 27% 96%; /* L3 nested well #f2f3f8 */
593
+ --hsl-popover: var(--hsl-card); /* L4 overlay: paper, lifted by shadow, never out-lightening a card */
594
+ --md3-surface-dim: #dcdee6; /* the recessed page */
595
+ --md3-surface-container-lowest: hsl(var(--hsl-card)); /* insets read as paper */
623
596
  --md3-surface-bright: #ffffff;
624
- --md3-surface-container-lowest: #ffffff; /* insets read as paper */
625
- --md3-surface-container-low: #f6f7fa; /* L1 chrome, field wells — well */
626
- --md3-surface-container: #ffffff; /* L2 resting card — paper */
627
- --md3-surface-container-high: #f1f2f7; /* L3 nested / hover — well */
628
- --md3-surface-container-highest: #ffffff; /* L4 overlay — paper + shadow */
629
- --md3-surface-variant: #f1f2f7;
630
- --md3-on-surface: #2d2d2d;
631
- --md3-on-surface-variant: #4c4c4c;
632
-
633
- --md3-primary: #3E349E;
634
- --md3-primary-dim: #352B88;
635
- --md3-primary-container: #352B88;
636
- --md3-on-primary: #ffffff;
637
- --md3-on-primary-container: #9B93F0;
638
- --md3-primary-fixed: #E8E5FF;
639
- --md3-primary-fixed-dim: #C5C0F0;
640
-
641
- --md3-secondary: #524A78;
642
- --md3-secondary-dim: #3E3660;
643
- --md3-secondary-container: #E8E5FF;
644
- --md3-on-secondary: #ffffff;
645
- --md3-on-secondary-container: #524A78;
646
-
647
- --md3-tertiary: #065F46;
648
- --md3-tertiary-container: #D1FAE5;
649
- --md3-outline: #787878;
650
- --md3-outline-variant: #c7c6d6;
651
-
652
- --md3-error: #ba1a1a;
653
- --md3-error-dim: #93000a;
654
- --md3-error-container: #ffdad6;
655
- --md3-on-error: #ffffff;
656
- --md3-on-error-container: #93000a;
657
-
597
+ --sidebar-background: var(--hsl-card); /* paper, flat, with a hairline */
658
598
  --md3-inverse-surface: #2e2e3e;
659
- --md3-inverse-primary: #A5AAFC;
660
599
  --md3-inverse-on-surface: #f0f1f4;
661
- --md3-surface-tint: #6366F1;
662
600
 
663
- /* Semantic HSL bridge — white paper on an achromatic canvas */
664
- --hsl-background: 223 23% 94%; /* L0 canvas #eceef3 */
665
- /* Neutral ink rather than pure black keeps the original 13.7:1 contrast on
666
- * white paper and 11.4:1 on the canvas: AAA with headroom in both places. */
667
- --hsl-foreground: 0 0% 17.6471%; /* #2d2d2d */
668
- --hsl-card: 0 0% 100%; /* L2 card = white paper */
669
- --hsl-card-foreground: 0 0% 17.6471%;
670
- --hsl-popover: 0 0% 100%;
671
- --hsl-popover-foreground: 0 0% 17.6471%;
672
- --hsl-primary: 246 54% 51%; /* #4c3fc6 — carries white at 7.4:1 */
673
- --hsl-primary-foreground: 0 0% 100%;
674
- --hsl-secondary: 230 27% 96%; /* L3 #f2f3f8 */
675
- --hsl-secondary-foreground: 0 0% 17.6471%;
676
- --hsl-muted: 230 27% 96%; /* L3 hover well */
677
- /* The shared components fade captions with /70 and /60, so a translucent
678
- * foreground renders composited over the surface behind it. This neutral value
679
- * keeps 3.0:1 at /60 and 8.6:1 at full strength on paper. */
680
- --hsl-muted-foreground: 0 0% 29.8039%; /* #4c4c4c text-muted */
681
- --hsl-accent: 230 27% 96%; /* neutral hover */
682
- --hsl-accent-foreground: 0 0% 17.6471%;
683
- --hsl-destructive: 0 72% 41%;
684
- --hsl-destructive-foreground: 0 0% 100%;
685
- --hsl-border: 244 16% 81%; /* #c8c7d6, the --md3-outline-variant hairline */
686
- --hsl-input: 225 29% 97%; /* field well #f5f6fa */
687
- --hsl-ring: 246 54% 51%;
688
- --hsl-success: 160 84% 30%;
689
- --hsl-warning: 41 96% 50%;
690
- --hsl-info: 212 80% 50%;
691
-
692
- /* depth-2 (card) and depth-4 (elevated) are BOTH paper, on purpose: in light,
693
- * a popover does not out-lighten a white card — it lifts by shadow. See the
694
- * ladder note above. */
695
- --depth-1: #eceef3; /* canvas */
696
- --depth-2: #ffffff; /* card — paper */
697
- --depth-3: #f1f2f7; /* nested well */
698
- --depth-4: #ffffff; /* elevated — paper, separated by shadow */
699
-
700
- --status-running: #059669;
701
- --status-creating: #3E349E;
702
- --status-stopped: #B45309;
703
- --status-warm: #C05621;
704
- --status-cold: #1D6FA4;
705
- --status-error: #ba1a1a;
706
- --status-deleted: #6B6B82;
601
+ /* ── Ink: neutral grey, so purple stays reserved for interaction ─────────
602
+ * 13.7:1 on paper and 11.4:1 on the canvas, AAA with headroom. --text-dim is
603
+ * pinned from the CANVAS, the lighter plane that binds the faintest tier:
604
+ * 4.6:1 there and 5.6:1 on the card, asserted in tokens.test.ts. */
605
+ --hsl-foreground: 0 0% 17.6471%; /* #2d2d2d */
606
+ --hsl-muted-foreground: 0 0% 29.8039%; /* #4c4c4c: 3.0:1 at /60 and 8.6:1 at full strength on paper */
607
+ --text-secondary: #3f3f3f;
608
+ --text-muted: #4c4c4c;
609
+ --text-dim: #686868;
610
+ --md3-outline: #787878;
707
611
 
708
- --glass-bg: var(--depth-2);
709
- --glass-bg-strong: var(--depth-1);
710
- --glass-border: var(--border-subtle);
711
- --glass-border-hover: var(--border-default);
612
+ /* ── Hairlines ──────────────────────────────────────────────────────────── */
613
+ --hsl-border: 244 16% 81%; /* #c8c7d6 */
614
+ --border-subtle: rgba(0, 0, 0, 0.10);
615
+ --border-default: rgba(0, 0, 0, 0.14);
616
+ --border-hover: rgba(0, 0, 0, 0.24);
712
617
 
713
- /* Status surface overrides — light values */
618
+ /* ── Accent ─────────────────────────────────────────────────────────────
619
+ * The fill is the same indigo as dark (it carries white at 7.4:1) and is
620
+ * also the ring: on paper it clears 3:1 as a line. The ink is the bright
621
+ * indigo at 6.1:1 on paper; the mark is the website's indigo. */
622
+ --hsl-ring: var(--hsl-primary);
623
+ --brand-primary: #6366F1;
624
+ --brand-purple: var(--brand-primary);
625
+ --accent-text: #3c32e9;
626
+ --md3-primary: var(--accent-text);
627
+ --md3-primary-dim: var(--btn-primary-bg);
628
+ --md3-on-primary-container: #ffffff;
629
+ --md3-primary-fixed: #E8E5FF; /* the accent tint: a chip or a selected row on paper */
630
+ --md3-primary-fixed-dim: color-mix(in srgb, var(--md3-primary-fixed) 85%, var(--accent-text));
631
+ --md3-secondary: var(--accent-text);
632
+ --md3-secondary-dim: var(--btn-primary-bg);
633
+ --md3-secondary-container: var(--md3-primary-fixed);
634
+ --md3-on-secondary: #ffffff;
635
+ --md3-on-secondary-container: var(--accent-text);
636
+ --md3-inverse-primary: #A5B4FC;
637
+ --md3-surface-tint: var(--brand-primary);
638
+ --accent-surface-soft: color-mix(in srgb, var(--btn-primary-bg) 5%, transparent);
639
+ --accent-surface-strong: color-mix(in srgb, var(--btn-primary-bg) 13%, transparent);
640
+ --bg-selection: color-mix(in srgb, var(--btn-primary-bg) 16%, transparent);
641
+ --border-accent: color-mix(in srgb, var(--btn-primary-hover) 18%, transparent);
642
+ --border-accent-hover: color-mix(in srgb, var(--btn-primary-hover) 35%, transparent);
643
+ --tangle-gradient: linear-gradient(135deg, var(--btn-primary-bg), var(--btn-primary-hover));
644
+ --tangle-gradient-text: var(--tangle-gradient);
645
+ --accent-gradient-strong: var(--tangle-gradient);
646
+
647
+ /* ── Status: the deep inks carry white, so each is its own fill too ─────── */
648
+ --hsl-success: 162.9 93.5% 24.3%; /* #047857 */
649
+ --hsl-warning: 25.9 90% 35.3%; /* #ab4f09 */
650
+ --hsl-destructive: 0 73.7% 41.8%; /* #b91c1c */
651
+ --hsl-destructive-foreground: 0 0% 100%;
652
+ --hsl-info: 201.3 96.3% 32.2%; /* #0369a1 */
714
653
  --surface-success-bg: #ecfdf5;
715
654
  --surface-success-border: #a7f3d0;
716
- --surface-success-text: #047857;
717
655
  --surface-warning-bg: #fffbeb;
718
656
  --surface-warning-border: #fde68a;
719
- --surface-warning-text: #ab4f09;
720
657
  --surface-danger-bg: #fff1f2;
721
658
  --surface-danger-border: #fecdd3;
722
- --surface-danger-text: #b91c1c;
723
659
  --surface-info-bg: #f0f9ff;
724
660
  --surface-info-border: #bae6fd;
725
- --surface-info-text: #0369a1;
726
661
  /* tones:begin — generated by scripts/gen-ladders.mjs; edit its CATEGORY map, not these lines. */
727
662
  --tone-violet-bg: #f7edfe;
728
663
  --tone-violet-bg-hover: #f2e2fc;
@@ -790,7 +725,6 @@
790
725
  --surface-teal-border: #a1ded2;
791
726
  --surface-teal-text: #017d6d;
792
727
  /* tones:end */
793
-
794
728
  /* Run-mix ramp — the fills of a proportional status bar.
795
729
  *
796
730
  * Separate from the `--surface-*-text` tones because a 6px bar is read
@@ -815,119 +749,14 @@
815
749
  --run-mix-in-flight: #00679e;
816
750
  --run-mix-cancelled: #515367;
817
751
 
818
- --surface-neutral-bg: var(--depth-3);
819
- --surface-neutral-border: var(--border-subtle);
820
- --surface-neutral-text: var(--text-muted);
821
-
822
- /* Sidebar — light, flat surface + hairline */
823
- --sidebar-background: 0 0% 100%; /* white paper */
824
- --sidebar-foreground: 0 0% 17.6471%;
825
- --sidebar-primary: 246 54% 51%; /* #4c3fc6 */
826
- --sidebar-primary-foreground: 0 0% 100%;
827
- --sidebar-accent: 0 0% 94.902%; /* L3 hover well */
828
- --sidebar-accent-foreground: 0 0% 17.6471%;
829
- --sidebar-border: 0 0% 81.5686%; /* = --md3-outline-variant */
830
- --sidebar-ring: 246 54% 51%;
831
-
832
- --mesh-teal: transparent;
833
- --mesh-violet: transparent;
834
- --mesh-blue: transparent;
835
-
836
- /* Surfaces — flat single-neutral light */
837
- --bg-root: #eceef3;
838
- --bg-dark: var(--depth-1);
839
- --bg-card: var(--depth-2);
840
- /* Paper, exactly like --bg-card: in light you cannot raise a plane by making it
841
- * lighter than white, so an overlay lifts by SHADOW. A consumer that fills with
842
- * --bg-elevated and applies no shadow gets no separation from a card — that is
843
- * the contract, not an oversight (--hsl-popover is white for the same reason).
844
- * Pair it with --shadow-dropdown. */
845
- --bg-elevated: var(--depth-4);
846
- --bg-section: var(--depth-1);
847
- --bg-input: hsl(var(--hsl-input)); /* the field well — same color, hex-free */
848
- --bg-hover: rgba(76, 63, 198, 0.06);
849
- --bg-selection: rgba(76, 63, 198, 0.16);
850
-
851
- /* Mirrors the dark ramp's spacing against white paper. The ink uses neutral
852
- * grays so purple stays reserved for interaction accents.
853
- *
854
- * --text-dim is pinned from the CANVAS (#eceef3), not the white card: it is
855
- * the faintest tier, so the lighter L0 is the surface that binds it. Its 4.6:1
856
- * canvas and 5.6:1 card contrasts clear the body-text floor, as asserted in
857
- * tokens.test.ts, because a ratio quoted in a comment drifts the moment a
858
- * surface moves and nothing catches it. */
859
- --text-primary: #2d2d2d;
860
- --text-secondary: #3f3f3f;
861
- --text-muted: #4c4c4c;
862
- --text-dim: #686868;
863
-
864
- --brand-primary: #6366F1;
865
- --brand-strong: #2E2A5E;
866
- --brand-strong-text: #ffffff;
867
- --brand-strong-text-muted: rgba(255, 255, 255, 0.7);
868
- --brand-strong-text-dim: rgba(255, 255, 255, 0.5);
869
- --brand-cool: #818CF8;
870
- --brand-glow: #A5AAFC;
871
- --brand-purple: #6366F1;
872
- --brand-vibrant: #6366F1;
873
- --brand-emerald: #059669;
874
-
875
- --border-subtle: rgba(0, 0, 0, 0.10);
876
- --border-default: rgba(0, 0, 0, 0.14);
877
- --border-hover: rgba(0, 0, 0, 0.24);
878
- --border-accent: rgba(91, 78, 212, 0.18);
879
- --border-accent-hover: rgba(91, 78, 212, 0.35);
880
-
881
- --btn-primary-bg: #4C3FC5;
882
- --btn-primary-hover: #5A50D6;
883
- --btn-primary-text: #ffffff;
884
- --btn-cta-bg: #4C3FC5;
885
- --btn-cta-text: #ffffff;
886
-
887
- --code-keyword: #3E349E;
888
- --code-string: #059669;
889
- --code-function: #352B88;
890
- --code-number: #B45309;
891
- --code-success: #059669;
892
- --code-comment: #6B6E8A;
893
- --code-error: #ba1a1a;
752
+ --glass-bg-strong: var(--depth-1);
753
+ --code-header-bg: color-mix(in srgb, hsl(var(--hsl-muted)) 50%, transparent);
894
754
 
895
755
  /* Shadows — flat, no inner ring, no glow */
896
756
  --shadow-card: 0 1px 2px rgba(25, 28, 36, 0.06);
897
757
  --shadow-dropdown: 0 8px 30px rgba(25, 28, 36, 0.10);
898
758
  --shadow-glow: none;
899
759
  --shadow-accent: 0 1px 2px rgba(25, 28, 36, 0.06);
900
-
901
- /* Indigo spark kept for logo + send; accent-fill is the active row well */
902
- --tangle-gradient: linear-gradient(135deg, #4F46E5, #5B4ED4);
903
- --tangle-gradient-text: linear-gradient(135deg, #4F46E5, #5B4ED4);
904
- --accent-gradient-strong: linear-gradient(135deg, #4F46E5, #5B4ED4);
905
- --accent-surface-soft: rgba(76, 63, 198, 0.05);
906
- --accent-surface-strong: rgba(76, 63, 198, 0.13);
907
- --accent-text: #3c32e9;
908
-
909
- /* Typography — unified on Inter */
910
- --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
911
- --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
912
- --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
913
-
914
- /* Radius — unified chrome scale (matches dark; 8px md) */
915
- --radius: 0.5rem;
916
- --radius-sm: 6px;
917
- --radius-md: 8px;
918
- --radius-lg: 10px;
919
- --radius-xl: 12px;
920
- --radius-pill: 999px;
921
- --radius-full: 999px;
922
- --radius-composer: 26px;
923
- /* Radius by role. New surfaces pick the role they play; system.css repeats
924
- these for pages that load it on its own ladders. */
925
- --radius-tag: 4px;
926
- --radius-chip: 8px;
927
- --radius-field: 12px;
928
- --radius-panel: 16px;
929
- --radius-cover: 20px;
930
- --radius-sheet: 24px;
931
760
  }
932
761
 
933
762