@nebutra/tokens 0.1.2 → 2.0.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 (72) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/brands/README.md +66 -0
  3. package/brands/cosmos/DESIGN.md +356 -0
  4. package/brands/cosmos/brand.json +209 -0
  5. package/brands/cosmos/refero-tokens.json +312 -0
  6. package/brands/gsap/brand.json +18 -91
  7. package/brands/linear/DESIGN.md +478 -0
  8. package/brands/linear/brand.json +20 -76
  9. package/brands/notion/brand.json +20 -90
  10. package/brands/raycast/brand.json +19 -81
  11. package/brands/stripe/brand.json +17 -88
  12. package/brands/vanta/brand.json +17 -101
  13. package/brands/vercel/brand.json +17 -68
  14. package/dist/brand-package/index.d.ts +123 -0
  15. package/dist/brand-package/index.js +2316 -0
  16. package/dist/brand-package/index.js.map +1 -0
  17. package/dist/brand-package/use-brand.d.ts +2 -0
  18. package/dist/brand-package/use-brand.js +963 -0
  19. package/dist/brand-package/use-brand.js.map +1 -0
  20. package/dist/index.d.ts +81 -0
  21. package/dist/index.js +2581 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/use-brand-ClmvK_lC.d.ts +387 -0
  24. package/package.json +41 -10
  25. package/recipe.css +330 -1
  26. package/skins/README.md +2 -4
  27. package/skins/cosmos.css +239 -0
  28. package/skins/gsap.css +62 -138
  29. package/skins/linear.css +85 -132
  30. package/skins/notion.css +62 -141
  31. package/skins/raycast.css +68 -137
  32. package/skins/stripe.css +60 -143
  33. package/skins/vanta.css +60 -151
  34. package/skins/vercel.css +83 -128
  35. package/styles.css +477 -115
  36. package/.turbo/turbo-build.log +0 -8
  37. package/.turbo/turbo-typecheck.log +0 -4
  38. package/AGENTS.md +0 -45
  39. package/DESIGN.md +0 -357
  40. package/scripts/compile-brand-run.ts +0 -79
  41. package/scripts/compile-brand.mjs +0 -25
  42. package/scripts/emit-skins-run.ts +0 -62
  43. package/scripts/emit-skins.mjs +0 -31
  44. package/scripts/sync-styles.mjs +0 -29
  45. package/src/brand-package/__tests__/compile-refero.test.ts +0 -427
  46. package/src/brand-package/__tests__/emit-css.test.ts +0 -130
  47. package/src/brand-package/apply-brand.ts +0 -92
  48. package/src/brand-package/compile-helpers.ts +0 -170
  49. package/src/brand-package/compile-refero.ts +0 -69
  50. package/src/brand-package/emit-css.ts +0 -468
  51. package/src/brand-package/hex-to-hsl.ts +0 -100
  52. package/src/brand-package/index.ts +0 -71
  53. package/src/brand-package/infer-recipe.ts +0 -198
  54. package/src/brand-package/normalize.ts +0 -295
  55. package/src/brand-package/presets/context.ts +0 -17
  56. package/src/brand-package/presets/generic.ts +0 -214
  57. package/src/brand-package/presets/gsap.ts +0 -146
  58. package/src/brand-package/presets/index.ts +0 -31
  59. package/src/brand-package/presets/linear.ts +0 -183
  60. package/src/brand-package/presets/notion.ts +0 -208
  61. package/src/brand-package/presets/raycast.ts +0 -138
  62. package/src/brand-package/presets/recipe.ts +0 -61
  63. package/src/brand-package/presets/stripe.ts +0 -207
  64. package/src/brand-package/presets/vanta.ts +0 -220
  65. package/src/brand-package/presets/vercel.ts +0 -187
  66. package/src/brand-package/types.ts +0 -285
  67. package/src/brand-package/use-brand.ts +0 -207
  68. package/src/brand-package/validate.ts +0 -108
  69. package/src/index.ts +0 -67
  70. package/src/theme-provider.tsx +0 -246
  71. package/tsconfig.json +0 -11
  72. package/turbo.json +0 -15
