@evinvest/uikit 0.18.0 → 0.20.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.
Files changed (277) hide show
  1. package/README.md +210 -13
  2. package/dist/chunk-3IPB4QQJ.js +306 -0
  3. package/dist/chunk-3IPB4QQJ.js.map +1 -0
  4. package/dist/components/accordion.js +136 -0
  5. package/dist/components/accordion.js.map +1 -0
  6. package/dist/components/alert-dialog.js +175 -0
  7. package/dist/components/alert-dialog.js.map +1 -0
  8. package/dist/components/alert.js +49 -0
  9. package/dist/components/alert.js.map +1 -0
  10. package/dist/components/avatar.js +54 -0
  11. package/dist/components/avatar.js.map +1 -0
  12. package/dist/components/badge.js +25 -0
  13. package/dist/components/badge.js.map +1 -0
  14. package/dist/components/band.js +75 -0
  15. package/dist/components/band.js.map +1 -0
  16. package/dist/components/breadcrumb.js +132 -0
  17. package/dist/components/breadcrumb.js.map +1 -0
  18. package/dist/components/button-group.js +61 -0
  19. package/dist/components/button-group.js.map +1 -0
  20. package/dist/components/button.js +55 -0
  21. package/dist/components/button.js.map +1 -0
  22. package/dist/components/calendar.js +226 -0
  23. package/dist/components/calendar.js.map +1 -0
  24. package/dist/components/card.js +98 -0
  25. package/dist/components/card.js.map +1 -0
  26. package/dist/components/carousel.js +262 -0
  27. package/dist/components/carousel.js.map +1 -0
  28. package/dist/components/chart.js +151 -0
  29. package/dist/components/chart.js.map +1 -0
  30. package/dist/components/checkbox.js +68 -0
  31. package/dist/components/checkbox.js.map +1 -0
  32. package/dist/components/collapsible.js +60 -0
  33. package/dist/components/collapsible.js.map +1 -0
  34. package/dist/components/command.js +274 -0
  35. package/dist/components/command.js.map +1 -0
  36. package/dist/components/container.js +18 -0
  37. package/dist/components/container.js.map +1 -0
  38. package/dist/components/context-menu.js +337 -0
  39. package/dist/components/context-menu.js.map +1 -0
  40. package/dist/components/date-time-picker.js +301 -0
  41. package/dist/components/date-time-picker.js.map +1 -0
  42. package/dist/components/dialog.js +207 -0
  43. package/dist/components/dialog.js.map +1 -0
  44. package/dist/components/drawer.js +351 -0
  45. package/dist/components/drawer.js.map +1 -0
  46. package/dist/components/dropdown-menu.js +356 -0
  47. package/dist/components/dropdown-menu.js.map +1 -0
  48. package/dist/components/empty.js +91 -0
  49. package/dist/components/empty.js.map +1 -0
  50. package/dist/components/field-context.js +30 -0
  51. package/dist/components/field-context.js.map +1 -0
  52. package/dist/components/field.js +216 -0
  53. package/dist/components/field.js.map +1 -0
  54. package/dist/components/footer.js +89 -0
  55. package/dist/components/footer.js.map +1 -0
  56. package/dist/components/form.js +101 -0
  57. package/dist/components/form.js.map +1 -0
  58. package/dist/components/hover-card.js +90 -0
  59. package/dist/components/hover-card.js.map +1 -0
  60. package/dist/components/info-tip.js +161 -0
  61. package/dist/components/info-tip.js.map +1 -0
  62. package/dist/components/input-group.js +124 -0
  63. package/dist/components/input-group.js.map +1 -0
  64. package/dist/components/input-otp.js +120 -0
  65. package/dist/components/input-otp.js.map +1 -0
  66. package/dist/components/input.js +26 -0
  67. package/dist/components/input.js.map +1 -0
  68. package/dist/components/item.js +167 -0
  69. package/dist/components/item.js.map +1 -0
  70. package/dist/components/kbd.js +29 -0
  71. package/dist/components/kbd.js.map +1 -0
  72. package/dist/components/label.js +11 -0
  73. package/dist/components/label.js.map +1 -0
  74. package/dist/components/logo.js +41 -0
  75. package/dist/components/logo.js.map +1 -0
  76. package/dist/components/menubar.js +330 -0
  77. package/dist/components/menubar.js.map +1 -0
  78. package/dist/components/native-select.js +62 -0
  79. package/dist/components/native-select.js.map +1 -0
  80. package/dist/components/navigation-menu.js +204 -0
  81. package/dist/components/navigation-menu.js.map +1 -0
  82. package/dist/components/pagination.js +138 -0
  83. package/dist/components/pagination.js.map +1 -0
  84. package/dist/components/popover.js +104 -0
  85. package/dist/components/popover.js.map +1 -0
  86. package/dist/components/progress.js +30 -0
  87. package/dist/components/progress.js.map +1 -0
  88. package/dist/components/radio-group.js +113 -0
  89. package/dist/components/radio-group.js.map +1 -0
  90. package/dist/components/resizable.js +193 -0
  91. package/dist/components/resizable.js.map +1 -0
  92. package/dist/components/scroll-area.js +40 -0
  93. package/dist/components/scroll-area.js.map +1 -0
  94. package/dist/components/section-descriptor.js +88 -0
  95. package/dist/components/section-descriptor.js.map +1 -0
  96. package/dist/components/select-chevron.js +25 -0
  97. package/dist/components/select-chevron.js.map +1 -0
  98. package/dist/components/select.js +288 -0
  99. package/dist/components/select.js.map +1 -0
  100. package/dist/components/separator.js +31 -0
  101. package/dist/components/separator.js.map +1 -0
  102. package/dist/components/sheet.js +200 -0
  103. package/dist/components/sheet.js.map +1 -0
  104. package/dist/components/sidebar.js +326 -0
  105. package/dist/components/sidebar.js.map +1 -0
  106. package/dist/components/skeleton.js +18 -0
  107. package/dist/components/skeleton.js.map +1 -0
  108. package/dist/components/slider.js +138 -0
  109. package/dist/components/slider.js.map +1 -0
  110. package/dist/components/sonner.js +319 -0
  111. package/dist/components/sonner.js.map +1 -0
  112. package/dist/components/spinner.js +23 -0
  113. package/dist/components/spinner.js.map +1 -0
  114. package/dist/components/status-screen.js +145 -0
  115. package/dist/components/status-screen.js.map +1 -0
  116. package/dist/components/switch.js +54 -0
  117. package/dist/components/switch.js.map +1 -0
  118. package/dist/components/table.js +124 -0
  119. package/dist/components/table.js.map +1 -0
  120. package/dist/components/tabs.js +133 -0
  121. package/dist/components/tabs.js.map +1 -0
  122. package/dist/components/terminal.js +206 -0
  123. package/dist/components/terminal.js.map +1 -0
  124. package/dist/components/textarea.js +22 -0
  125. package/dist/components/textarea.js.map +1 -0
  126. package/dist/components/toggle-group.js +105 -0
  127. package/dist/components/toggle-group.js.map +1 -0
  128. package/dist/components/toggle.js +53 -0
  129. package/dist/components/toggle.js.map +1 -0
  130. package/dist/components/tooltip.js +102 -0
  131. package/dist/components/tooltip.js.map +1 -0
  132. package/dist/generated/accent.js +27 -0
  133. package/dist/generated/accent.js.map +1 -0
  134. package/dist/generated/accordion.js +13 -0
  135. package/dist/generated/accordion.js.map +1 -0
  136. package/dist/generated/alert-dialog.js +15 -0
  137. package/dist/generated/alert-dialog.js.map +1 -0
  138. package/dist/generated/alert.js +16 -0
  139. package/dist/generated/alert.js.map +1 -0
  140. package/dist/generated/avatar.js +9 -0
  141. package/dist/generated/avatar.js.map +1 -0
  142. package/dist/generated/badge.js +13 -0
  143. package/dist/generated/badge.js.map +1 -0
  144. package/dist/generated/band.js +39 -0
  145. package/dist/generated/band.js.map +1 -0
  146. package/dist/generated/breadcrumb.js +15 -0
  147. package/dist/generated/breadcrumb.js.map +1 -0
  148. package/dist/generated/button-group.js +14 -0
  149. package/dist/generated/button-group.js.map +1 -0
  150. package/dist/generated/button.js +30 -0
  151. package/dist/generated/button.js.map +1 -0
  152. package/dist/generated/calendar.js +29 -0
  153. package/dist/generated/calendar.js.map +1 -0
  154. package/dist/generated/card.js +17 -0
  155. package/dist/generated/card.js.map +1 -0
  156. package/dist/generated/carousel.js +31 -0
  157. package/dist/generated/carousel.js.map +1 -0
  158. package/dist/generated/chart.js +9 -0
  159. package/dist/generated/chart.js.map +1 -0
  160. package/dist/generated/checkbox.js +7 -0
  161. package/dist/generated/checkbox.js.map +1 -0
  162. package/dist/generated/command.js +27 -0
  163. package/dist/generated/command.js.map +1 -0
  164. package/dist/generated/container.js +5 -0
  165. package/dist/generated/container.js.map +1 -0
  166. package/dist/generated/context-menu.js +19 -0
  167. package/dist/generated/context-menu.js.map +1 -0
  168. package/dist/generated/date-time-picker.js +15 -0
  169. package/dist/generated/date-time-picker.js.map +1 -0
  170. package/dist/generated/dialog.js +17 -0
  171. package/dist/generated/dialog.js.map +1 -0
  172. package/dist/generated/drawer.js +26 -0
  173. package/dist/generated/drawer.js.map +1 -0
  174. package/dist/generated/dropdown-menu.js +21 -0
  175. package/dist/generated/dropdown-menu.js.map +1 -0
  176. package/dist/generated/empty.js +20 -0
  177. package/dist/generated/empty.js.map +1 -0
  178. package/dist/generated/field.js +33 -0
  179. package/dist/generated/field.js.map +1 -0
  180. package/dist/generated/focus.js +5 -0
  181. package/dist/generated/focus.js.map +1 -0
  182. package/dist/generated/form.js +11 -0
  183. package/dist/generated/form.js.map +1 -0
  184. package/dist/generated/hover-card.js +5 -0
  185. package/dist/generated/hover-card.js.map +1 -0
  186. package/dist/generated/input-group.js +29 -0
  187. package/dist/generated/input-group.js.map +1 -0
  188. package/dist/generated/input-otp.js +15 -0
  189. package/dist/generated/input-otp.js.map +1 -0
  190. package/dist/generated/input.js +11 -0
  191. package/dist/generated/input.js.map +1 -0
  192. package/dist/generated/item.js +40 -0
  193. package/dist/generated/item.js.map +1 -0
  194. package/dist/generated/kbd.js +7 -0
  195. package/dist/generated/kbd.js.map +1 -0
  196. package/dist/generated/label.js +5 -0
  197. package/dist/generated/label.js.map +1 -0
  198. package/dist/generated/menubar.js +27 -0
  199. package/dist/generated/menubar.js.map +1 -0
  200. package/dist/generated/navigation-menu.js +19 -0
  201. package/dist/generated/navigation-menu.js.map +1 -0
  202. package/dist/generated/popover.js +5 -0
  203. package/dist/generated/popover.js.map +1 -0
  204. package/dist/generated/progress.js +7 -0
  205. package/dist/generated/progress.js.map +1 -0
  206. package/dist/generated/radio-group.js +7 -0
  207. package/dist/generated/radio-group.js.map +1 -0
  208. package/dist/generated/resizable.js +11 -0
  209. package/dist/generated/resizable.js.map +1 -0
  210. package/dist/generated/scroll-area.js +14 -0
  211. package/dist/generated/scroll-area.js.map +1 -0
  212. package/dist/generated/select.js +15 -0
  213. package/dist/generated/select.js.map +1 -0
  214. package/dist/generated/separator.js +10 -0
  215. package/dist/generated/separator.js.map +1 -0
  216. package/dist/generated/sheet.js +25 -0
  217. package/dist/generated/sheet.js.map +1 -0
  218. package/dist/generated/sidebar.js +41 -0
  219. package/dist/generated/sidebar.js.map +1 -0
  220. package/dist/generated/skeleton.js +5 -0
  221. package/dist/generated/skeleton.js.map +1 -0
  222. package/dist/generated/slider.js +11 -0
  223. package/dist/generated/slider.js.map +1 -0
  224. package/dist/generated/sonner.js +30 -0
  225. package/dist/generated/sonner.js.map +1 -0
  226. package/dist/generated/switch.js +7 -0
  227. package/dist/generated/switch.js.map +1 -0
  228. package/dist/generated/table.js +21 -0
  229. package/dist/generated/table.js.map +1 -0
  230. package/dist/generated/tabs.js +11 -0
  231. package/dist/generated/tabs.js.map +1 -0
  232. package/dist/generated/terminal.js +70 -0
  233. package/dist/generated/terminal.js.map +1 -0
  234. package/dist/generated/textarea.js +11 -0
  235. package/dist/generated/textarea.js.map +1 -0
  236. package/dist/generated/toggle.js +18 -0
  237. package/dist/generated/toggle.js.map +1 -0
  238. package/dist/generated/tooltip.js +5 -0
  239. package/dist/generated/tooltip.js.map +1 -0
  240. package/dist/index.d.ts +141 -34
  241. package/dist/index.js +73 -8623
  242. package/dist/lib/cn.js +12 -0
  243. package/dist/lib/cn.js.map +1 -0
  244. package/dist/lib/focus.js +5 -0
  245. package/dist/lib/focus.js.map +1 -0
  246. package/dist/palette-cli.js +41 -0
  247. package/dist/palette-cli.js.map +1 -0
  248. package/dist/palette.d.ts +80 -0
  249. package/dist/palette.js +25 -0
  250. package/dist/palette.js.map +1 -0
  251. package/dist/primitives/dismissable-layer.js +35 -0
  252. package/dist/primitives/dismissable-layer.js.map +1 -0
  253. package/dist/primitives/focus-scope.js +58 -0
  254. package/dist/primitives/focus-scope.js.map +1 -0
  255. package/dist/primitives/merge-refs.js +13 -0
  256. package/dist/primitives/merge-refs.js.map +1 -0
  257. package/dist/primitives/portal.js +19 -0
  258. package/dist/primitives/portal.js.map +1 -0
  259. package/dist/primitives/presence.js +45 -0
  260. package/dist/primitives/presence.js.map +1 -0
  261. package/dist/primitives/slot.js +39 -0
  262. package/dist/primitives/slot.js.map +1 -0
  263. package/dist/primitives/use-controllable-state.js +24 -0
  264. package/dist/primitives/use-controllable-state.js.map +1 -0
  265. package/dist/primitives/use-floating.js +128 -0
  266. package/dist/primitives/use-floating.js.map +1 -0
  267. package/dist/primitives/use-hover-intent.js +50 -0
  268. package/dist/primitives/use-hover-intent.js.map +1 -0
  269. package/dist/primitives/use-roving-focus.js +40 -0
  270. package/dist/primitives/use-roving-focus.js.map +1 -0
  271. package/dist/primitives/walk-elements.js +13 -0
  272. package/dist/primitives/walk-elements.js.map +1 -0
  273. package/package.json +15 -5
  274. package/styles/ev.css +73 -0
  275. package/styles/theme.css +470 -0
  276. package/styles/tokens.css +129 -90
  277. package/dist/index.js.map +0 -1
