@nebutra/tokens 0.1.2 → 0.1.3

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 (69) hide show
  1. package/brands/gsap/brand.json +17 -64
  2. package/brands/linear/brand.json +18 -57
  3. package/brands/notion/brand.json +18 -62
  4. package/brands/raycast/brand.json +17 -64
  5. package/brands/stripe/brand.json +16 -72
  6. package/brands/vanta/brand.json +16 -63
  7. package/brands/vercel/brand.json +16 -50
  8. package/dist/brand-package/index.d.ts +123 -0
  9. package/dist/brand-package/index.js +61 -0
  10. package/dist/brand-package/index.js.map +1 -0
  11. package/dist/brand-package/use-brand.d.ts +2 -0
  12. package/dist/brand-package/use-brand.js +12 -0
  13. package/dist/brand-package/use-brand.js.map +1 -0
  14. package/dist/chunk-ALJTP5HL.js +881 -0
  15. package/dist/chunk-ALJTP5HL.js.map +1 -0
  16. package/dist/chunk-RGUJQOLH.js +1582 -0
  17. package/dist/chunk-RGUJQOLH.js.map +1 -0
  18. package/dist/index.d.ts +81 -0
  19. package/dist/index.js +187 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/use-brand-C8d4DPqE.d.ts +371 -0
  22. package/package.json +34 -9
  23. package/recipe.css +322 -1
  24. package/skins/README.md +2 -4
  25. package/skins/gsap.css +31 -119
  26. package/skins/linear.css +33 -117
  27. package/skins/notion.css +27 -117
  28. package/skins/raycast.css +32 -119
  29. package/skins/stripe.css +27 -129
  30. package/skins/vanta.css +27 -122
  31. package/skins/vercel.css +30 -111
  32. package/styles.css +388 -113
  33. package/.turbo/turbo-build.log +0 -8
  34. package/.turbo/turbo-typecheck.log +0 -4
  35. package/AGENTS.md +0 -45
  36. package/DESIGN.md +0 -357
  37. package/scripts/compile-brand-run.ts +0 -79
  38. package/scripts/compile-brand.mjs +0 -25
  39. package/scripts/emit-skins-run.ts +0 -62
  40. package/scripts/emit-skins.mjs +0 -31
  41. package/scripts/sync-styles.mjs +0 -29
  42. package/src/brand-package/__tests__/compile-refero.test.ts +0 -427
  43. package/src/brand-package/__tests__/emit-css.test.ts +0 -130
  44. package/src/brand-package/apply-brand.ts +0 -92
  45. package/src/brand-package/compile-helpers.ts +0 -170
  46. package/src/brand-package/compile-refero.ts +0 -69
  47. package/src/brand-package/emit-css.ts +0 -468
  48. package/src/brand-package/hex-to-hsl.ts +0 -100
  49. package/src/brand-package/index.ts +0 -71
  50. package/src/brand-package/infer-recipe.ts +0 -198
  51. package/src/brand-package/normalize.ts +0 -295
  52. package/src/brand-package/presets/context.ts +0 -17
  53. package/src/brand-package/presets/generic.ts +0 -214
  54. package/src/brand-package/presets/gsap.ts +0 -146
  55. package/src/brand-package/presets/index.ts +0 -31
  56. package/src/brand-package/presets/linear.ts +0 -183
  57. package/src/brand-package/presets/notion.ts +0 -208
  58. package/src/brand-package/presets/raycast.ts +0 -138
  59. package/src/brand-package/presets/recipe.ts +0 -61
  60. package/src/brand-package/presets/stripe.ts +0 -207
  61. package/src/brand-package/presets/vanta.ts +0 -220
  62. package/src/brand-package/presets/vercel.ts +0 -187
  63. package/src/brand-package/types.ts +0 -285
  64. package/src/brand-package/use-brand.ts +0 -207
  65. package/src/brand-package/validate.ts +0 -108
  66. package/src/index.ts +0 -67
  67. package/src/theme-provider.tsx +0 -246
  68. package/tsconfig.json +0 -11
  69. package/turbo.json +0 -15