package/recipe.css CHANGED
@@ -11,6 +11,14 @@
11
11
  * Tailwind utilities: bg-brand-mark / text-brand-mark (see @theme below).
12
12
  */
13
13
 
14
+ @custom-variant hover {
15
+ @media (hover: hover) and (pointer: fine) {
16
+ &:hover {
17
+ @slot;
18
+ }
19
+ }
20
+ }
21
+
14
22
  :root {
15
23
  /* ── Color roles (overridden by brand skins) ──────────────────── */
16
24
  --role-action: var(--primary);
@@ -23,7 +31,8 @@
23
31
  --btn-default-fg: hsl(var(--primary-foreground));
24
32
  --btn-default-border: transparent;
25
33
  --btn-default-border-width: 0px;
26
- --btn-default-stroke-gradient: transparent;
34
+ /* An image, never a colour — see the @property registration below. */
35
+ --btn-default-stroke-gradient: linear-gradient(transparent, transparent);
27
36
  --btn-default-hover-bg: color-mix(in srgb, hsl(var(--primary)) 90%, transparent);
28
37
  --btn-default-radius: var(--radius-button, var(--radius-md, 0.375rem));
29
38
  --btn-default-shadow: 0 0 #0000;
@@ -42,6 +51,11 @@
42
51
  --control-height-sm: 2rem; /* 32 */
43
52
  --control-height-md: 2.5rem; /* 40 */
44
53
  --control-height-lg: 3rem; /* 48 */
54
+ /* Icon-only triggers — orthogonal to the text-button ladder above.
55
+ apps use 28/32/36 rounded-md squares; circle uses the same boxes. */
56
+ --control-height-icon-sm: 1.75rem; /* 28 */
57
+ --control-height-icon-md: 2rem; /* 32 */
58
+ --control-height-icon-lg: 2.25rem; /* 36 */
45
59
  --control-font-size-tiny: 0.6875rem; /* 11 */
46
60
  --control-font-size-sm: 0.75rem; /* 12 */
47
61
  --control-font-size-md: 0.875rem; /* 14 */
@@ -62,6 +76,321 @@
62
76
  --badge-default-hover-bg: color-mix(in srgb, hsl(var(--brand-mark)) 80%, transparent);
63
77
  }
64
78
 
