@lovett/ui 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.d.ts +2659 -0
  2. package/dist/index.js +14451 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/styles.css +636 -0
  5. package/dist/tokens.css +700 -0
  6. package/package.json +44 -17
  7. package/src/__tests__/button.test.tsx +137 -0
  8. package/src/__tests__/card.test.tsx +103 -0
  9. package/src/__tests__/dead-render.test.tsx +117 -0
  10. package/src/__tests__/input.test.tsx +134 -0
  11. package/src/__tests__/modal.test.tsx +154 -0
  12. package/src/__tests__/page-shell.test.tsx +128 -0
  13. package/src/__tests__/setup.ts +43 -0
  14. package/src/__tests__/token-shape.test.ts +193 -0
  15. package/src/allocation-sparkbar.tsx +90 -0
  16. package/src/card.tsx +1 -1
  17. package/src/collapsible-card.tsx +85 -0
  18. package/src/data-grid/table-body.tsx +8 -1
  19. package/src/dropdown-menu.tsx +1 -1
  20. package/src/floating-status-bar.tsx +112 -0
  21. package/src/folder-tree-picker.tsx +5 -6
  22. package/src/frame-stack.tsx +27 -10
  23. package/src/hero-form-card.tsx +2 -2
  24. package/src/icons/brand.tsx +187 -0
  25. package/src/index.ts +43 -0
  26. package/src/lib/clipboard.ts +14 -0
  27. package/src/lib/color.ts +111 -0
  28. package/src/meta-cell.tsx +52 -0
  29. package/src/meta-previews/MetaFeedCarousel.tsx +1 -1
  30. package/src/meta-previews/MetaFeedPreview.tsx +1 -1
  31. package/src/microsoft-logo.tsx +33 -0
  32. package/src/modal.tsx +77 -6
  33. package/src/pill-button.tsx +23 -5
  34. package/src/profile-section.tsx +40 -9
  35. package/src/sortable-table.tsx +5 -1
  36. package/src/styles.css +74 -0
  37. package/src/tabs.tsx +4 -0
  38. package/src/tag-chip-input.tsx +1 -1
  39. package/src/theme-v2.css +466 -0
  40. package/src/token-badge.tsx +92 -0
  41. package/src/tokens.css +181 -60
  42. package/src/v2/README.md +208 -0
  43. package/src/v2/__demo__/showcase.tsx +1045 -0
  44. package/src/v2/action.tsx +91 -0
  45. package/src/v2/callout.tsx +76 -0
  46. package/src/v2/document-section.tsx +82 -0
  47. package/src/v2/document-shell.tsx +0 -0
  48. package/src/v2/field-row.tsx +113 -0
  49. package/src/v2/icons.tsx +165 -0
  50. package/src/v2/index.ts +147 -0
  51. package/src/v2/layout.tsx +293 -0
  52. package/src/v2/progress-track.tsx +89 -0
  53. package/src/v2/stat-tile.tsx +129 -0
  54. package/src/v2/states.tsx +271 -0
  55. package/src/v2/status-pill.tsx +74 -0
  56. package/src/v2/theme.css +1861 -0
  57. package/src/v2/timeline.tsx +81 -0
  58. package/src/v2/tokens.ts +228 -0
  59. package/src/value-chip.tsx +76 -0