package/styles/tokens.css CHANGED
@@ -1,14 +1,18 @@
1
1
  /* AUTO-GENERATED by `nix run .#gen` from the repo-root tokens.css — do not edit. */
2
2
  /*
3
- * Shared design tokens — the theme contract for the `ev_lib::uikit` (Rust) and
4
- * `@evinvest/uikit` (TypeScript) components. Import this file from any Tailwind v4
5
- * entrypoint. Single source for both ports — `@evinvest/uikit` copies it into
6
- * `styles/tokens.css` from its `prepare` script.
3
+ * Shared design tokens for the `ev_lib::uikit` (Rust) and `@evinvest/uikit`
4
+ * (TypeScript) components. Import this file from any Tailwind v4 entrypoint.
5
+ * Single source for both ports; `nix run .#gen` flattens it into each.
7
6
  *
8
- * The values below are EV's. A non-EV consumer writes its own sheet against the
9
- * same names; the class tables reference nothing else.
7
+ * Two halves, in this order:
10
8
  *
11
- * SCOPE :root · .dark · .light — a consumer defines one or both
9
+ * CONTRACT the names the class tables reference, the Tailwind mapping, the
10
+ * tokens derived from a palette, and the geometry. Brand-neutral.
11
+ * PALETTE EV's values for the contract's colour names. A non-EV consumer
12
+ * writes (or generates — `evinvest-palette`, see the uikit README)
13
+ * its own palette against the same names under `[data-brand="…"]`.
14
+ *
15
+ * SCOPE :root · [data-brand] — a theme root; .light · .dark — a polarity
12
16
  * SURFACES background · card · popover · muted · hover
13
17
  * INK ink · ink-mid · ink-soft
14
18
  * LINES border · input · ring
@@ -21,23 +25,18 @@
21
25
  * CHARTS chart-1 … chart-5
22
26
  * MARK brand-mark · brand-aspect — supplied, never defined here
23
27
  *
24
- * Custom properties inherit, so a scope class on a `<section>` re-themes its
25
- * subtree: `bg-card text-ink` is correct on both polarities with no prop. EV is
26
- * single-polarity, so `:root` and `.dark` carry the same block and `.light` is
27
- * undefined — a `Section(Light)` is then a no-op here, which is what a
28
- * single-polarity palette should do.
28
+ * Custom properties inherit, so a scope on a `<section>` re-themes its subtree:
29
+ * `bg-card text-ink` is correct under any brand and either polarity with no
30
+ * prop. A brand's palette declares `.light` and `.dark` beneath its own
31
+ * `[data-brand]`, so a `<Section polarity="dark">` inside it takes that
32
+ * brand's dark values — never EV's. EV is single-polarity: its palette sits on
33
+ * the theme root alone and both polarity classes inherit it unchanged.
34
+ * Brand scopes do not nest: CSS has no "nearest ancestor", so a polarity class
35
+ * inside a brand inside another brand may match either.
29
36
  *
30
37
  * Accents are ordered by significance, not by meaning; see docs/spec/accents.md.
31
38
  * Their values are read outside CSS (gitops mirrors them into Discord embed
32
39
  * bars), so they stay plain hex — no color-mix.
33
- *
34
- * Surface ladder — dark UI reads as unfinished when every plane is the same
35
- * value, so the steps below are deliberate and measured; keep them apart if you
36
- * retune. background #070d18 → secondary #0d1526 (1.07:1) → card #16233c (1.24:1
37
- * vs background) → popover #1d2c49 (1.13:1 vs card) → muted #26375c. Elevation
38
- * rides the same ladder via --shadow-card / -elevated / -overlay. Card is also
39
- * pinned from above by a second floor: --primary has to clear 3:1 against it
40
- * (3.39:1 today), so lifting card one step breaks the tokens test.
41
40
  */