79
+ /**
80
+ * The stroke slot is consumed as a background-image layer, so its value has to
81
+ * be an <image>. A colour there is not merely ignored: background-image becomes
82
+ * invalid as a whole and the browser drops every layer, including the solid
83
+ * brand fill in front of it. The button then renders with no background at all,
84
+ * while `color` — a separate declaration — keeps painting white text. That is
85
+ * what shipped: every default Button outside the gsap skin was white-on-white,
86
+ * because the slot held `transparent`, which is a colour.
87
+ *
88
+ * Registering the property makes that unrepresentable rather than merely
89
+ * discouraged. A colour written here now fails to parse against `<image>`, so
90
+ * the declaration is rejected and the slot keeps this initial value — the fill
91
+ * survives. Skins are generated, and a generator is exactly the place where one
92
+ * wrong branch reaches every brand at once.
93
+ *
94
+ * The var() fallbacks at the use sites stay as the unregistered path, so the
95
+ * recipe is still correct where @property is unsupported.
96
+ */
97
+ @property --btn-default-stroke-gradient {
98
+ syntax: "<image>";
99
+ inherits: true;
100
+ initial-value: linear-gradient(transparent, transparent);
101
+ }
102
+
103
+ /**
104
+ * ── Type contracts for every other recipe slot ───────────────────────────────
105
+ *
106
+ * Same reasoning, generalised. Each slot below is read from a typed CSS position
107
+ * (a colour, a length, a font weight), so a wrong-typed value written by a skin
108
+ * does not fail locally — it makes the whole consuming declaration invalid at
109
+ * computed-value time and the browser silently drops it. Registering the slot
110
+ * moves the failure to the slot itself: the bad value is rejected, the property
111
+ * keeps its initial-value, and the declaration that reads it still renders.
112
+ *
113
+ * Rules observed here:
114
+ * • `inherits: true` on every slot. Custom properties inherit by default, and
115
+ * every skin sets these on `:root` / `[data-theme]` for descendants to read.
116
+ * `inherits: false` would hand every non-root element the initial-value.
117
+ * • `initial-value` is computationally independent: absolute colours and `px`
118
+ * only. No `var()`, no `em`/`rem`, no `currentcolor`. The declared values in
119
+ * `:root` above are free to keep using `var()` and `rem` — the restriction
120
+ * applies to the initial-value alone. The px initials are the rem defaults
121
+ * resolved at a 16px root (0.375rem → 6px) so the fallback matches the
122
+ * factory ladder rather than inventing a new one.
123
+ * • Registering a `<length>` slot absolutises `rem` at computed-value time.
124
+ * Every skin writes `rem` or `px`, so this is a no-op today. A skin that
125
+ * wants element-relative sizing must not use `em` in these slots: it would
126
+ * resolve against the element's own font-size before the slot is read.
127
+ */
128
+
129
+ /* ── Colour slots ─────────────────────────────────────────────────────────────
130
+ * Read inside `linear-gradient()` (button) and `background-color` (badge).
131
+ * Initial-values are the light-theme action blue (222.8 85% 55.7%) and its
132
+ * white foreground, i.e. the factory default rendered without a skin.
133
+ */
134
+ @property --btn-default-bg {
135
+ syntax: "<color>";
136
+ inherits: true;
137
+ initial-value: hsl(222.8 85% 55.7%);
138
+ }
139
+
140
+ @property --btn-default-fg {
141
+ syntax: "<color>";
142
+ inherits: true;
143
+ initial-value: #ffffff;
144
+ }
145
+
146
+ @property --btn-default-border {
147
+ syntax: "<color>";
148
+ inherits: true;
149
+ initial-value: transparent;
150
+ }
151
+
152
+ @property --btn-default-hover-bg {
153
+ syntax: "<color>";
154
+ inherits: true;
155
+ initial-value: hsl(222.8 85% 55.7%);
156
+ }
157
+
158
+ @property --badge-default-bg {
159
+ syntax: "<color>";
160
+ inherits: true;
161
+ initial-value: hsl(222.8 85% 55.7%);
162
+ }
163
+
164
+ @property --badge-default-fg {
165
+ syntax: "<color>";
166
+ inherits: true;
167
+ initial-value: #ffffff;
168
+ }
169
+
170
+ @property --badge-default-border {
171
+ syntax: "<color>";
172
+ inherits: true;
173
+ initial-value: transparent;
174
+ }
175
+
176
+ @property --badge-default-hover-bg {
177
+ syntax: "<color>";
178
+ inherits: true;
179
+ initial-value: hsl(222.8 85% 55.7%);
180
+ }
181
+
182
+ /* ── Length slots ─────────────────────────────────────────────────────────────
183
+ * `border-width` and the control ladder are plain lengths. Radii and padding go
184
+ * into `border-radius` / `padding`, which both accept percentages, so those get
185
+ * `<length-percentage>` — a skin expressing a pill as `50%` stays legal.
186
+ */
187
+ @property --btn-default-border-width {
188
+ syntax: "<length>";
189
+ inherits: true;
190
+ initial-value: 0px;
191
+ }
192
+
193
+ @property --btn-default-radius {
194
+ syntax: "<length-percentage>";
195
+ inherits: true;
196
+ initial-value: 6px;
197
+ }
198
+
199
+ @property --btn-default-padding-y {
200
+ syntax: "<length-percentage>";
201
+ inherits: true;
202
+ initial-value: 8px;
203
+ }
204
+
205
+ @property --btn-default-padding-x {
206
+ syntax: "<length-percentage>";
207
+ inherits: true;
208
+ initial-value: 16px;
209
+ }
210
+
211
+ @property --badge-default-radius {
212
+ syntax: "<length-percentage>";
213
+ inherits: true;
214
+ initial-value: 9999px;
215
+ }
216
+
217
+ @property --radius-button {
218
+ syntax: "<length-percentage>";
219
+ inherits: true;
220
+ initial-value: 6px;
221
+ }
222
+
223
+ @property --radius-card {
224
+ syntax: "<length-percentage>";
225
+ inherits: true;
226
+ initial-value: 12px;
227
+ }
228
+
229
+ @property --radius-badge {
230
+ syntax: "<length-percentage>";
231
+ inherits: true;
232
+ initial-value: 9999px;
233
+ }
234
+
235
+ @property --radius-inputs {
236
+ syntax: "<length-percentage>";
237
+ inherits: true;
238
+ initial-value: 6px;
239
+ }
240
+
241
+ @property --radius-pill {
242
+ syntax: "<length-percentage>";
243
+ inherits: true;
244
+ initial-value: 9999px;
245
+ }
246
+
247
+ @property --control-height-tiny {
248
+ syntax: "<length>";
249
+ inherits: true;
250
+ initial-value: 24px;
251
+ }
252
+
253
+ @property --control-height-sm {
254
+ syntax: "<length>";
255
+ inherits: true;
256
+ initial-value: 32px;
257
+ }
258
+
259
+ @property --control-height-md {
260
+ syntax: "<length>";
261
+ inherits: true;
262
+ initial-value: 40px;
263
+ }
264
+
265
+ @property --control-height-lg {
266
+ syntax: "<length>";
267
+ inherits: true;
268
+ initial-value: 48px;
269
+ }
270
+
271
+ @property --control-height-icon-sm {
272
+ syntax: "<length>";
273
+ inherits: true;
274
+ initial-value: 28px;
275
+ }
276
+
277
+ @property --control-height-icon-md {
278
+ syntax: "<length>";
279
+ inherits: true;
280
+ initial-value: 32px;
281
+ }
282
+
283
+ @property --control-height-icon-lg {
284
+ syntax: "<length>";
285
+ inherits: true;
286
+ initial-value: 36px;
287
+ }
288
+
289
+ @property --control-font-size-tiny {
290
+ syntax: "<length>";
291
+ inherits: true;
292
+ initial-value: 11px;
293
+ }
294
+
295
+ @property --control-font-size-sm {
296
+ syntax: "<length>";
297
+ inherits: true;
298
+ initial-value: 12px;
299
+ }
300
+
301
+ @property --control-font-size-md {
302
+ syntax: "<length>";
303
+ inherits: true;
304
+ initial-value: 14px;
305
+ }
306
+
307
+ @property --control-font-size-lg {
308
+ syntax: "<length>";
309
+ inherits: true;
310
+ initial-value: 16px;
311
+ }
312
+
313
+ /* ── Numeric slots ────────────────────────────────────────────────────────────
314
+ * `font-weight` also accepts `normal` / `bold` in CSS, so those two idents are
315
+ * spelled into the syntax rather than excluded. `bolder` / `lighter` are left
316
+ * out on purpose: they are parent-relative and cannot survive as a slot value.
317
+ * Every skin currently writes a number (300–700).
318
+ */
319
+ @property --font-weight-medium {
320
+ syntax: "<number> | normal | bold";
321
+ inherits: true;
322
+ initial-value: 500;
323
+ }
324
+
325
+ @property --font-weight-heading {
326
+ syntax: "<number> | normal | bold";
327
+ inherits: true;
328
+ initial-value: 600;
329
+ }
330
+
331
+ /* ── Genuinely untypeable: box-shadow lists ───────────────────────────────────
332
+ * These four are consumed as `box-shadow`, and the `@property` syntax grammar
333
+ * has no `<shadow>` component — its component names are limited to <length>,
334
+ * <number>, <percentage>, <length-percentage>, <color>, <image>, <url>,
335
+ * <integer>, <angle>, <time>, <resolution>, <transform-function>,
336
+ * <transform-list>, <custom-ident> and <string>, with only `+` / `#`
337
+ * multipliers. A shadow list ("0 1px 2px 0 rgb(…)", possibly comma-separated)
338
+ * cannot be spelled in that grammar, so `*` is the honest answer, not a
339
+ * shortcut. These slots therefore remain unguarded and a colour written into
340
+ * one still voids the `box-shadow` declaration that reads it. The audit script
341
+ * is the backstop for them.
342
+ */
343
+ @property --btn-default-shadow {
344
+ syntax: "*";
345
+ inherits: true;
346
+ }
347
+
348
+ @property --elevation-card {
349
+ syntax: "*";
350
+ inherits: true;
351
+ }
352
+
353
+ @property --elevation-control {
354
+ syntax: "*";
355
+ inherits: true;
356
+ }
357
+
358
+ @property --elevation-raised {
359
+ syntax: "*";
360
+ inherits: true;
361
+ }
362
+
363
+ /* ── Genuinely untypeable: bare HSL channel triplets ──────────────────────────
364
+ * These carry channels ("222 47% 11%"), not colours — they exist to be wrapped
365
+ * as `hsl(var(--brand-mark))` / `hsl(var(--brand-mark) / 0.08)`, which is what
366
+ * makes the alpha-suffix idiom possible. `<color>` would reject every one of
367
+ * them. There is also no typed spelling for the triplet: `@property` syntax has
368
+ * no space-separated component composition and no <number-percentage>, so
369
+ * "<number> <percentage> <percentage>" is not expressible. `*` is correct here.
370
+ * The type error these slots invite is the inverse one — writing a full colour
371
+ * (`#0033FE`) where channels are expected, which the audit script catches at
372
+ * the `hsl()` call site.
373
+ */
374
+ @property --role-action {
375
+ syntax: "*";
376
+ inherits: true;
377
+ }
378
+
379
+ @property --role-action-fg {
380
+ syntax: "*";
381
+ inherits: true;
382
+ }
383
+
384
+ @property --brand-mark {
385
+ syntax: "*";
386
+ inherits: true;
387
+ }
388
+
389
+ @property --brand-mark-foreground {
390
+ syntax: "*";
391
+ inherits: true;
392
+ }
393
+
65
394
  /* Tailwind v4: brand-mark as first-class color (Logo / BrandMark / badge) */