@@ -0,0 +1,700 @@
1
+ /*
2
+ * @lovett/ui — design tokens
3
+ *
4
+ * Ported from brand-ai-extension/webapp/src/index.css
5
+ * tag : v-pre-workspace-intake-phase1
6
+ * sha : 172dd010fee808bcd03795119ba888206da5bafd
7
+ *
8
+ * Note: in the source repo, tokens live in `webapp/src/index.css`. PRD §7.2
9
+ * still references `webapp/src/styles/tokens.css` — that path does not exist
10
+ * in source. Flagged in Phase 1 completion report as a PRD-correction.
11
+ *
12
+ * This file contains ONLY token declarations (`:root` + `[data-theme="light"]`).
13
+ * Component CSS utilities the primitives consume (`.ds-card-surface`,
14
+ * `.btn`, `.input-shell`, etc.) live in `./styles.css`.
15
+ *
16
+ * Token discipline (CLAUDE.md §4 is the human-readable source of truth;
17
+ * `packages/eslint-config/index.js` mirrors the internal list for ESLint
18
+ * enforcement; ADR-008 Decision 7 makes these three a hard-gate parity check).
19
+ *
20
+ * PUBLIC tokens (apps may use freely):
21
+ * • Brand: --accent, --accent-hover, --accent-muted,
22
+ * --accent-subtle, --accent-glow
23
+ * • Hierarchy: --hierarchy-red, --hierarchy-red-dark,
24
+ * --hierarchy-red-darkest, --hierarchy-red-shadow,
25
+ * --hierarchy-outline, --hierarchy-slate,
26
+ * --hierarchy-on-base
27
+ * (theme-INDEPENDENT; fixed in both themes because
28
+ * the hierarchy SVG overlay sits on an always-light
29
+ * OSM basemap regardless of UI theme)
30
+ * • Text: --foreground, --text-secondary, --text-tertiary,
31
+ * --text-muted, --muted-foreground
32
+ * • Semantic: --success(-bg), --warning(-bg), --info(-bg),
33
+ * --destructive(-bg, -foreground)
34
+ * • Folder: --folder-red/amber/emerald/teal/blue/violet/pink/
35
+ * slate/ink (categorical palette for folder accents)
36
+ * • Spacing: --space-1…12 (4 / 8 / 12 / 16 / 20 / 24 / 32 / 40 / 48)
37
+ * • Radius: --radius-xs/sm/md/lg/xl/2xl/full, --radius (legacy)
38
+ * • Motion: --ease-out, --ease-spring, --ease-in-out,
39
+ * --dur-fast, --dur-base, --dur-slow,
40
+ * --transition, --transition-slow (legacy)
41
+ * • Controls: --ctrl-sm/md/lg (32 / 40 / 48 — Button/Input heights)
42
+ * • Focus: --ring-focus, --ring-error
43
+ * • Shadow: --shadow-sm/md/lg/xl/2xl, --shadow-glow
44
+ * • Visual utilities (generic, not surface-specific):
45
+ * --border, --border-strong (divider / separator lines)
46
+ * --surface-overlay, --surface-overlay-soft/strong/stronger
47
+ * (hover backgrounds, badge fills)
48
+ * --surface-on-accent (thumb / knob surface on accent-
49
+ * colored backgrounds — switches,
50
+ * sliders, badges-on-accent)
51
+ * --muted (muted fill for ghost UI)
52
+ * --header-height (sticky AppHeader height — referenced
53
+ * by sticky chip bars; same value lives
54
+ * in src/lib/layout.ts for JS callsites)
55
+ * --section-scroll-offset (in-page anchor scroll-margin)
56
+ * --prose-narrow, --prose-wide (reading-width caps)
57
+ * --inset-highlight (top-edge highlight strip; tokenized to
58
+ * avoid copy-pasting the rgba into many
59
+ * boxShadow strings)
60
+ * --font-sans (default font stack)
61
+ *
62
+ * INTERNAL tokens (primitives only — ESLint-enforced):
63
+ * • Background / surface tokens consumed by specific primitives:
64
+ * --background, --bg-card, --bg-card-hover, --bg-surface,
65
+ * --bg-elevated, --bg-input, --bg-input-hover, --page-bg,
66
+ * --main-content-bg, --main-content-border, --card, --card-foreground,
67
+ * --popover, --popover-foreground, --canvas-placement-bg
68
+ * • <Card> internals: --border-card, --card-foot-bg,
69
+ * --card-shadow-rest, --card-shadow-hover,
70
+ * --card-frame-bg, --tray-bg, --tray-inset, --radius-tray
71
+ * • <Input> internals: --border-input, --input
72
+ * • <Modal> internals: --modal-overlay
73
+ * • Sidebar internals (only <Sidebar/> reads these): --sidebar-bg,
74
+ * --sidebar-surface, --sidebar-surface-hover, --sidebar-surface-active,
75
+ * --sidebar-text, --sidebar-text-bright, --sidebar-text-dim,
76
+ * --sidebar-border, --sidebar-section-label, --sidebar-rail,
77
+ * --sidebar-width, --sidebar-collapsed-width
78
+ * • Toast internals (only <Toaster/> reads these): --toast-bg,
79
+ * --toast-text, --toast-border
80
+ * • shadcn legacy aliases primitives may still rely on:
81
+ * --secondary, --secondary-foreground
82
+ * • Legacy brand aliases kept working for existing pages:
83
+ * --primary, --primary-hover, --primary-foreground, --ring
84
+ */
85
+
86
+ :root {
87
+ /* ============================================================
88
+ BRAND ACCENT (red) — PUBLIC
89
+ ============================================================ */
90
+ --accent: 207 14 15;
91
+ --accent-hover: 227 26 27;
92
+ --accent-muted: 207 14 15 / 0.12;
93
+ --accent-subtle: 207 14 15 / 0.06;
94
+ --accent-glow: 207 14 15 / 0.25;
95
+
96
+ /* ============================================================
97
+ HIERARCHY MAP PALETTE — PUBLIC, theme-INDEPENDENT.
98
+ The hierarchy SVG overlay sits on top of an always-light OSM
99
+ basemap regardless of UI theme. These tokens are fixed in both
100
+ light and dark mode so the overlay reads consistently. Sourced
101
+ from zip-search src/index.css:95-211 (hierarchy block).
102
+ ============================================================ */
103
+
104
+ /* Brand-red ladder — used by state pills, stacked NE callouts,
105
+ drilled-state outlines, connector lines, hover/selected states. */
106
+ --hierarchy-red: 231 12 7; /* primary fill */
107
+ --hierarchy-red-dark: 184 9 5; /* hover fill, selected stroke */
108
+ --hierarchy-red-darkest: 139 7 4; /* selected fill */
109
+ --hierarchy-red-shadow: 75 3 2; /* near-black; selected stacked-pill stroke */
110
+
111
+ /* Slate ladder — default outlines + context-state polygon + label fills. */
112
+ --hierarchy-outline: 71 85 105; /* slate-600; default outline stroke */
113
+ --hierarchy-slate: 100 116 139; /* slate-500; context-state polygon + label fill */
114
+
115
+ /* White triplet — for the translucent default state-polygon fill. */
116
+ --hierarchy-on-base: 255 255 255;
117
+
118
+ /* Legacy brand aliases — kept working for existing primitive code paths.
119
+ INTERNAL: lenses/routes/components should use --accent / --foreground. */
120
+ --primary: var(--accent);
121
+ --primary-hover: var(--accent-hover);
122
+ --primary-foreground: 255 255 255;
123
+ --ring: var(--accent);
124
+
125
+ /* ============================================================
126
+ DARK THEME SURFACES — INTERNAL (primitives only)
127
+ ============================================================ */
128
+ --foreground: 244 244 245;
129
+ /* PUBLIC (ADR-063 D1): opaque elevated surface for bespoke lens/app
130
+ floating chrome (pills, capsules, one-off raised panels) that can't be a
131
+ primitive. Same values as the INTERNAL --bg-elevated by design. */
132
+ --card-foreground: 244 244 245;
133
+ --popover-foreground: 244 244 245;
134
+ --muted-foreground: 161 161 170;
135
+ --text-secondary: 161 161 170;
136
+ /* Lifted 113->132 / 82->106: at the old values --text-tertiary measured
137
+ 3.56:1 and --text-muted 2.22:1 on --bg-card, i.e. both under the 3:1
138
+ floor on the surface they are most often used on. */
139
+ --text-tertiary: 132 132 141;
140
+ --text-muted: 106 106 115;
141
+ --secondary-foreground: 244 244 245;
142
+
143
+ --border: 255 255 255 / 0.08;
144
+ --border-strong: 255 255 255 / 0.14;
145
+ /* Semantic — PUBLIC */
146
+ --success: 16 185 129;
147
+ --success-bg: 16 185 129 / 0.12;
148
+ --warning: 245 158 11;
149
+ --warning-bg: 245 158 11 / 0.12;
150
+ --info: 59 130 246;
151
+ --info-bg: 59 130 246 / 0.12;
152
+ --destructive: 239 68 68;
153
+ --destructive-bg: 239 68 68 / 0.12;
154
+ --destructive-foreground: 255 255 255;
155
+
156
+ /* Folder palette — PUBLIC. Categorical accents for Asset folders
157
+ (ADR-035). Tuned brighter here so they read on near-black cards;
158
+ the light theme block below carries darker, more saturated values.
159
+ Folders store a colour KEY ('red' | 'amber' | …), never a hex. */
160
+ --folder-red: 239 68 68;
161
+ --folder-amber: 245 158 11;
162
+ --folder-emerald: 16 185 129;
163
+ --folder-teal: 20 184 166;
164
+ --folder-blue: 59 130 246;
165
+ --folder-violet: 139 92 246;
166
+ --folder-pink: 236 72 153;
167
+ --folder-slate: 148 163 184;
168
+ --folder-ink: 203 213 225;
169
+
170
+ /* ============================================================
171
+ SIDEBAR — INTERNAL (only <Sidebar/> reads these)
172
+ ============================================================ */
173
+ --sidebar-bg: 10 10 11;
174
+ --sidebar-surface: 255 255 255 / 0.04;
175
+ --sidebar-surface-hover: 255 255 255 / 0.08;
176
+ --sidebar-surface-active: 207 14 15 / 0.15;
177
+ --sidebar-text: 161 161 170;
178
+ --sidebar-text-bright: 244 244 245;
179
+ --sidebar-text-dim: 113 113 122;
180
+ --sidebar-border: 255 255 255 / 0.06;
181
+ --sidebar-section-label: 82 82 91;
182
+ --sidebar-rail: 255 255 255 / 0.08;
183
+ --sidebar-width: 260px;
184
+ --sidebar-collapsed-width: 56px;
185
+
186
+ /* ============================================================
187
+ LAYOUT — PUBLIC (generic visual utilities)
188
+ ============================================================ */
189
+ --header-height: 56px;
190
+ --section-scroll-offset: 120px;
191
+ --prose-narrow: 32.5rem;
192
+ --prose-wide: 42rem;
193
+ --inset-highlight: 255 255 255 / 0.05;
194
+
195
+ /* Bullet primitives — PUBLIC (generic visual utilities).
196
+ Used by atoms.ListItem + lens bullet markers (e.g. value-section).
197
+ `--bullet-offset-y` is the marginTop needed to optically center a
198
+ `--bullet-size` dot against a line of `text-[13px]` body copy with
199
+ line-height 1.5. If a future callsite needs a different alignment
200
+ (e.g. larger text, looser leading), override `marginTop` per-callsite
201
+ rather than introducing a per-context token. */
202
+ --bullet-size: 5px;
203
+ --bullet-offset-y: 7px;
204
+
205
+ /* Recessed card footer strip — INTERNAL (<Card.Foot>) */
206
+ /* Card surface — INTERNAL (<Card>) */
207
+ --border-card: 255 255 255 / 0.08;
208
+ --card-shadow-rest:
209
+ 0 1px 2px rgb(0 0 0 / 0.20);
210
+ --card-shadow-hover:
211
+ 0 4px 12px rgb(0 0 0 / 0.28);
212
+
213
+ /* Card-tray — INTERNAL (<Card.Tray>) */
214
+ --tray-inset: 4px;
215
+ --radius-tray: var(--radius-md);
216
+
217
+ /* Card frame — INTERNAL (outer .ds-card-surface bg in framed mode) */
218
+ /* ============================================================
219
+ CALCULATOR SHELL v2 — INTERNAL (ADR-076 D7). Consumed only by
220
+ the v2 primitives (CalculatorShellV2 header, StatRow tiles) and
221
+ the recessed .ds-card-tray. rgb triples / shadow lists, no hex.
222
+ ============================================================ */
223
+ /* Near-black brand surface — v2 header + StatRow tiles. The only
224
+ heavy-black surface in the kit (ADR-076 D4); black is reserved
225
+ for the header and StatRow tiles, nowhere else. The prototype's
226
+ #0a0a0b hex is non-compliant, so this is an rgb triple. */
227
+ --brand-ink: 0 0 0;
228
+ /* v2 shell card elevation — multi-layer drop shadow + top-edge
229
+ inner highlight (the "read depth as quality" ladder). */
230
+ --shell-shadow:
231
+ inset 0 1px 0 rgb(255 255 255 / 0.05),
232
+ 0 4px 12px rgb(0 0 0 / 0.35),
233
+ 0 16px 48px rgb(0 0 0 / 0.45);
234
+ /* Recessed-tray shadow — the bright inner tray reads as inset. */
235
+ --tray-shadow: inset 0 1px 2px rgb(0 0 0 / 0.18);
236
+
237
+ /* ============================================================
238
+ MAIN CONTENT AREA — INTERNAL (layout primitives only)
239
+ ============================================================ */
240
+ --main-content-border: 255 255 255 / 0.08;
241
+
242
+ /* ============================================================
243
+ RADIUS SCALE — PUBLIC
244
+ ============================================================ */
245
+ --radius-sm: 8px;
246
+ --radius-md: 12px;
247
+ --radius-lg: 16px;
248
+ --radius-xl: 20px;
249
+ --radius-2xl: 24px;
250
+ --radius-full: 9999px;
251
+ --radius: 0.5rem; /* legacy alias */
252
+
253
+ /* ============================================================
254
+ SHADOWS — PUBLIC
255
+ ============================================================ */
256
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
257
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.25);
258
+ --shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.35);
259
+ --shadow-glow: 0 0 20px rgb(207 14 15 / 0.25);
260
+
261
+ /* Transitions — PUBLIC (legacy bundled motion; --dur and --ease are preferred). */
262
+ --transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);
263
+ --transition-slow: 300ms cubic-bezier(0.16, 1, 0.3, 1);
264
+
265
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
266
+
267
+ /* Monospace stack — added 2026-05-18 per ADR-023 D17.
268
+ * JetBrains Mono is loaded from Google Fonts in apps/<app>/index.html
269
+ * (added per pre-batch coordination commit). Falls back to platform
270
+ * monospaces if the @font-face fails. Used by tabular numeric data
271
+ * in keywords table (Phase 8), SEMSpec ad-strength / CPC / search
272
+ * volume columns (Phase 12), and any other tabular-data surfaces.
273
+ * Consume via Tailwind `font-mono` utility (resolves to this stack). */
274
+ --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
275
+
276
+ /* ============================================================
277
+ LIGHT-MODE-READY ADDITIONS (dark values; light overrides below)
278
+ ============================================================ */
279
+ --surface-overlay-soft: 255 255 255 / 0.04;
280
+ --surface-overlay: 255 255 255 / 0.06;
281
+ --surface-overlay-strong: 255 255 255 / 0.10;
282
+ --surface-overlay-stronger: 255 255 255 / 0.16;
283
+ --modal-overlay: 0 0 0 / 0.65;
284
+
285
+ /* `--surface-on-accent` — PUBLIC (Phase Sidebar-E MIN-1, ADR-049).
286
+ * The "thumb on a colored track" surface: switch thumbs, slider
287
+ * knobs, badge fills sitting on accent-colored backgrounds. Both
288
+ * themes default to pure white because thumbs read as white against
289
+ * red accents in either light or dark mode — the alternative is a
290
+ * theme-aware off-white that buys nothing. Kept as a public token
291
+ * (rather than a raw `white` literal) so the boundary lint can
292
+ * enforce it and so a future redesign can re-tune both themes from
293
+ * one place. Consumers wrap as `rgb(var(--surface-on-accent))`. */
294
+ --surface-on-accent: 255 255 255;
295
+
296
+ --border-input: 255 255 255 / 0.10;
297
+
298
+ --shadow-xl: 0 16px 40px rgb(0 0 0 / 0.55);
299
+ --shadow-2xl: 0 24px 60px rgb(0 0 0 / 0.60);
300
+
301
+ --ring-focus: 0 0 0 3px rgb(207 14 15 / 0.25);
302
+ --ring-error: 0 0 0 3px rgb(239 68 68 / 0.25);
303
+
304
+ --radius-xs: 6px;
305
+
306
+ --ctrl-sm: 32px;
307
+ --ctrl-md: 40px;
308
+ --ctrl-lg: 48px;
309
+
310
+ /* Spacing scale (4px base) — PUBLIC */
311
+ --space-1: 4px;
312
+ --space-2: 8px;
313
+ --space-3: 12px;
314
+ --space-4: 16px;
315
+ --space-5: 20px;
316
+ --space-6: 24px;
317
+ --space-8: 32px;
318
+ --space-10: 40px;
319
+ --space-12: 48px;
320
+
321
+ /* Motion — PUBLIC */
322
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
323
+ --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
324
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
325
+ --dur-fast: 140ms;
326
+ --dur-base: 240ms;
327
+ --dur-slow: 420ms;
328
+
329
+ /* Toast — INTERNAL (only <Toaster/> reads these) */
330
+ --toast-bg: 17 17 19;
331
+ --toast-text: 244 244 245;
332
+ --toast-border: 255 255 255 / 0.10;
333
+
334
+ /* Canvas placement bg — INTERNAL (placement primitive) */
335
+
336
+ /* Surface ladder — dark. Same five roles, same hue family. */
337
+ --surface-page: 11 11 12;
338
+ --surface-frame: 21 21 23;
339
+ --surface-card: 27 27 30;
340
+ --surface-inset: 45 45 48;
341
+ --surface-hover: 55 55 59;
342
+ /* A control you can act on — input, select, option tile, chip-button.
343
+ Sits at CARD level on purpose: in light that is white against the
344
+ frame behind it, which reads "type here"; in dark it is raised above
345
+ the frame. --muted/--surface-inset is the RECESSED role (wells, zebra,
346
+ table headers) and reads as disabled when a control borrows it. The
347
+ lenses were borrowing it because --bg-input is internal and this
348
+ public role did not exist. */
349
+ --surface-control: var(--surface-card);
350
+ --surface-elevated: 37 37 40;
351
+
352
+ --page-bg: var(--surface-page);
353
+ --background: var(--surface-page);
354
+ --main-content-bg: var(--surface-page);
355
+ --bg-surface: var(--surface-frame);
356
+ --bg-card: var(--surface-card);
357
+ --card: var(--surface-card);
358
+ --tray-bg: var(--surface-card);
359
+ --card-frame-bg: var(--surface-frame);
360
+ --card-foot-bg: var(--surface-frame);
361
+ --bg-elevated: var(--surface-elevated);
362
+ --surface-raised: var(--surface-elevated);
363
+ --popover: var(--surface-elevated);
364
+ --bg-input: var(--surface-frame);
365
+ --muted: var(--surface-inset);
366
+ --input: var(--surface-inset);
367
+ --secondary: var(--surface-inset);
368
+ --canvas-placement-bg: var(--surface-frame);
369
+ --bg-card-hover: var(--surface-hover);
370
+ --bg-input-hover: var(--surface-inset);
371
+
372
+ }
373
+
374
+ /* ============================================================
375
+ LIGHT THEME OVERRIDES
376
+ Only the flip + re-tune tokens land here; sidebar / toast /
377
+ radii / spacing / motion all stay invariant on :root.
378
+
379
+ THREE-MODE SYSTEM (2026-05-26): three values for data-theme:
380
+ dark — :root defaults (everything dark)
381
+ light — full light theme (this block + sidebar light overrides)
382
+ combo — light surfaces + DARK sidebar
383
+ "combo" inherits the dark --sidebar-* from :root by NOT redeclaring
384
+ them. The shared light surface tokens are gathered under the
385
+ combined `[data-theme="light"], [data-theme="combo"]` selector
386
+ below; the --sidebar-* overrides sit in a `[data-theme="light"]`-
387
+ only block immediately after.
388
+ ============================================================ */
389
+ [data-theme="light"], [data-theme="combo"] {
390
+ /* Brand accent — base stays, hover darkens, glow softens */
391
+ --accent-hover: 178 8 9;
392
+ --accent-muted: 207 14 15 / 0.10;
393
+ --accent-subtle: 207 14 15 / 0.05;
394
+ --accent-glow: 207 14 15 / 0.20;
395
+
396
+ /* Light-mode neutral palette (2026-05-18, iteration 2) — owner
397
+ * iterated from "kill the steel undertone" → "still too gray; push
398
+ * as close to pure #FFF without quite touching it." Page surface is
399
+ * now 254/254/254 (one unit shy of pure white, perfectly neutral).
400
+ * Cards stay 255 — fill delta is just 1 unit, so card chrome leans
401
+ * almost entirely on --border-card (0.07). If cards visually
402
+ * disappear, bump --border-card. Hover/muted/input-hover stay
403
+ * darker on purpose so state changes remain visible. */
404
+ --foreground: 17 17 19;
405
+ /* PUBLIC (ADR-063 D1) — see the dark block note. */
406
+ --card-foreground: 17 17 19;
407
+ --popover-foreground: 17 17 19;
408
+ --muted-foreground: 82 82 91;
409
+ --text-secondary: 82 82 91;
410
+ /* --text-tertiary stays from :root */
411
+ /* Lifted 161->137 — was 2.56:1 on a white card. */
412
+ --text-muted: 137 137 146;
413
+ --secondary-foreground: 17 17 19;
414
+
415
+ --border: 0 0 0 / 0.12;
416
+ --border-strong: 0 0 0 / 0.20;
417
+ /* Bumped from 0.16 → 0.22 — inputs on a white --bg-input (above) sitting
418
+ * inside a Card (--bg-card = 255 / same as input) need a stronger border
419
+ * to delineate. The 0.16 value worked when --bg-input was slate-tinted; on
420
+ * white-on-white the prior border was too subtle. */
421
+ --border-input: 0 0 0 / 0.22;
422
+ --success: 5 150 105;
423
+ --success-bg: 5 150 105 / 0.10;
424
+ /* `--warning` and the folder palette are intentionally split out
425
+ to a `[data-theme="light"]`-only block below — combo mode keeps
426
+ the brighter `:root` values so group-card colors stay readable
427
+ against the dark `--sidebar-bg` it inherits. */
428
+ --info: 37 99 235;
429
+ --info-bg: 37 99 235 / 0.10;
430
+ --destructive: 220 38 38;
431
+ --destructive-bg: 220 38 38 / 0.10;
432
+
433
+ --surface-overlay-soft: 0 0 0 / 0.03;
434
+ --surface-overlay: 0 0 0 / 0.05;
435
+ --surface-overlay-strong: 0 0 0 / 0.08;
436
+ --surface-overlay-stronger: 0 0 0 / 0.12;
437
+ --modal-overlay: 0 0 0 / 0.40;
438
+ /* `--surface-on-accent` stays white in light mode too — see :root
439
+ * comment. Declared here for symmetry / future-proofing. */
440
+ --surface-on-accent: 255 255 255;
441
+
442
+ --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06);
443
+ --shadow-md: 0 4px 12px rgb(0 0 0 / 0.08);
444
+ --shadow-lg: 0 8px 25px rgb(0 0 0 / 0.10);
445
+ --shadow-xl: 0 16px 40px rgb(0 0 0 / 0.12);
446
+ --shadow-2xl: 0 24px 60px rgb(0 0 0 / 0.16);
447
+ --shadow-glow: 0 0 20px rgb(207 14 15 / 0.20);
448
+
449
+ --ring-focus: 0 0 0 3px rgb(207 14 15 / 0.20);
450
+ --ring-error: 0 0 0 3px rgb(220 38 38 / 0.18);
451
+
452
+ /* Match --page-bg (254/254/254). Was 241/245/249 (slate) — that's
453
+ * what was actually painting "the steely background" the owner
454
+ * flagged; --page-bg is the body, but MainLayout's content area
455
+ * paints with this token. Both must move together. */
456
+ --main-content-border: 0 0 0 / 0.10;
457
+ /* Calculator Shell v2 — light overrides (ADR-076 D7). */
458
+ --brand-ink: 20 20 23;
459
+ --shell-shadow:
460
+ inset 0 1px 0 rgb(255 255 255 / 0.8),
461
+ 0 2px 6px rgb(0 0 0 / 0.06),
462
+ 0 16px 40px rgb(0 0 0 / 0.10);
463
+ --tray-shadow: inset 0 1px 2px rgb(0 0 0 / 0.04);
464
+
465
+ /* Bumped from 0.07 → 0.12 (matches public --border) — when --page-bg
466
+ * moved to 254 (near-white) and --bg-card stayed 255, cards lost their
467
+ * fill-contrast affordance. The border now does the visual work, same
468
+ * opacity as Button secondary border (the "Generate from URL" look the
469
+ * owner called the gold standard). */
470
+ --border-card: 0 0 0 / 0.12;
471
+ --card-shadow-rest:
472
+ inset 0 1px 0 rgb(255 255 255 / 0.7),
473
+ 0 1px 2px rgb(0 0 0 / 0.06),
474
+ 0 4px 10px rgb(0 0 0 / 0.06);
475
+ --card-shadow-hover:
476
+ inset 0 1px 0 rgb(255 255 255 / 0.7),
477
+ 0 2px 4px rgb(0 0 0 / 0.07),
478
+ 0 8px 24px rgb(0 0 0 / 0.09);
479
+
480
+ /* ============================================================
481
+ SURFACE LADDER — the canonical set (2026-08-04)
482
+ ------------------------------------------------------------
483
+ Before this, 19 surface tokens carried 4 distinct values, in
484
+ THREE different grey families: --card-frame-bg / --card-foot-bg
485
+ at hue 248 (slate), --muted / --bg-card-hover at 90
486
+ (achromatic), --input / --secondary at 286 (zinc). Mixing grey
487
+ families is what reads as "inconsistent" long before anyone can
488
+ say which value is wrong.
489
+
490
+ Everything now sits on hue 286 — the family the TEXT ramp
491
+ already uses (--foreground and every --text-* measure 285.8-286.4),
492
+ so greys and type finally agree.
493
+
494
+ FIVE roles. Every legacy name below is an alias onto one of them,
495
+ so no component changes and nothing is orphaned:
496
+
497
+ page the document. PURE WHITE — no grey backdrop.
498
+ card cards, trays, inputs, popovers. Also white; separation
499
+ is border + shadow, not a fill step.
500
+ frame the CONTAINED BACKGROUND for a group of sections —
501
+ <FrameStack> wrapping several <SectionCard>. The only
502
+ place a grey surface is correct.
503
+ inset recessed wells, table header strips, zebra rows.
504
+ hover the deepest step; hover on an inset.
505
+ ============================================================ */
506
+ --surface-page: 255 255 255;
507
+ --surface-card: 255 255 255;
508
+ --surface-frame: 247 247 249;
509
+ --surface-inset: 242 242 244;
510
+ --surface-hover: 238 238 240;
511
+ /* A control you can act on — input, select, option tile, chip-button.
512
+ Sits at CARD level on purpose: in light that is white against the
513
+ frame behind it, which reads "type here"; in dark it is raised above
514
+ the frame. --muted/--surface-inset is the RECESSED role (wells, zebra,
515
+ table headers) and reads as disabled when a control borrows it. The
516
+ lenses were borrowing it because --bg-input is internal and this
517
+ public role did not exist. */
518
+ --surface-control: var(--surface-card);
519
+
520
+ /* Legacy names — aliases onto the five above. Kept so the ~1,300
521
+ existing call sites keep working; prefer the canonical names. */
522
+ --page-bg: var(--surface-page);
523
+ --background: var(--surface-page);
524
+ --bg-surface: var(--surface-page);
525
+ --main-content-bg: var(--surface-page);
526
+ --bg-card: var(--surface-card);
527
+ --card: var(--surface-card);
528
+ --tray-bg: var(--surface-card);
529
+ --bg-elevated: var(--surface-card);
530
+ --surface-raised: var(--surface-card);
531
+ --popover: var(--surface-card);
532
+ --bg-input: var(--surface-card);
533
+ --card-frame-bg: var(--surface-frame);
534
+ --card-foot-bg: var(--surface-frame);
535
+ --muted: var(--surface-inset);
536
+ --input: var(--surface-inset);
537
+ --secondary: var(--surface-inset);
538
+ --canvas-placement-bg: var(--surface-inset);
539
+ --bg-card-hover: var(--surface-hover);
540
+ --bg-input-hover: var(--surface-frame);
541
+
542
+ }
543
+
544
+ /* ============================================================
545
+ SIDEBAR — INTERNAL (only <Sidebar/> reads these)
546
+ Light-theme overrides — Phase Sidebar-E (ADR-049 D7 + Phase E).
547
+ The rail should still read as a "distinct surface" from
548
+ --main-content-bg in light mode, just inverted: a slightly-
549
+ tinted light background with darker borders + text. Values
550
+ below were tuned against the dark-theme reference:
551
+ dark --sidebar-bg = 10 10 11 (near-black)
552
+ dark --sidebar-border = white/0.06 (faint white hairline)
553
+ Light analogue:
554
+ --sidebar-bg = 244 244 248 (cool off-white, one step
555
+ darker than --bg-card so
556
+ the rail still reads as
557
+ a step DOWN from --main-
558
+ content-bg's 254/254/254)
559
+ --sidebar-border = 0/0.10 (matches public --border)
560
+ `--sidebar-text*` and `--sidebar-surface-*` track the public
561
+ text + overlay scale in the same direction as the dark values,
562
+ just polarity-flipped.
563
+
564
+ This block is `[data-theme="light"]`-ONLY — `combo` mode keeps
565
+ the dark `--sidebar-*` values from :root so the sidebar stays
566
+ dark even though the rest of the shell is light.
567
+ ============================================================ */
568
+ [data-theme="light"] {
569
+ /* Flat white — this token is the app FRAME (main-layout.tsx paints the
570
+ whole shell with it), so a grey here tints every light page behind the
571
+ floating content card. The card's border + shadow carry the separation.
572
+ Matches meta-ads-audit-dashboard, which consumes the same tokens and
573
+ sets its rail to --page-bg. */
574
+ --sidebar-bg: 255 255 255;
575
+ --sidebar-surface: 0 0 0 / 0.04;
576
+ --sidebar-surface-hover: 0 0 0 / 0.08;
577
+ --sidebar-surface-active: 207 14 15 / 0.10;
578
+ --sidebar-text: 82 82 91;
579
+ --sidebar-text-bright: 17 17 19;
580
+ --sidebar-text-dim: 113 113 122;
581
+ --sidebar-border: 0 0 0 / 0.10;
582
+ --sidebar-section-label: 113 113 122;
583
+ --sidebar-rail: 0 0 0 / 0.08;
584
+
585
+ /* ============================================================
586
+ SEMANTIC + FOLDER PALETTE — light-only overrides.
587
+ Owner directive 2026-05-27: combo mode (dark sidebar + light
588
+ shell) was rendering group cards in the rail with the
589
+ light-mode darker/saturated folder palette painted on a still-
590
+ dark --sidebar-bg → low contrast, hard to see the category
591
+ color. Splitting these out so combo inherits the brighter
592
+ `:root` values (designed for dark substrate) keeps the rail
593
+ readable. Trade-off: in combo mode, asset folders rendering on
594
+ the light main-content area use the brighter `:root` tokens
595
+ too — they read slightly less optimal on white but stay
596
+ visible. Sidebar visibility is the primary affordance in combo.
597
+ ============================================================ */
598
+ --warning: 217 119 6;
599
+ --warning-bg: 217 119 6 / 0.10;
600
+ --folder-red: 207 14 15;
601
+ --folder-amber: 217 119 6;
602
+ --folder-emerald: 5 150 105;
603
+ --folder-teal: 13 148 136;
604
+ --folder-blue: 29 78 216;
605
+ --folder-violet: 124 58 237;
606
+ --folder-pink: 219 39 119;
607
+ --folder-slate: 71 85 105;
608
+ --folder-ink: 15 23 42;
609
+ }
610
+
611
+ /* ============================================================
612
+ META-PREVIEW PLATFORM-CHROME TOKENS (theme-INDEPENDENT)
613
+ ------------------------------------------------------------
614
+ The `@lovett/ui/meta-previews` renderers reproduce real
615
+ Facebook / Instagram / Messenger UI, which is always-light
616
+ regardless of the workspace theme — exactly like the
617
+ `--hierarchy-*` palette above. They consume these as inline
618
+ `var(--color-*)` values (full colours, NOT rgb-triplets), so
619
+ they live as plain custom properties here rather than in the
620
+ light/dark theme blocks.
621
+
622
+ Promotion gap fix (ADR-060 D12 follow-through): the renderers
623
+ referenced `var(--color-surface-50/100/200)` + a `meta-blue`
624
+ colour that were never defined when the previews were promoted
625
+ out of the prototype (docs/prototype/meta/preview-package). The
626
+ values below are the prototype's Tailwind-config colours (see
627
+ the package README "Tailwind config" block), restoring pixel
628
+ fidelity. `--color-meta-ink` is the always-dark preview-surface
629
+ base text (formerly the lens-local `--mc-fb-text`).
630
+
631
+ These are platform-chrome and exempt substrate (packages/ui),
632
+ so the hex literals are permitted here and nowhere in apps/.
633
+ ============================================================ */
634
+ :root {
635
+ --color-meta-blue: rgb(var(--platform-facebook));
636
+ --color-meta-ink: #050505;
637
+ --color-surface-50: #fafafa;
638
+ --color-surface-100: #f4f4f5;
639
+ --color-surface-200: #e4e4e7;
640
+ }
641
+
642
+ /* ============================================================
643
+ PLATFORM BRAND + MEDIA-OVERLAY TOKENS (theme-INDEPENDENT)
644
+ ------------------------------------------------------------
645
+ Third-party brand colours. Facebook is #1877F2 whether the
646
+ workspace is light or dark, so these deliberately sit OUTSIDE
647
+ the theme blocks — putting them in the theme would make a
648
+ platform badge change colour with the app, which is wrong.
649
+
650
+ This block exists because `swipes.css` and `collections.css`
651
+ each said, in their own header: "when a second consumer of
652
+ these exact tokens lands, promote to packages/ui/src/tokens.css".
653
+ Three landed (collections, ad-intel, the social previews), and
654
+ the same Facebook blue ended up defined in seven places under
655
+ seven names — plus an eighth, `--adstage-fb-like`, at a DIFFERENT
656
+ and wrong value (#2078f4). This is that promotion.
657
+
658
+ BARE TRIPLES, not complete colours: the same brand colour is
659
+ used at different opacities per surface (swipes/collections
660
+ badges at 0.78, ad-intel at 0.85), so the alpha belongs at the
661
+ call site — `rgb(var(--platform-facebook) / 0.78)`. Baking an
662
+ alpha into the token is what makes it un-shareable.
663
+
664
+ The `--media-*` overlays DO carry their alpha, because there the
665
+ opacity ramp IS the shared definition: swipes and collections
666
+ used byte-identical values.
667
+
668
+ Platform-chrome and exempt substrate (packages/ui), so the
669
+ literals are permitted here and nowhere in apps/.
670
+ ============================================================ */
671
+ :root {
672
+ /* Platform brand colours. */
673
+ --platform-facebook: 24 119 242;
674
+ --platform-instagram: 225 48 108;
675
+ --platform-linkedin: 10 102 194;
676
+ --platform-google: 66 133 244;
677
+ /* Google's logo green — the discovery report's tiles use it where the
678
+ * badge surfaces use the logo blue above. Both are real, both are used. */
679
+ --platform-google-green: 52 168 83;
680
+ --platform-youtube: 255 0 0;
681
+ /* TikTok's brand is black, which only works as a BADGE FILL (white text
682
+ * on it). As an icon colour on a dark card it measures 1.22:1, so the
683
+ * discovery tiles use theme-aware --folder-slate instead. Same reason
684
+ * there is no --platform-x: X is brand-black too, and its one consumer
685
+ * uses --folder-ink, which is byte-identical in light mode. */
686
+ --platform-tiktok: 0 0 0;
687
+ --platform-yelp: 211 35 35;
688
+ --platform-email: 245 158 11;
689
+ --platform-website: 255 255 255;
690
+ /* Near-white reads on every brand fill above. */
691
+ --platform-on-color: 255 255 255;
692
+
693
+ /* Overlays on creative media. Theme-independent because the thing
694
+ * underneath is an image, not a themed surface. */
695
+ --media-scrim-strong: 0 0 0 / 0.78;
696
+ --media-scrim-mid: 0 0 0 / 0.2;
697
+ --media-scrim-label: 0 0 0 / 0.55;
698
+ --media-on-scrim: 255 255 255 / 0.88;
699
+ --media-overlay-btn: 255 255 255 / 0.14;
700
+ }