42
41
 
43
42
  /*
@@ -347,6 +346,8 @@
347
346
  }
348
347
  }
349
348
 
349
+ /* ═══ CONTRACT ═══════════════════════════════════════════════════ */
350
+
350
351
  @theme inline {
351
352
  --radius-sm: calc(var(--radius) - 4px);
352
353
  --radius-md: calc(var(--radius) - 2px);
@@ -363,15 +364,24 @@
363
364
  0 2px 4px -2px rgb(0 0 0 / 0.30);
364
365
  --shadow-overlay: 0 16px 48px -12px rgb(0 0 0 / 0.60);
365
366
 
366
- /* The leading var() carries its own fallback: next/font (landing) sets
367
- * `--font-inter` / `--font-playfair`; a Rust consumer `@font-face`s the same
368
- * families itself. Without the in-var fallback an undefined leading var would
369
- * invalidate the whole `font-family` and drop the browser to its default — so
370
- * it must never resolve to guaranteed-invalid. */
371
- --font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, sans-serif;
372
- --font-serif: var(--font-playfair, "Playfair Display"), ui-serif, Georgia, serif;
373
- --font-mono: var(--font-inter, "Inter"), ui-monospace, monospace;
374
- --font-display: var(--font-playfair, "Playfair Display"), ui-serif, Georgia, serif;
367
+ /* The families are parameters: a brand scope sets `--brand-font-sans`,
368
+ * `--brand-font-display` and `--brand-font-mono` (evinvest-palette writes
369
+ * them from a brand file's [fonts]); `font-serif` is the display role under
370
+ * its older name. Unset, each falls through to EV: next/font (landing) sets
371
+ * `--font-inter` / `--font-playfair`, a Rust consumer `@font-face`s the same
372
+ * families itself. Every leading var() carries its own fallback — an
373
+ * undefined one would invalidate the whole `font-family` and drop the
374
+ * browser to its default, so it must never resolve to guaranteed-invalid.
375
+ * `inline` puts the var() chain in the utility itself, so it resolves on
376
+ * the element and a brand scope re-fonts its subtree. */
377
+ --font-sans: var(--brand-font-sans, var(--font-inter, "Inter")), ui-sans-serif, system-ui, sans-serif;
378
+ --font-serif: var(--brand-font-display, var(--font-playfair, "Playfair Display")), ui-serif, Georgia, serif;
379
+ --font-mono: var(--brand-font-mono, var(--font-inter, "Inter")), ui-monospace, monospace;
380
+ --font-display: var(--brand-font-display, var(--font-playfair, "Playfair Display")), ui-serif, Georgia, serif;
381
+
382
+ /* The colour contract: every `--color-X: var(--X)` below is a name a palette
383
+ * owes, except the derived ones in the next rule. `evinvest-palette` reads
384
+ * this block as the list, so there is no second copy of it to drift. */
375
385
 
376
386
  /* surfaces */
377
387
  --color-background: var(--background);
@@ -418,73 +428,27 @@
418
428
  --color-chart-5: var(--chart-5);
419
429
  }