66
395
  @theme inline {
67
396
  --color-brand-mark: hsl(var(--brand-mark));
package/skins/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Design skins & Brand Packages
2
2
 
3
3
  **Goal:** Create Center users swap a **design language** by applying one **Brand Package**.
4
- This package is a *carrier* (roles + free elev/radii + zones), not a list of brand hacks.
4
+ This package is a *carrier* (roles + free elev/radii), not a list of brand hacks.
5
5
 
6
6
  ## Two layers
7
7
 
@@ -130,11 +130,9 @@ const { iframeRef, apply: preview, writePreviewDocument } = useBrandIframePrevie
130
130
  // preview(brand) or writePreviewDocument(brand, "<button class='btn-brand-default'>CTA</button>")
131
131
  ```
132
132
 
133
- ### Fonts + zones
133
+ ### Fonts
134
134
 
135
135
  - `typography.faces[]` → emitted as `@font-face` (swap URL for Create Center CDN/upload).
136
- - `zones.product` / `zones.marketing` → CSS vars + `[data-zone="…"]` / `.zone-…` consumers.
137
- - Marketing may use 224px display; product stays compact.
138
136
 
139
137
  ### design-sync
140
138
 
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Brand carrier skin: Cosmos (cosmos) v2.0.0
3
+ * darkDefault=false dualMode=true button=solid
4
+ * fonts=0 mode=global
5
+ * Contract: roles.action → --primary; roles.brand → --brand-mark (never default CTA)
6
+ */
7
+
8
+ :root,
9
+ html[data-brand="cosmos"] {
10
+ /* ── Color roles (carrier) ── */
11
+ --role-canvas: 30 20% 96%;
12
+ --role-canvas-fg: 0 0% 5%;
13
+ --role-surface: 0 0% 100%;
14
+ --role-surface-fg: 0 0% 5%;
15
+ --role-action: 0 0% 5%;
16
+ --role-action-fg: 30 20% 96%;
17
+ --role-quiet: 0 0% 100%;
18
+ --role-quiet-fg: 0 0% 5%;
19
+ --role-muted: 30 20% 96%;
20
+ --role-muted-fg: 12 2% 42%;
21
+ --role-border: 30 5% 85%;
22
+ --role-input: 30 5% 85%;
23
+ --role-ring: 0 0% 5%;
24
+ --role-brand: 0 0% 5%;
25
+ --role-brand-fg: 30 20% 96%;
26
+ --brand-mark: 0 0% 5%;
27
+ --brand-mark-foreground: 30 20% 96%;
28
+
29
+ /* ── shadcn bridge (primary = action CTA) ── */
30
+ --background: 30 20% 96%;
31
+ --foreground: 0 0% 5%;
32
+ --card: 0 0% 100%;
33
+ --card-foreground: 0 0% 5%;
34
+ --popover: 0 0% 100%;
35
+ --popover-foreground: 0 0% 5%;
36
+ --primary: 0 0% 5%;
37
+ --primary-foreground: 30 20% 96%;
38
+ --secondary: 0 0% 100%;
39
+ --secondary-foreground: 0 0% 5%;
40
+ --muted: 30 20% 96%;
41
+ --muted-foreground: 12 2% 42%;
42
+ --accent: 0 0% 5%;
43
+ --accent-foreground: 30 20% 96%;
44
+ --destructive: 4 76% 40%;
45
+ --destructive-foreground: 0 0% 100%;
46
+ --border: 30 5% 85%;
47
+ --input: 30 5% 85%;
48
+ --ring: 0 0% 5%;
49
+ --success: 134 62% 40%;
50
+ --success-foreground: 0 0% 100%;
51
+ --info: 12 2% 42%;
52
+ --info-foreground: 0 0% 100%;
53
+ --sidebar: 0 0% 100%;
54
+ --sidebar-foreground: 0 0% 5%;
55
+ --sidebar-primary: 0 0% 5%;
56
+ --sidebar-primary-foreground: 30 20% 96%;
57
+ --sidebar-accent: 0 0% 5%;
58
+ --sidebar-accent-foreground: 30 20% 96%;
59
+ --sidebar-border: 30 5% 85%;
60
+ --sidebar-ring: 0 0% 5%;
61
+ --brand-primary: hsl(0 0% 5%);
62
+ --brand-accent: hsl(0 0% 5%);
63
+ --brand-accent-foreground: hsl(30 20% 96%);
64
+ --brand-tertiary: hsl(0 0% 5%);
65
+ --brand-gradient: hsl(var(--primary));
66
+ --brand-gradient-reverse: hsl(var(--primary));
67
+ --brand-gradient-vertical: hsl(var(--primary));
68
+ --brand-gradient-radial: hsl(var(--primary));
69
+
70
+ /* ── Neutral ramp, anchored on this language's roles ── */
71
+ --neutral-1: hsl(30 20% 96%);
72
+ --neutral-2: hsl(0 0% 100%);
73
+ --neutral-3: color-mix(in oklab, hsl(30 5% 85%) 20%, hsl(0 0% 100%));
74
+ --neutral-4: color-mix(in oklab, hsl(30 5% 85%) 40%, hsl(0 0% 100%));
75
+ --neutral-5: color-mix(in oklab, hsl(30 5% 85%) 60%, hsl(0 0% 100%));
76
+ --neutral-6: color-mix(in oklab, hsl(30 5% 85%) 80%, hsl(0 0% 100%));
77
+ --neutral-7: hsl(30 5% 85%);
78
+ --neutral-8: color-mix(in oklab, hsl(12 2% 42%) 25%, hsl(30 5% 85%));
79
+ --neutral-9: color-mix(in oklab, hsl(12 2% 42%) 50%, hsl(30 5% 85%));
80
+ --neutral-10: color-mix(in oklab, hsl(12 2% 42%) 75%, hsl(30 5% 85%));
81
+ --neutral-11: hsl(12 2% 42%);
82
+ --neutral-12: hsl(0 0% 5%);
83
+
84
+ /* Recipe (action language + free elev/radii) */
85
+ /* Shape slots */
86
+ --btn-default-radius: 16px;
87
+ --radius-button: 16px;
88
+ --radius-buttons: 16px;
89
+ --radius-md: min(16px, 16px);
90
+ --radius-card: 16px;
91
+ --radius-lg: 16px;
92
+ --radius-badge: 16px;
93
+ --badge-default-radius: 16px;
94
+ --radius-inputs: 16px;
95
+ --input-radius: 16px;
96
+ --radius-pill: 9999px;
97
+
98
+ /* Free elevation (carrier-provided CSS shadows) */
99
+ --elevation-card: 0 0 #0000;
100
+ --elevation-control: 0 0 #0000;
101
+ --elevation-raised: 0 0 #0000;
102
+ --elevation-xs: 0 0 #0000;
103
+ --elevation-sm: 0 0 #0000;
104
+ --elevation-md: 0 0 #0000;
105
+ --elevation-lg: 0 0 #0000;
106
+ --btn-default-shadow: 0 0 #0000;
107
+ --btn-default-bg: hsl(var(--primary));
108
+ --btn-default-fg: hsl(var(--primary-foreground));
109
+ --btn-default-border-width: 0px;
110
+ --btn-default-border: transparent;
111
+ --btn-default-stroke-gradient: linear-gradient(transparent, transparent);
112
+ --btn-default-hover-bg: color-mix(in srgb, hsl(var(--primary)) 90%, transparent);
113
+ --badge-default-bg: hsl(var(--secondary));
114
+ --badge-default-fg: hsl(var(--secondary-foreground));
115
+ --badge-default-border: transparent;
116
+ --badge-default-hover-bg: color-mix(in srgb, hsl(var(--secondary)) 90%, white);
117
+
118
+ /* Typography */
119
+ --font-sans:
120
+ var(--font-reg-fraunces), "Fraunces", "GT Super", Georgia,
121
+ var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC", ui-serif, serif;
122
+ --font-cn:
123
+ var(--font-reg-fraunces), "Fraunces", "GT Super", Georgia,
124
+ var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC", ui-serif, serif;
125
+ --font-heading:
126
+ var(--font-reg-fraunces), "Fraunces", "GT Super", Georgia,
127
+ var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC", ui-serif, serif;
128
+ --font-display:
129
+ var(--font-reg-fraunces), "Fraunces", "GT Super", Georgia,
130
+ var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC", ui-serif, serif;
131
+ --font-mono: var(--font-jetbrains-mono), "JetBrains Mono", ui-monospace, monospace;
132
+ --font-weight-heading: 350;
133
+
134
+ /* Motion */
135
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
136
+ --motion-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
137
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
138
+ --motion-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
139
+ --duration-micro: 80ms;
140
+ --motion-duration-micro: 80ms;
141
+ --duration-flow: 180ms;
142
+ --motion-duration-flow: 180ms;
143
+ --duration-reveal: 180ms;
144
+ --motion-duration-reveal: 180ms;
145
+ --duration-cinematic: 320ms;
146
+ --motion-duration-cinematic: 320ms;
147
+
148
+ /* Spacing */
149
+ --space-source-xs: 0.5rem;
150
+ --space-source-sm: 1rem;
151
+ --space-source-md: 1.25rem;
152
+ --space-source-lg: 1.5rem;
153
+ --space-source-xl: 2rem;
154
+ --space-source-2xl: 3rem;
155
+
156
+ /* Taxonomy / decorative (not product CTA) */
157
+ --brand-category-linen-canvas: #f7f5f3;
158
+ --brand-category-ink-black: #0d0d0d;
159
+ --brand-category-paper-white: #ffffff;
160
+ --brand-category-stone: #6e6a69;
161
+ --brand-category-pebble: #9a9796;
162
+ }
163
+
164
+ .dark,
165
+ html.dark[data-brand="cosmos"] {
166
+ /* ── Color roles (carrier) ── */
167
+ --role-canvas: 0 0% 5%;
168
+ --role-canvas-fg: 30 20% 96%;
169
+ --role-surface: 0 0% 11%;
170
+ --role-surface-fg: 30 20% 96%;
171
+ --role-action: 30 20% 96%;
172
+ --role-action-fg: 0 0% 5%;
173
+ --role-quiet: 0 0% 14%;
174
+ --role-quiet-fg: 30 20% 96%;
175
+ --role-muted: 0 0% 11%;
176
+ --role-muted-fg: 15 2% 60%;
177
+ --role-border: 0 1% 18%;
178
+ --role-input: 0 1% 18%;
179
+ --role-ring: 30 20% 96%;
180
+ --role-brand: 30 20% 96%;
181
+ --role-brand-fg: 0 0% 5%;
182
+ --brand-mark: 30 20% 96%;
183
+ --brand-mark-foreground: 0 0% 5%;
184
+
185
+ /* ── shadcn bridge (primary = action CTA) ── */
186
+ --background: 0 0% 5%;
187
+ --foreground: 30 20% 96%;
188
+ --card: 0 0% 11%;
189
+ --card-foreground: 30 20% 96%;
190
+ --popover: 0 0% 11%;
191
+ --popover-foreground: 30 20% 96%;
192
+ --primary: 30 20% 96%;
193
+ --primary-foreground: 0 0% 5%;
194
+ --secondary: 0 0% 14%;
195
+ --secondary-foreground: 30 20% 96%;
196
+ --muted: 0 0% 11%;
197
+ --muted-foreground: 15 2% 60%;
198
+ --accent: 30 20% 96%;
199
+ --accent-foreground: 0 0% 5%;
200
+ --destructive: 4 76% 40%;
201
+ --destructive-foreground: 0 0% 100%;
202
+ --border: 0 1% 18%;
203
+ --input: 0 1% 18%;
204
+ --ring: 30 20% 96%;
205
+ --success: 134 62% 40%;
206
+ --success-foreground: 0 0% 100%;
207
+ --info: 12 2% 42%;
208
+ --info-foreground: 0 0% 100%;
209
+ --sidebar: 0 0% 11%;
210
+ --sidebar-foreground: 30 20% 96%;
211
+ --sidebar-primary: 30 20% 96%;
212
+ --sidebar-primary-foreground: 0 0% 5%;
213
+ --sidebar-accent: 30 20% 96%;
214
+ --sidebar-accent-foreground: 0 0% 5%;
215
+ --sidebar-border: 0 1% 18%;
216
+ --sidebar-ring: 30 20% 96%;
217
+ --brand-primary: hsl(30 20% 96%);
218
+ --brand-accent: hsl(30 20% 96%);
219
+ --brand-accent-foreground: hsl(0 0% 5%);
220
+ --brand-tertiary: hsl(30 20% 96%);
221
+ --brand-gradient: hsl(var(--primary));
222
+ --brand-gradient-reverse: hsl(var(--primary));
223
+ --brand-gradient-vertical: hsl(var(--primary));
224
+ --brand-gradient-radial: hsl(var(--primary));
225
+
226
+ /* ── Neutral ramp, anchored on this language's roles ── */
227
+ --neutral-1: hsl(0 0% 5%);
228
+ --neutral-2: hsl(0 0% 11%);
229
+ --neutral-3: color-mix(in oklab, hsl(0 1% 18%) 20%, hsl(0 0% 11%));
230
+ --neutral-4: color-mix(in oklab, hsl(0 1% 18%) 40%, hsl(0 0% 11%));
231
+ --neutral-5: color-mix(in oklab, hsl(0 1% 18%) 60%, hsl(0 0% 11%));
232
+ --neutral-6: color-mix(in oklab, hsl(0 1% 18%) 80%, hsl(0 0% 11%));
233
+ --neutral-7: hsl(0 1% 18%);
234
+ --neutral-8: color-mix(in oklab, hsl(15 2% 60%) 25%, hsl(0 1% 18%));
235
+ --neutral-9: color-mix(in oklab, hsl(15 2% 60%) 50%, hsl(0 1% 18%));
236
+ --neutral-10: color-mix(in oklab, hsl(15 2% 60%) 75%, hsl(0 1% 18%));
237
+ --neutral-11: hsl(15 2% 60%);
238
+ --neutral-12: hsl(30 20% 96%);
239
+ }