@@ -1,220 +0,0 @@
1
- /**
2
- * vanta design-language compile preset (stress fixture).
3
- */
4
- import { leafHex } from "../compile-helpers";
5
- import { tryHexToHsl } from "../hex-to-hsl";
6
- import type { BrandPackage } from "../types";
7
- import type { CompileContext } from "./context";
8
- import { buildRecipe } from "./recipe";
9
-
10
- export function buildVanta(ctx: CompileContext): BrandPackage {
11
- const parchment = ctx.colors.parchment ?? ctx.colors["page-canvas"] ?? "#f7f8fa";
12
- const paper = ctx.colors.paper ?? ctx.colors["card-surface"] ?? "#ffffff";
13
- const carbon = ctx.colors.carbon ?? "#181822";
14
- const graphite = ctx.colors.graphite ?? "#6d6e87";
15
- const steel = ctx.colors.steel ?? "#9e9fb7";
16
- const ash = ctx.colors.ash ?? "#dfdfe9";
17
- const fog = ctx.colors.fog ?? "#eaeaf1";
18
- const lavender = ctx.colors["lavender-wash"] ?? "#ddd6ff";
19
- const vivid = ctx.colors["vivid-violet"] ?? "#5e05c4";
20
- const indigo = ctx.colors["indigo-ink"] ?? "#260048";
21
- const mid = ctx.colors["mid-violet"] ?? "#8f47d5";
22
- const amber = ctx.colors["amber-signal"] ?? "#ffbe0f";
23
-
24
- ctx.warnings.push(
25
- "Vanta: vivid-violet is action CTA only; indigo-ink is brand-mark/logo (never default CTA fill).",
26
- );
27
- ctx.warnings.push("Vanta: elevation=none — cards use 1px carbon border, not box-shadow.");
28
-
29
- const brand: BrandPackage = {
30
- id: "vanta",
31
- name: "Vanta",
32
- darkDefault: false,
33
- version: "1.0.0",
34
- roles: {
35
- canvas: tryHexToHsl(parchment, "220 23% 97%"),
36
- canvasForeground: tryHexToHsl(carbon, "240 14% 11%"),
37
- surface: tryHexToHsl(paper, "0 0% 100%"),
38
- surfaceForeground: tryHexToHsl(carbon, "240 14% 11%"),
39
- // Action = single saturated CTA moment
40
- action: tryHexToHsl(vivid, "268 95% 39%"),
41
- actionForeground: tryHexToHsl(paper, "0 0% 100%"),
42
- // Brand mark = logo / wordmark / decorative ink (≠ action)
43
- brand: tryHexToHsl(indigo, "273 100% 14%"),
44
- brandForeground: tryHexToHsl(paper, "0 0% 100%"),
45
- // Quiet = lavender informational chips (not violet fill)
46
- quiet: tryHexToHsl(lavender, "249 100% 92%"),
47
- quietForeground: tryHexToHsl(indigo, "273 100% 14%"),
48
- muted: tryHexToHsl(fog, "240 14% 93%"),
49
- mutedForeground: tryHexToHsl(graphite, "237 11% 48%"),
50
- border: tryHexToHsl(carbon, "240 14% 11%"),
51
- input: tryHexToHsl(paper, "0 0% 100%"),
52
- ring: tryHexToHsl(vivid, "268 95% 39%"),
53
- destructive: "0 72% 51%",
54
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
55
- warning: tryHexToHsl(amber, "44 100% 53%"),
56
- warningForeground: tryHexToHsl(carbon, "240 14% 11%"),
57
- info: tryHexToHsl(mid, "269 63% 56%"),
58
- infoForeground: tryHexToHsl(paper, "0 0% 100%"),
59
- },
60
- // semantic filled by normalize from roles
61
- semantic: {
62
- background: tryHexToHsl(parchment, "220 23% 97%"),
63
- foreground: tryHexToHsl(carbon, "240 14% 11%"),
64
- card: tryHexToHsl(paper, "0 0% 100%"),
65
- cardForeground: tryHexToHsl(carbon, "240 14% 11%"),
66
- popover: tryHexToHsl(paper, "0 0% 100%"),
67
- popoverForeground: tryHexToHsl(carbon, "240 14% 11%"),
68
- primary: tryHexToHsl(vivid, "268 95% 39%"),
69
- primaryForeground: tryHexToHsl(paper, "0 0% 100%"),
70
- secondary: tryHexToHsl(lavender, "249 100% 92%"),
71
- secondaryForeground: tryHexToHsl(indigo, "273 100% 14%"),
72
- muted: tryHexToHsl(fog, "240 14% 93%"),
73
- mutedForeground: tryHexToHsl(graphite, "237 11% 48%"),
74
- accent: tryHexToHsl(indigo, "273 100% 14%"),
75
- accentForeground: tryHexToHsl(paper, "0 0% 100%"),
76
- destructive: "0 72% 51%",
77
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
78
- border: tryHexToHsl(carbon, "240 14% 11%"),
79
- input: tryHexToHsl(paper, "0 0% 100%"),
80
- ring: tryHexToHsl(vivid, "268 95% 39%"),
81
- warning: tryHexToHsl(amber, "44 100% 53%"),
82
- warningForeground: tryHexToHsl(carbon, "240 14% 11%"),
83
- info: tryHexToHsl(mid, "269 63% 56%"),
84
- infoForeground: tryHexToHsl(paper, "0 0% 100%"),
85
- },
86
- recipe: buildRecipe({
87
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
88
- // Prefer structural ctx.radius tokens (full=pill, 2xl=cards) over free-text
89
- radii: {
90
- button:
91
- leafHex(ctx.radius, ["buttons"]) ??
92
- leafHex(ctx.radius, ["full"]) ??
93
- ctx.recipeHints.radii?.button ??
94
- "999px",
95
- card: leafHex(ctx.radius, ["cards"]) ?? leafHex(ctx.radius, ["2xl"]) ?? "16px",
96
- badge:
97
- leafHex(ctx.radius, ["badges"]) ??
98
- leafHex(ctx.radius, ["full"]) ??
99
- ctx.recipeHints.radii?.button ??
100
- "999px",
101
- input:
102
- leafHex(ctx.radius, ["inputs"]) ??
103
- leafHex(ctx.radius, ["full"]) ??
104
- ctx.recipeHints.radii?.button ??
105
- "999px",
106
- },
107
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
108
- density: ctx.recipeHints.density ?? "comfortable",
109
- // Informational chips = lavender wash + indigo (quiet), not violet CTA fill
110
- badgeDefault: "muted",
111
- outlineBorder: carbon,
112
- }),
113
- typography: {
114
- fontSans: `'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif`,
115
- fontDisplay: `'Reckless', 'Source Serif 4', 'Lora', ui-serif, Georgia, serif`,
116
- headingWeight: 500,
117
- faces: [
118
- {
119
- family: "Inter Variable",
120
- src: [
121
- {
122
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
123
- format: "woff2",
124
- },
125
- ],
126
- weight: "100 900",
127
- display: "swap",
128
- },
129
- {
130
- family: "Reckless",
131
- // Placeholder — Create Center / tenant hosts the licensed Reckless cut
132
- src: [{ url: "/brand-assets/reckless-regular.woff2", format: "woff2" }],
133
- weight: 400,
134
- display: "swap",
135
- },
136
- {
137
- family: "Reckless",
138
- src: [{ url: "/brand-assets/reckless-medium.woff2", format: "woff2" }],
139
- weight: 500,
140
- display: "swap",
141
- },
142
- ],
143
- },
144
- zones: {
145
- product: {
146
- caption: {
147
- fontSize: "12px",
148
- lineHeight: 1.5,
149
- fontWeight: 500,
150
- letterSpacing: "-0.02px",
151
- },
152
- bodySm: {
153
- fontSize: "14px",
154
- lineHeight: 1.43,
155
- fontWeight: 400,
156
- letterSpacing: "-0.02px",
157
- },
158
- body: {
159
- fontSize: "16px",
160
- lineHeight: 1.5,
161
- fontWeight: 400,
162
- letterSpacing: "-0.02px",
163
- },
164
- subheading: { fontSize: "20px", lineHeight: 1.4, fontWeight: 500 },
165
- headingSm: { fontSize: "24px", lineHeight: 1.35, fontWeight: 500 },
166
- heading: { fontSize: "32px", lineHeight: 1.3, fontWeight: 500 },
167
- },
168
- marketing: {
169
- body: {
170
- fontSize: "16px",
171
- lineHeight: 1.5,
172
- fontWeight: 400,
173
- letterSpacing: "-0.02px",
174
- },
175
- heading: {
176
- fontSize: "42px",
177
- lineHeight: 1.2,
178
- fontWeight: 400,
179
- letterSpacing: "-0.84px",
180
- },
181
- headingLg: {
182
- fontSize: "56px",
183
- lineHeight: 1.15,
184
- fontWeight: 400,
185
- letterSpacing: "-1.12px",
186
- },
187
- display: {
188
- fontSize: "90px",
189
- lineHeight: 1.1,
190
- fontWeight: 400,
191
- letterSpacing: "-1.62px",
192
- },
193
- },
194
- },
195
- extensions: {
196
- categories: {
197
- brand: indigo,
198
- action: vivid,
199
- link: mid,
200
- heroWash: lavender,
201
- warning: amber,
202
- steel,
203
- ash,
204
- },
205
- decorative: {
206
- "hero-wash": lavender,
207
- },
208
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://www.vanta.com",
209
- notes: [
210
- "roles.action = Vivid Violet (filled CTA only).",
211
- "roles.brand = Indigo Ink (logo / wordmark / brand-mark).",
212
- "Elevation none — 1px carbon borders frame cards; no drop-shadow.",
213
- "Full-pill controls (999px); cards 16px.",
214
- "UI = Inter Variable; marketing display = Reckless serif.",
215
- "Lavender wash is marketing hero surface (decorative), not product chrome fill.",
216
- ],
217
- },
218
- };
219
- return brand;
220
- }
@@ -1,187 +0,0 @@
1
- /**
2
- * vercel design-language compile preset (stress fixture).
3
- * Dual-mode: light monochrome + dark monochrome (orthogonal to ThemeProvider class).
4
- */
5
- import { tryHexToHsl } from "../hex-to-hsl";
6
- import type { BrandPackage, BrandSemanticColors } from "../types";
7
- import type { CompileContext } from "./context";
8
- import { buildRecipe } from "./recipe";
9
-
10
- function vercelLightSemantic(
11
- paper: string,
12
- pure: string,
13
- hairline: string,
14
- charcoal: string,
15
- stone: string,
16
- obsidian: string,
17
- terminal: string,
18
- ): BrandSemanticColors {
19
- return {
20
- background: tryHexToHsl(paper, "0 0% 98%"),
21
- foreground: tryHexToHsl(obsidian, "0 0% 9%"),
22
- card: tryHexToHsl(pure, "0 0% 100%"),
23
- cardForeground: tryHexToHsl(obsidian, "0 0% 9%"),
24
- popover: tryHexToHsl(pure, "0 0% 100%"),
25
- popoverForeground: tryHexToHsl(obsidian, "0 0% 9%"),
26
- // Filled black button
27
- primary: tryHexToHsl(obsidian, "0 0% 9%"),
28
- primaryForeground: tryHexToHsl(pure, "0 0% 100%"),
29
- secondary: tryHexToHsl(hairline, "0 0% 92%"),
30
- secondaryForeground: tryHexToHsl(charcoal, "0 0% 30%"),
31
- muted: tryHexToHsl(hairline, "0 0% 92%"),
32
- mutedForeground: tryHexToHsl(stone, "0 0% 40%"),
33
- accent: tryHexToHsl(hairline, "0 0% 92%"),
34
- accentForeground: tryHexToHsl(obsidian, "0 0% 9%"),
35
- destructive: "0 72% 51%",
36
- destructiveForeground: tryHexToHsl(pure, "0 0% 100%"),
37
- border: tryHexToHsl(hairline, "0 0% 92%"),
38
- input: tryHexToHsl(pure, "0 0% 100%"),
39
- ring: tryHexToHsl(obsidian, "0 0% 9%"),
40
- success: tryHexToHsl(terminal, "133 49% 32%"),
41
- successForeground: tryHexToHsl(pure, "0 0% 100%"),
42
- info: tryHexToHsl(charcoal, "0 0% 30%"),
43
- infoForeground: tryHexToHsl(pure, "0 0% 100%"),
44
- };
45
- }
46
-
47
- /** Dark shell monochrome — white CTA on carbon, hairline borders. */
48
- function vercelDarkSemantic(terminal: string): BrandSemanticColors {
49
- return {
50
- background: "0 0% 0%",
51
- foreground: "0 0% 93%",
52
- card: "0 0% 4%",
53
- cardForeground: "0 0% 93%",
54
- popover: "0 0% 4%",
55
- popoverForeground: "0 0% 93%",
56
- primary: "0 0% 100%",
57
- primaryForeground: "0 0% 0%",
58
- secondary: "0 0% 12%",
59
- secondaryForeground: "0 0% 93%",
60
- muted: "0 0% 12%",
61
- mutedForeground: "0 0% 63%",
62
- accent: "0 0% 12%",
63
- accentForeground: "0 0% 93%",
64
- destructive: "0 72% 51%",
65
- destructiveForeground: "0 0% 100%",
66
- border: "0 0% 16%",
67
- input: "0 0% 4%",
68
- ring: "0 0% 100%",
69
- success: tryHexToHsl(terminal, "133 49% 40%"),
70
- successForeground: "0 0% 100%",
71
- info: "0 0% 70%",
72
- infoForeground: "0 0% 0%",
73
- };
74
- }
75
-
76
- export function buildVercel(ctx: CompileContext): BrandPackage {
77
- const paper = ctx.colors["paper-white"] ?? ctx.colors["page-canvas"] ?? "#fafafa";
78
- const pure = ctx.colors["pure-white"] ?? ctx.colors["card-surface"] ?? "#ffffff";
79
- const hairline = ctx.colors.hairline ?? "#ebebeb";
80
- const charcoal = ctx.colors.charcoal ?? "#4d4d4d";
81
- const stone = ctx.colors.stone ?? "#666666";
82
- const obsidian = ctx.colors.obsidian ?? ctx.colors["inverted-surface"] ?? "#171717";
83
- const carbon = ctx.colors.carbon ?? "#000000";
84
- const terminal = ctx.colors["terminal-green"] ?? "#297a3a";
85
-
86
- ctx.warnings.push(
87
- "Vercel: monochrome dual-mode — no chromatic CTA; Terminal Green is support only.",
88
- );
89
-
90
- const light = vercelLightSemantic(paper, pure, hairline, charcoal, stone, obsidian, terminal);
91
- const dark = vercelDarkSemantic(terminal);
92
-
93
- const brand: BrandPackage = {
94
- id: "vercel",
95
- name: "Vercel",
96
- darkDefault: false,
97
- version: "1.0.0",
98
- semantic: light,
99
- modes: {
100
- light: { semantic: light },
101
- dark: { semantic: dark },
102
- },
103
- recipe: buildRecipe({
104
- buttonDefault: "solid",
105
- radii: {
106
- button: ctx.recipeHints.radii?.button ?? "6px",
107
- card: "6px",
108
- badge: "6px",
109
- input: "6px",
110
- },
111
- elevationPreset:
112
- ctx.recipeHints.elevationPreset === "key"
113
- ? "hairline"
114
- : (ctx.recipeHints.elevationPreset ?? "hairline"),
115
- density: ctx.recipeHints.density ?? "compact",
116
- badgeDefault: "muted",
117
- cardShadow: "rgba(0, 0, 0, 0.08) 0px 0px 0px 1px, rgb(250, 250, 250) 0px 0px 0px 1px",
118
- outlineBorder: hairline,
119
- }),
120
- typography: {
121
- fontSans: `'Geist Sans', 'Geist', ui-sans-serif, system-ui, sans-serif`,
122
- fontMono: `'Geist Mono', ui-monospace, Menlo, monospace`,
123
- fontDisplay: `'Geist Sans', 'Geist', ui-sans-serif, system-ui, sans-serif`,
124
- headingWeight: 450,
125
- faces: [
126
- {
127
- family: "Geist Sans",
128
- src: [
129
- {
130
- url: "https://cdn.jsdelivr.net/fontsource/fonts/geist-sans:vf@latest/latin-wght-normal.woff2",
131
- format: "woff2",
132
- },
133
- ],
134
- weight: "100 900",
135
- display: "swap",
136
- },
137
- ],
138
- },
139
- zones: {
140
- product: {
141
- caption: { fontSize: "13px", lineHeight: 1.54, fontWeight: 400 },
142
- bodySm: { fontSize: "14px", lineHeight: 1.43, fontWeight: 400 },
143
- body: { fontSize: "16px", lineHeight: 1.5, fontWeight: 400 },
144
- heading: {
145
- fontSize: "30px",
146
- lineHeight: 1.1,
147
- fontWeight: 500,
148
- letterSpacing: "-1.5px",
149
- },
150
- },
151
- marketing: {
152
- body: { fontSize: "16px", lineHeight: 1.5, fontWeight: 400 },
153
- heading: {
154
- fontSize: "30px",
155
- lineHeight: 1.1,
156
- fontWeight: 450,
157
- letterSpacing: "-1.5px",
158
- },
159
- headingLg: {
160
- fontSize: "56px",
161
- lineHeight: 1,
162
- fontWeight: 450,
163
- letterSpacing: "-3.36px",
164
- },
165
- display: {
166
- fontSize: "64px",
167
- lineHeight: 1,
168
- fontWeight: 450,
169
- letterSpacing: "-3.84px",
170
- },
171
- },
172
- },
173
- extensions: {
174
- categories: {
175
- carbon: carbon,
176
- terminal: terminal,
177
- },
178
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://vercel.com",
179
- notes: [
180
- "Dual-mode monochrome — light: Obsidian CTA; dark: white CTA on carbon.",
181
- "Elevation is hairline double-ring, never drop-shadow.",
182
- "Spectrum/solar gradients are marketing-only decorative (not product chrome).",
183
- ],
184
- },
185
- };
186
- return brand;
187
- }
@@ -1,285 +0,0 @@
1
- /**
2
- * Brand Package — Create Center carrier contract.
3
- *
4
- * This is a *host* for third-party design languages (Linear, GSAP, Raycast, Vercel…),
5
- * not a list of brand presets. Presets only demonstrate the contract.
6
- *
7
- * Layers:
8
- * 1. roles — color meaning (action vs brand-mark vs surface…)
9
- * 2. recipe — control language (button/badge fill, radii slots, free elev CSS)
10
- * 3. typography / zones / fonts
11
- * 4. semantic — shadcn/Tailwind bridge (derived from roles; components keep using --primary)
12
- *
13
- * @see packages/design/ARCHITECTURE.md
14
- */
15
-
16
- /** HSL channel triple without `hsl()` wrapper, e.g. "66 89% 54%" */
17
- export type HslChannels = string;
18
-
19
- /** Full CSS color (hex / rgb / hsl / oklch / gradient) */
20
- export type CssColor = string;
21
-
22
- export type ButtonDefaultStyle = "solid" | "outline" | "gradient-stroke";
23
-
24
- /**
25
- * Elevation *presets* are only shortcuts that expand to free CSS box-shadow tokens.
26
- * Carriers should prefer `recipe.elevationTokens` with arbitrary shadow stacks.
27
- */
28
- export type ElevationStyle = "none" | "soft" | "raised" | "key" | "hairline";
29
-
30
- export type Density = "compact" | "comfortable" | "spacious";
31
-
32
- /** Badge fill language (may diverge from action CTA) */
33
- export type BadgeDefaultStyle = "match-action" | "match-primary" | "muted" | "outline" | "brand";
34
-
35
- export type BrandZoneId = "product" | "marketing";
36
-
37
- /**
38
- * First-class color roles — what Create Center fills.
39
- * Maps onto CSS vars and into shadcn semantic for component compatibility.
40
- */
41
- export interface BrandColorRoles {
42
- /** Page canvas */
43
- canvas: HslChannels;
44
- canvasForeground: HslChannels;
45
- /** Contained surfaces (cards, panels) */
46
- surface: HslChannels;
47
- surfaceForeground: HslChannels;
48
- /**
49
- * Product *action* fill (default Button / primary chrome).
50
- * Must be readable as a CTA — not necessarily the brand mark color.
51
- */
52
- action: HslChannels;
53
- actionForeground: HslChannels;
54
- /**
55
- * Brand mark only (logo accent, AI diamond, VI-adjacent product accent).
56
- * NEVER used for default form CTAs unless Create Center explicitly maps action ← brand.
57
- */
58
- brand?: HslChannels;
59
- brandForeground?: HslChannels;
60
- /** Quiet fills (secondary, badges muted) */
61
- quiet: HslChannels;
62
- quietForeground: HslChannels;
63
- muted: HslChannels;
64
- mutedForeground: HslChannels;
65
- border: HslChannels;
66
- input: HslChannels;
67
- ring: HslChannels;
68
- destructive: HslChannels;
69
- destructiveForeground: HslChannels;
70
- success?: HslChannels;
71
- successForeground?: HslChannels;
72
- warning?: HslChannels;
73
- warningForeground?: HslChannels;
74
- info?: HslChannels;
75
- infoForeground?: HslChannels;
76
- }
77
-
78
- /** Shape slots — components bind per role, not one global radius */
79
- export interface BrandRadii {
80
- button: string;
81
- card: string;
82
- badge?: string;
83
- input?: string;
84
- pill?: string;
85
- }
86
-
87
- /**
88
- * Free-form elevation — any CSS box-shadow string the brand requires.
89
- * Components only read --elevation-card / control / raised.
90
- */
91
- export interface BrandElevationTokens {
92
- card: string;
93
- control?: string;
94
- raised?: string;
95
- }
96
-
97
- /**
98
- * shadcn/Tailwind bridge. Prefer editing `roles`; semantic is kept in sync by normalize().
99
- * Components continue to use bg-primary etc. which map to action.
100
- */
101
- export interface BrandSemanticColors {
102
- background: HslChannels;
103
- foreground: HslChannels;
104
- card: HslChannels;
105
- cardForeground: HslChannels;
106
- popover: HslChannels;
107
- popoverForeground: HslChannels;
108
- /** = roles.action (product CTA) */
109
- primary: HslChannels;
110
- primaryForeground: HslChannels;
111
- secondary: HslChannels;
112
- secondaryForeground: HslChannels;
113
- muted: HslChannels;
114
- mutedForeground: HslChannels;
115
- accent: HslChannels;
116
- accentForeground: HslChannels;
117
- destructive: HslChannels;
118
- destructiveForeground: HslChannels;
119
- border: HslChannels;
120
- input: HslChannels;
121
- ring: HslChannels;
122
- success?: HslChannels;
123
- successForeground?: HslChannels;
124
- warning?: HslChannels;
125
- warningForeground?: HslChannels;
126
- info?: HslChannels;
127
- infoForeground?: HslChannels;
128
- }
129
-
130
- /**
131
- * Canonical product-chrome recipe (post-normalize).
132
- * Free radii + free elevation tokens only — no buttonRadius/elevation aliases.
133
- */
134
- export interface BrandRecipe {
135
- buttonDefault: ButtonDefaultStyle;
136
- density: Density;
137
- radii: BrandRadii;
138
- elevationTokens: BrandElevationTokens;
139
- primaryStrokeGradient?: string;
140
- outlineBorder?: CssColor;
141
- badgeDefault?: BadgeDefaultStyle;
142
- }
143
-
144
- /**
145
- * Loose recipe accepted by normalize/compile before canonicalization.
146
- * Legacy keys (buttonRadius, elevation preset, cardShadow) are input-only.
147
- */
148
- export type BrandRecipeInput = {
149
- buttonDefault: ButtonDefaultStyle;
150
- density?: Density;
151
- radii?: BrandRadii;
152
- elevationTokens?: BrandElevationTokens;
153
- /** @deprecated input-only — expanded to elevationTokens */
154
- elevation?: ElevationStyle;
155
- primaryStrokeGradient?: string;
156
- outlineBorder?: CssColor;
157
- badgeDefault?: BadgeDefaultStyle;
158
- /** @deprecated input-only — use radii.button */
159
- buttonRadius?: string;
160
- /** @deprecated input-only — use radii.card */
161
- cardRadius?: string;
162
- /** @deprecated input-only — use radii.badge */
163
- badgeRadius?: string;
164
- /** @deprecated input-only — use radii.input */
165
- inputRadius?: string;
166
- /** @deprecated input-only — use elevationTokens.card */
167
- cardShadow?: string;
168
- };
169
-
170
- export interface BrandFontSource {
171
- url: string;
172
- format?: "woff2" | "woff" | "truetype" | "opentype" | "svg";
173
- }
174
-
175
- export interface BrandFontFace {
176
- family: string;
177
- src: BrandFontSource[];
178
- weight?: number | string;
179
- style?: "normal" | "italic" | "oblique";
180
- display?: "auto" | "block" | "swap" | "fallback" | "optional";
181
- unicodeRange?: string;
182
- }
183
-
184
- export interface BrandTypeStep {
185
- fontSize: string;
186
- lineHeight?: string | number;
187
- letterSpacing?: string;
188
- fontWeight?: string | number;
189
- }
190
-
191
- export interface BrandZoneTypography {
192
- caption?: BrandTypeStep;
193
- bodySm?: BrandTypeStep;
194
- body?: BrandTypeStep;
195
- bodyLg?: BrandTypeStep;
196
- subheading?: BrandTypeStep;
197
- headingSm?: BrandTypeStep;
198
- heading?: BrandTypeStep;
199
- headingLg?: BrandTypeStep;
200
- display?: BrandTypeStep;
201
- }
202
-
203
- export interface BrandZones {
204
- /** App shell — never inherits marketing display sizes */
205
- product?: BrandZoneTypography;
206
- /** Landing / hero — large display allowed */
207
- marketing?: BrandZoneTypography;
208
- }
209
-
210
- export interface BrandTypography {
211
- fontSans: string;
212
- fontMono?: string;
213
- fontDisplay?: string;
214
- headingWeight?: number | string;
215
- faces?: BrandFontFace[];
216
- }
217
-
218
- export interface BrandExtensions {
219
- /** Taxonomy / category accents (GSAP disciplines, etc.) — product chrome may ignore */
220
- categories?: Record<string, CssColor>;
221
- /** Marketing-only decorative gradients (spectrum, hero floor) — never product CTA */
222
- decorative?: Record<string, string>;
223
- /** @deprecated prefer zones.marketing.display */
224
- displaySizePx?: number;
225
- sourceUrl?: string;
226
- notes?: string[];
227
- }
228
-
229
- /**
230
- * One appearance mode’s colors (light or dark).
231
- * Prefer roles; semantic is the shadcn bridge (derived when omitted).
232
- */
233
- export interface BrandModePalette {
234
- roles?: BrandColorRoles;
235
- semantic?: BrandSemanticColors;
236
- }
237
-
238
- /**
239
- * Optional dual-mode colors. When both light and dark are set, emit binds:
240
- * light → :root / html[data-brand]
241
- * dark → .dark / html.dark[data-brand]
242
- * Single-mode packs omit this and use top-level semantic + darkDefault selector rules.
243
- */
244
- export interface BrandModes {
245
- light?: BrandModePalette;
246
- dark?: BrandModePalette;
247
- }
248
-
249
- export interface BrandPackage {
250
- id: string;
251
- name: string;
252
- darkDefault: boolean;
253
- version: string;
254
- /**
255
- * Canonical color roles for the **default** mode (see darkDefault).
256
- * If omitted, derived from semantic on normalize().
257
- */
258
- roles?: BrandColorRoles;
259
- /**
260
- * shadcn bridge for the default mode — always present after normalize().
261
- */
262
- semantic: BrandSemanticColors;
263
- /**
264
- * Dual light/dark palettes (optional). When present after normalize, both modes
265
- * are fully specified and emitBrandCss writes separate light/dark color blocks.
266
- */
267
- modes?: BrandModes;
268
- /** Always canonical after normalizeBrandPackage(). */
269
- recipe: BrandRecipe;
270
- typography: BrandTypography;
271
- zones?: BrandZones;
272
- extensions?: BrandExtensions;
273
- }
274
-
275
- /** Pre-normalize package (recipe may still use legacy keys). */
276
- export type BrandPackageInput = Omit<BrandPackage, "recipe"> & {
277
- recipe: BrandRecipeInput;
278
- modes?: BrandModes;
279
- };
280
-
281
- export interface CompileResult {
282
- brand: BrandPackage;
283
- css: string;
284
- warnings: string[];
285
- }