420
430
 
421
- :root,
422
- .dark {
423
- /* ── surfaces ────────────────────────────────────────────────── */
424
- --background: #070d18;
425
- --card: #16233c;
426
- --popover: #1d2c49;
427
- --muted: #26375c;
431
+ /* Derived from the palette, and redeclared on every scope a palette can be
432
+ * declared on. A var() inside a custom property is resolved where it is
433
+ * declared and the result is what inherits — declared on `:root` alone, a brand
434
+ * scope would change the text and keep EV's borders, hover and focus ring.
435
+ * Zero specificity, so a palette that sets one of these outright wins. */
436
+ :where(:root, .dark, .light, [data-brand]) {
428
437
  /* the menu-item tint: a surface state, so it is named for the state */
429
438
  --hover: color-mix(in srgb, var(--ink) 8%, transparent);
430
-
431
- /* ── ink ─────────────────────────────────────────────────────── */
432
- --ink: #e6e1d3;
433
439
  --ink-mid: color-mix(in srgb, var(--ink) 82%, transparent);
434
440
  /* the most-used secondary text token, so it has to clear AA on its own */
435
441
  --ink-soft: color-mix(in srgb, var(--ink) 65%, transparent);
436
-
437
- /* ── lines ───────────────────────────────────────────────────── */
438
442
  --border: color-mix(in srgb, var(--ink) 14%, transparent);
439
443
  --input: color-mix(in srgb, var(--ink) 20%, transparent);
440
444
  --ring: var(--primary-ink);
445
+ }
441
446
 
442
- /* ── roles ───────────────────────────────────────────────────── */
443
- --brand: #001e4e;
444
- /* teal, not the brand navy: navy filled a Button/Switch at ~1.1:1 against
445
- * these surfaces, i.e. an invisible control, and consumers hardcoded teal to
446
- * get around it. The brand navy still lives in --brand.
447
- *
448
- * A fill and an ink are different jobs, so the primary role carries two
449
- * values (docs/spec/accents.md). --primary-ink is the teal that reads on the
450
- * surfaces: 5.85:1 on background, 4.72:1 on card. --primary is the same hue
451
- * and chroma one step down in lightness — OKLCH L 0.550 C 0.094 H 183°
452
- * against the ink's L 0.630 C 0.101 H 183° — so the CTA carries white:
453
- * --on-primary reads on it at 4.63:1, and the fill clears the 3:1 non-text
454
- * floor against background (4.20:1), secondary (3.94:1), card (3.39:1) and
455
- * popover (3.01:1), so a checked Switch on a Card still reads as one. Muted
456
- * is the one surface under the floor (2.54:1); the controls that fill there
457
- * carry an on-primary mark, see the spec. Darkening the single value instead
458
- * would have dropped every teal label below AA. */
459
- --primary: #128377;
460
- --on-primary: #ffffff;
461
- --primary-ink: #2a9d8f;
462
- --secondary: #0d1526;
463
- --on-secondary: var(--ink);
464
- /* valence, not significance: a success reads as good while staying quiet */
465
- --positive: #2e9e5b;
466
- --on-positive: #070d18;
467
- --accent-trace: #6b7fa0;
468
- --on-accent-trace: #070d18;
469
- --accent-debug: #2a9d8f;
470
- --on-accent-debug: #070d18;
471
- --accent-info: #4a90d9;
472
- --on-accent-info: #070d18;
473
- --accent-warn: #f2c94c;
474
- --on-accent-warn: #070d18;
475
- /* lifted off the brand red #da251d, which failed AA on a card. This one value
476
- * carries both the fill and the text; the vivid brand red is a gitops
477
- * constant, not a UI token. */
478
- --accent-error: #ef5b52;
479
- --on-accent-error: #070d18;
480
-
481
- --chart-1: #2a9d8f;
482
- --chart-2: #2e9e5b;
483
- --chart-3: #f2c94c;
484
- --chart-4: #e58aae;
485
- --chart-5: #4a90d9;
486
-
487
- /* ── scalars ─────────────────────────────────────────────────── */
447
+ /* Geometry. A `[data-brand]` scope is a theme root of its own, so it restarts
448
+ * from these defaults (and from the scale-ups below) rather than inheriting
449
+ * whatever an ancestor computed — `--control-radius` in particular is derived
450
+ * from `--radius` and has to be recomputed wherever `--radius` may change. */
451
+ :where(:root, [data-brand]) {
488
452
  --radius: 0.375rem; /* 6px on mobile; bumped to 8px ≥ sm (see @media below) */
489
453
  /* control geometry, so a consumer reshapes every button/input at once */
490
454
  --control-radius: calc(var(--radius) - 2px);
@@ -514,7 +478,9 @@
514
478
  * --brand-mark: url("/assets/logo.svg");
515
479
  * --brand-aspect: 387 / 335;
516
480
  */
481
+ }
517
482
 
483
+ :root {
518
484
  /* shell offset — viewport space reserved by whatever shell hosts the app.
519
485
  Standalone apps get 0; a hosting shell overrides on :root. Size
520
486
  viewport-bound surfaces with calc(100dvh - var(--ev-shell-offset, 0px)). */
@@ -524,21 +490,94 @@
524
490
  /* Mobile-first scale-ups: roomier gutters + slightly larger radii on bigger
525
491
  viewports. Lives here (not per-component) so every consumer scales alike. */
526
492
  @media (min-width: 640px) {
527
- :root {
493
+ :where(:root, [data-brand]) {
528
494
  --radius: 0.5rem;
529
495
  --page-px: 1.5rem;
530
496
  }
531
497
  }
532
498
 
533
499
  @media (min-width: 768px) {
534
- :root {
500
+ :where(:root, [data-brand]) {
535
501
  --band-py: 4rem;
536
502
  --band-py-tight: 3.5rem;
537
503
  }
538
504
  }
539
505
 
540
506
  @media (min-width: 1024px) {
541
- :root {
507
+ :where(:root, [data-brand]) {
542
508
  --page-px: 3rem;
543
509
  }
544
510
  }
511
+
512
+ /* ═══ PALETTE: EV ════════════════════════════════════════════════
513
+ *
514
+ * Zero specificity: EV is the default a brand scope overrides, whatever order
515
+ * the two sheets are imported in.
516
+ *
517
+ * Surface ladder — dark UI reads as unfinished when every plane is the same
518
+ * value, so the steps below are deliberate and measured; keep them apart if you
519
+ * retune. background #070d18 → secondary #0d1526 (1.07:1) → card #16233c (1.24:1
520
+ * vs background) → popover #1d2c49 (1.13:1 vs card) → muted #26375c. Elevation
521
+ * rides the same ladder via --shadow-card / -elevated / -overlay. Card is also
522
+ * pinned from above by a second floor: --primary has to clear 3:1 against it
523
+ * (3.39:1 today), so lifting card one step breaks the tokens test.
524
+ */
525
+ :where(:root, [data-brand="ev"]) {
526
+ /* the polarity, for what the platform draws itself (a native <select>'s
527
+ * popup); a custom property so nothing but the kit's opt-ins reads it */
528
+ --scheme: dark;
529
+
530
+ /* ── surfaces ────────────────────────────────────────────────── */
531
+ --background: #070d18;
532
+ --card: #16233c;
533
+ --popover: #1d2c49;
534
+ --muted: #26375c;
535
+
536
+ /* ── ink ─────────────────────────────────────────────────────── */
537
+ --ink: #e6e1d3;
538
+
539
+ /* ── roles ───────────────────────────────────────────────────── */
540
+ --brand: #001e4e;
541
+ /* teal, not the brand navy: navy filled a Button/Switch at ~1.1:1 against
542
+ * these surfaces, i.e. an invisible control, and consumers hardcoded teal to
543
+ * get around it. The brand navy still lives in --brand.
544
+ *
545
+ * A fill and an ink are different jobs, so the primary role carries two
546
+ * values (docs/spec/accents.md). --primary-ink is the teal that reads on the
547
+ * surfaces: 5.85:1 on background, 4.72:1 on card. --primary is the same hue
548
+ * and chroma one step down in lightness — OKLCH L 0.550 C 0.094 H 183°
549
+ * against the ink's L 0.630 C 0.101 H 183° — so the CTA carries white:
550
+ * --on-primary reads on it at 4.63:1, and the fill clears the 3:1 non-text
551
+ * floor against background (4.20:1), secondary (3.94:1), card (3.39:1) and
552
+ * popover (3.01:1), so a checked Switch on a Card still reads as one. Muted
553
+ * is the one surface under the floor (2.54:1); the controls that fill there
554
+ * carry an on-primary mark, see the spec. Darkening the single value instead
555
+ * would have dropped every teal label below AA. */
556
+ --primary: #128377;
557
+ --on-primary: #ffffff;
558
+ --primary-ink: #2a9d8f;
559
+ --secondary: #0d1526;
560
+ --on-secondary: var(--ink);
561
+ /* valence, not significance: a success reads as good while staying quiet */
562
+ --positive: #2e9e5b;
563
+ --on-positive: #070d18;
564
+ --accent-trace: #6b7fa0;
565
+ --on-accent-trace: #070d18;
566
+ --accent-debug: #2a9d8f;
567
+ --on-accent-debug: #070d18;
568
+ --accent-info: #4a90d9;
569
+ --on-accent-info: #070d18;
570
+ --accent-warn: #f2c94c;
571
+ --on-accent-warn: #070d18;
572
+ /* lifted off the brand red #da251d, which failed AA on a card. This one value
573
+ * carries both the fill and the text; the vivid brand red is a gitops
574
+ * constant, not a UI token. */
575
+ --accent-error: #ef5b52;
576
+ --on-accent-error: #070d18;
577
+
578
+ --chart-1: #2a9d8f;
579
+ --chart-2: #2e9e5b;
580
+ --chart-3: #f2c94c;
581
+ --chart-4: #e58aae;
582
+ --chart-5: #4a90d9;
583
+ }