@nebutra/tokens 0.1.3 → 3.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 (41) hide show
  1. package/CHANGELOG.md +16 -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 +1 -27
  7. package/brands/linear/DESIGN.md +478 -0
  8. package/brands/linear/brand.json +2 -19
  9. package/brands/notion/brand.json +2 -28
  10. package/brands/raycast/brand.json +2 -17
  11. package/brands/stripe/brand.json +1 -16
  12. package/brands/vanta/brand.json +1 -38
  13. package/brands/vercel/brand.json +1 -18
  14. package/dist/brand-package/index.d.ts +13 -7
  15. package/dist/brand-package/index.js +2290 -34
  16. package/dist/brand-package/index.js.map +1 -1
  17. package/dist/brand-package/use-brand.d.ts +1 -1
  18. package/dist/brand-package/use-brand.js +957 -5
  19. package/dist/brand-package/use-brand.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +2417 -22
  22. package/dist/index.js.map +1 -1
  23. package/dist/{use-brand-C8d4DPqE.d.ts → use-brand-ClmvK_lC.d.ts} +19 -3
  24. package/dist/values.d.ts +1192 -0
  25. package/dist/values.js +518 -0
  26. package/dist/values.js.map +1 -0
  27. package/package.json +16 -4
  28. package/recipe.css +36 -2
  29. package/skins/cosmos.css +239 -0
  30. package/skins/gsap.css +32 -20
  31. package/skins/linear.css +54 -17
  32. package/skins/notion.css +36 -25
  33. package/skins/raycast.css +37 -19
  34. package/skins/stripe.css +34 -15
  35. package/skins/vanta.css +34 -30
  36. package/skins/vercel.css +55 -19
  37. package/styles.css +492 -149
  38. package/dist/chunk-ALJTP5HL.js +0 -881
  39. package/dist/chunk-ALJTP5HL.js.map +0 -1
  40. package/dist/chunk-RGUJQOLH.js +0 -1582
  41. package/dist/chunk-RGUJQOLH.js.map +0 -1
@@ -1,881 +0,0 @@
1
- // src/brand-package/use-brand.ts
2
- import { useCallback, useEffect, useRef, useState } from "react";
3
-
4
- // src/brand-package/hex-to-hsl.ts
5
- function hexToHslChannels(hex) {
6
- let h = hex.trim().replace(/^#/, "");
7
- if (h.length === 3) {
8
- h = h.split("").map((c) => c + c).join("");
9
- }
10
- if (h.length !== 6 || !/^[0-9a-fA-F]+$/.test(h)) {
11
- throw new Error(`Invalid hex color: ${hex}`);
12
- }
13
- const r = Number.parseInt(h.slice(0, 2), 16) / 255;
14
- const g = Number.parseInt(h.slice(2, 4), 16) / 255;
15
- const b = Number.parseInt(h.slice(4, 6), 16) / 255;
16
- return srgbToHslChannels(r, g, b);
17
- }
18
- function srgbToHslChannels(r, g, b) {
19
- const max = Math.max(r, g, b);
20
- const min = Math.min(r, g, b);
21
- const l = (max + min) / 2;
22
- let s = 0;
23
- let hue = 0;
24
- if (max !== min) {
25
- const d = max - min;
26
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
27
- switch (max) {
28
- case r:
29
- hue = (g - b) / d + (g < b ? 6 : 0);
30
- break;
31
- case g:
32
- hue = (b - r) / d + 2;
33
- break;
34
- default:
35
- hue = (r - g) / d + 4;
36
- }
37
- hue /= 6;
38
- }
39
- const H = Math.round(hue * 360);
40
- const S = Math.round(s * 100);
41
- const L = Math.round(l * 100);
42
- return `${H} ${S}% ${L}%`;
43
- }
44
- var HSL_CHANNELS_RE = /^(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%$/;
45
- function colorToHslChannels(color) {
46
- const t = color.trim();
47
- if (!t) throw new Error("Empty color");
48
- const channels = t.match(HSL_CHANNELS_RE);
49
- if (channels) {
50
- return `${Math.round(Number(channels[1]))} ${Math.round(Number(channels[2]))}% ${Math.round(Number(channels[3]))}%`;
51
- }
52
- const hslPrefix = t.match(/^hsla?\(/i);
53
- if (hslPrefix) {
54
- const inner = t.slice(hslPrefix[0].length).split(")")[0] ?? "";
55
- const nums = inner.match(/[\d.]+/g) ?? [];
56
- if (nums.length >= 3) {
57
- return `${Math.round(Number(nums[0]))} ${Math.round(Number(nums[1]))}% ${Math.round(Number(nums[2]))}%`;
58
- }
59
- }
60
- const rgbPrefix = t.match(/^rgba?\(/i);
61
- if (rgbPrefix) {
62
- const inner = t.slice(rgbPrefix[0].length).split(")")[0] ?? "";
63
- const nums = inner.match(/[\d.]+/g) ?? [];
64
- if (nums.length >= 3) {
65
- return srgbToHslChannels(Number(nums[0]) / 255, Number(nums[1]) / 255, Number(nums[2]) / 255);
66
- }
67
- }
68
- if (t.startsWith("#") || /^[0-9a-fA-F]{3,8}$/.test(t)) {
69
- return hexToHslChannels(t.startsWith("#") ? t : `#${t}`);
70
- }
71
- throw new Error(`Unsupported color: ${color}`);
72
- }
73
- function tryHexToHsl(hex, fallback) {
74
- return tryColorToHsl(hex, fallback);
75
- }
76
- function tryColorToHsl(color, fallback) {
77
- if (!color) return fallback;
78
- try {
79
- return colorToHslChannels(color);
80
- } catch {
81
- return fallback;
82
- }
83
- }
84
-
85
- // src/brand-package/normalize.ts
86
- var NONE = "0 0 #0000";
87
- var KEY_SHADOW = "rgba(255, 255, 255, 0.05) 0px 1px 0px 0px inset, rgba(255, 255, 255, 0.25) 0px 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px -1px 0px 0px inset";
88
- var HAIRLINE_SHADOW = "rgba(0, 0, 0, 0.08) 0px 0px 0px 1px, rgb(250, 250, 250) 0px 0px 0px 1px";
89
- var SOFT_CARD = "var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05))";
90
- var SOFT_CONTROL = "var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.04))";
91
- var SOFT_RAISED = "var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1))";
92
- function asChannels(value) {
93
- if (value == null || value === "") return void 0;
94
- const v = value.trim();
95
- if (v.startsWith("#")) return tryHexToHsl(v, "0 0% 50%");
96
- return v;
97
- }
98
- function elevationPresetToTokens(preset, cardShadow) {
99
- switch (preset) {
100
- case "none":
101
- return { card: NONE, control: NONE, raised: NONE };
102
- case "key":
103
- return {
104
- card: cardShadow ?? KEY_SHADOW,
105
- control: NONE,
106
- raised: cardShadow ?? KEY_SHADOW
107
- };
108
- case "hairline":
109
- return {
110
- card: cardShadow ?? HAIRLINE_SHADOW,
111
- control: NONE,
112
- raised: cardShadow ?? HAIRLINE_SHADOW
113
- };
114
- case "raised":
115
- return { card: SOFT_RAISED, control: SOFT_CONTROL, raised: SOFT_RAISED };
116
- default:
117
- return { card: SOFT_CARD, control: SOFT_CONTROL, raised: SOFT_RAISED };
118
- }
119
- }
120
- function rolesFromSemantic(s, brandMark) {
121
- const roles = {
122
- canvas: s.background,
123
- canvasForeground: s.foreground,
124
- surface: s.card,
125
- surfaceForeground: s.cardForeground,
126
- action: s.primary,
127
- actionForeground: s.primaryForeground,
128
- quiet: s.secondary,
129
- quietForeground: s.secondaryForeground,
130
- muted: s.muted,
131
- mutedForeground: s.mutedForeground,
132
- border: s.border,
133
- input: s.input,
134
- ring: s.ring,
135
- destructive: s.destructive,
136
- destructiveForeground: s.destructiveForeground
137
- };
138
- const brand = asChannels(brandMark?.brand);
139
- if (brand) roles.brand = brand;
140
- const brandFg = asChannels(brandMark?.brandForeground);
141
- if (brandFg) roles.brandForeground = brandFg;
142
- if (s.success) roles.success = s.success;
143
- if (s.successForeground) roles.successForeground = s.successForeground;
144
- if (s.warning) roles.warning = s.warning;
145
- if (s.warningForeground) roles.warningForeground = s.warningForeground;
146
- if (s.info) roles.info = s.info;
147
- if (s.infoForeground) roles.infoForeground = s.infoForeground;
148
- return roles;
149
- }
150
- function semanticFromRoles(r) {
151
- const accent = r.brand ?? r.quiet;
152
- const accentFg = r.brandForeground ?? r.quietForeground;
153
- const semantic = {
154
- background: r.canvas,
155
- foreground: r.canvasForeground,
156
- card: r.surface,
157
- cardForeground: r.surfaceForeground,
158
- popover: r.surface,
159
- popoverForeground: r.surfaceForeground,
160
- primary: r.action,
161
- primaryForeground: r.actionForeground,
162
- secondary: r.quiet,
163
- secondaryForeground: r.quietForeground,
164
- muted: r.muted,
165
- mutedForeground: r.mutedForeground,
166
- accent,
167
- accentForeground: accentFg,
168
- destructive: r.destructive,
169
- destructiveForeground: r.destructiveForeground,
170
- border: r.border,
171
- input: r.input,
172
- ring: r.ring
173
- };
174
- if (r.success) semantic.success = r.success;
175
- if (r.successForeground) semantic.successForeground = r.successForeground;
176
- if (r.warning) semantic.warning = r.warning;
177
- if (r.warningForeground) semantic.warningForeground = r.warningForeground;
178
- if (r.info) semantic.info = r.info;
179
- if (r.infoForeground) semantic.infoForeground = r.infoForeground;
180
- return semantic;
181
- }
182
- function normalizeRadii(recipe) {
183
- const button = recipe.radii?.button ?? recipe.buttonRadius ?? "0.375rem";
184
- const card = recipe.radii?.card ?? recipe.cardRadius ?? "0.75rem";
185
- const radii = {
186
- button,
187
- card,
188
- badge: recipe.radii?.badge ?? recipe.badgeRadius ?? "9999px",
189
- input: recipe.radii?.input ?? recipe.inputRadius ?? button,
190
- pill: recipe.radii?.pill ?? "9999px"
191
- };
192
- return radii;
193
- }
194
- function normalizeElevation(recipe) {
195
- if (recipe.elevationTokens?.card) {
196
- const elev = { card: recipe.elevationTokens.card };
197
- elev.control = recipe.elevationTokens.control ?? NONE;
198
- elev.raised = recipe.elevationTokens.raised ?? recipe.elevationTokens.card;
199
- return elev;
200
- }
201
- return elevationPresetToTokens(recipe.elevation, recipe.cardShadow);
202
- }
203
- function normalizeBadgeDefault(badge) {
204
- if (!badge || badge === "match-primary") return "match-action";
205
- return badge;
206
- }
207
- function categoryBrandMark(brand) {
208
- return typeof brand.extensions?.categories?.brand === "string" ? brand.extensions.categories.brand : void 0;
209
- }
210
- function normalizeModePalette(palette, categoryBrand) {
211
- let baseRoles;
212
- if (palette.roles) {
213
- baseRoles = { ...palette.roles };
214
- } else if (palette.semantic) {
215
- const mark = {
216
- brandForeground: palette.semantic.primaryForeground
217
- };
218
- if (categoryBrand) mark.brand = categoryBrand;
219
- baseRoles = rolesFromSemantic(palette.semantic, mark);
220
- } else {
221
- throw new Error("BrandModePalette requires roles or semantic");
222
- }
223
- const roles = {
224
- canvas: baseRoles.canvas,
225
- canvasForeground: baseRoles.canvasForeground,
226
- surface: baseRoles.surface,
227
- surfaceForeground: baseRoles.surfaceForeground,
228
- action: baseRoles.action,
229
- actionForeground: baseRoles.actionForeground,
230
- quiet: baseRoles.quiet,
231
- quietForeground: baseRoles.quietForeground,
232
- muted: baseRoles.muted,
233
- mutedForeground: baseRoles.mutedForeground,
234
- border: baseRoles.border,
235
- input: baseRoles.input,
236
- ring: baseRoles.ring,
237
- destructive: baseRoles.destructive,
238
- destructiveForeground: baseRoles.destructiveForeground
239
- };
240
- const brandCh = asChannels(baseRoles.brand);
241
- if (brandCh) roles.brand = brandCh;
242
- const brandFg = asChannels(baseRoles.brandForeground) ?? baseRoles.actionForeground;
243
- if (brandCh) roles.brandForeground = brandFg;
244
- if (baseRoles.success) roles.success = baseRoles.success;
245
- if (baseRoles.successForeground) roles.successForeground = baseRoles.successForeground;
246
- if (baseRoles.warning) roles.warning = baseRoles.warning;
247
- if (baseRoles.warningForeground) roles.warningForeground = baseRoles.warningForeground;
248
- if (baseRoles.info) roles.info = baseRoles.info;
249
- if (baseRoles.infoForeground) roles.infoForeground = baseRoles.infoForeground;
250
- return { roles, semantic: semanticFromRoles(roles) };
251
- }
252
- function normalizeModes(brand, categoryBrand) {
253
- const raw = brand.modes;
254
- if (!raw?.light && !raw?.dark) return void 0;
255
- const modes = {};
256
- if (raw.light) {
257
- const n = normalizeModePalette(raw.light, categoryBrand);
258
- modes.light = { roles: n.roles, semantic: n.semantic };
259
- }
260
- if (raw.dark) {
261
- const n = normalizeModePalette(raw.dark, categoryBrand);
262
- modes.dark = { roles: n.roles, semantic: n.semantic };
263
- }
264
- if (modes.light && modes.dark) return modes;
265
- return modes;
266
- }
267
- function normalizeBrandPackage(brand) {
268
- const categoryBrand = categoryBrandMark(brand);
269
- const modes = normalizeModes(brand, categoryBrand);
270
- const defaultModeKey = brand.darkDefault ? "dark" : "light";
271
- const defaultMode = modes?.[defaultModeKey] ?? modes?.light ?? modes?.dark;
272
- let primaryPalette;
273
- if (defaultMode?.roles || defaultMode?.semantic) {
274
- primaryPalette = {};
275
- if (defaultMode.roles) primaryPalette.roles = defaultMode.roles;
276
- if (defaultMode.semantic) primaryPalette.semantic = defaultMode.semantic;
277
- } else {
278
- primaryPalette = { semantic: brand.semantic };
279
- if (brand.roles) primaryPalette.roles = brand.roles;
280
- }
281
- const { roles, semantic } = normalizeModePalette(primaryPalette, categoryBrand);
282
- const looseRecipe = brand.recipe;
283
- const radii = normalizeRadii(looseRecipe);
284
- const elevationTokens = normalizeElevation(looseRecipe);
285
- const recipe = {
286
- buttonDefault: looseRecipe.buttonDefault,
287
- density: looseRecipe.density ?? "comfortable",
288
- badgeDefault: normalizeBadgeDefault(looseRecipe.badgeDefault),
289
- radii,
290
- elevationTokens
291
- };
292
- if (looseRecipe.primaryStrokeGradient) {
293
- recipe.primaryStrokeGradient = looseRecipe.primaryStrokeGradient;
294
- }
295
- if (looseRecipe.outlineBorder) {
296
- recipe.outlineBorder = looseRecipe.outlineBorder;
297
- }
298
- const out = {
299
- ...brand,
300
- roles,
301
- semantic,
302
- recipe
303
- };
304
- if (modes) out.modes = modes;
305
- else delete out.modes;
306
- return out;
307
- }
308
- function isDualModeBrand(brand) {
309
- return Boolean(brand.modes?.light?.semantic && brand.modes?.dark?.semantic);
310
- }
311
-
312
- // src/brand-package/emit-css.ts
313
- function cssFontStack(stack) {
314
- return stack.replace(/'/g, '"');
315
- }
316
- function motionVars(motion) {
317
- if (!motion) return [];
318
- const lines = [];
319
- const curves = [
320
- ["easeOut", "ease-out"],
321
- ["easeInOut", "ease-in-out"],
322
- ["easeSpring", "ease-spring"]
323
- ];
324
- for (const [key, name] of curves) {
325
- const value = motion[key];
326
- if (typeof value !== "string" || !value.trim()) continue;
327
- lines.push(` --${name}: ${value};`);
328
- lines.push(` --motion-${name}: ${value};`);
329
- }
330
- const durations = [
331
- ["micro", "micro"],
332
- ["flow", "flow"],
333
- ["reveal", "reveal"],
334
- ["cinematic", "cinematic"]
335
- ];
336
- for (const [key, name] of durations) {
337
- const value = motion[key];
338
- if (typeof value !== "number" || !Number.isFinite(value)) continue;
339
- lines.push(` --duration-${name}: ${value}ms;`);
340
- lines.push(` --motion-duration-${name}: ${value}ms;`);
341
- }
342
- return lines.length ? [``, ` /* Motion */`, ...lines] : [];
343
- }
344
- function spacingVars(spacing) {
345
- if (!spacing) return [];
346
- const lines = [];
347
- const steps = [
348
- ["xs", "xs"],
349
- ["sm", "sm"],
350
- ["md", "md"],
351
- ["lg", "lg"],
352
- ["xl", "xl"],
353
- ["2xl", "2xl"]
354
- ];
355
- for (const [key, name] of steps) {
356
- const value = spacing[key];
357
- if (typeof value !== "string" || !value.trim()) continue;
358
- lines.push(` --space-source-${name}: ${value};`);
359
- }
360
- return lines.length ? [``, ` /* Spacing */`, ...lines] : [];
361
- }
362
- function recipeVars(recipe) {
363
- const radii = recipe.radii;
364
- const elev = recipe.elevationTokens;
365
- const lines = [
366
- ` /* Shape slots */`,
367
- ` --btn-default-radius: ${radii.button};`,
368
- ` --radius-button: ${radii.button};`,
369
- ` --radius-buttons: ${radii.button};`,
370
- ` --radius-md: ${radii.button};`,
371
- ` --radius-card: ${radii.card};`,
372
- ` --radius-lg: ${radii.card};`,
373
- ` --radius-badge: ${radii.badge ?? "9999px"};`,
374
- ` --badge-default-radius: ${radii.badge ?? "9999px"};`,
375
- ` --radius-inputs: ${radii.input ?? radii.button};`,
376
- ` --input-radius: ${radii.input ?? radii.button};`,
377
- ` --radius-pill: ${radii.pill ?? "9999px"};`,
378
- ``,
379
- ` /* Free elevation (carrier-provided CSS shadows) */`,
380
- ` --elevation-card: ${elev.card};`,
381
- ` --elevation-control: ${elev.control ?? "0 0 #0000"};`,
382
- ` --elevation-raised: ${elev.raised ?? elev.card};`,
383
- // --elevation-*, not --shadow-*. The theme block maps `--shadow-md` to
384
- // `var(--elevation-md)` and inlines that into `.shadow-md`, so a skin that
385
- // set --shadow-md was writing to the alias while the utility read the
386
- // source. Every language's shadows were therefore byte-identical in the
387
- // browser — measured across all seven with getComputedStyle — while the
388
- // token files disagreed convincingly. Setting the source is what a brand
389
- // switch needs; the alias takes care of itself.
390
- ` --elevation-xs: ${elev.control ?? "0 0 #0000"};`,
391
- ` --elevation-sm: ${elev.card};`,
392
- ` --elevation-md: ${elev.raised ?? elev.card};`,
393
- ` --elevation-lg: ${elev.raised ?? elev.card};`,
394
- ` --btn-default-shadow: 0 0 #0000;`
395
- ];
396
- switch (recipe.buttonDefault) {
397
- case "outline": {
398
- const edge = recipe.outlineBorder ?? "hsl(var(--foreground))";
399
- lines.push(" --btn-default-bg: transparent;");
400
- lines.push(" --btn-default-fg: hsl(var(--foreground));");
401
- lines.push(" --btn-default-border-width: 1px;");
402
- lines.push(" --btn-default-border: transparent;");
403
- lines.push(` --btn-default-stroke-gradient: linear-gradient(${edge}, ${edge});`);
404
- lines.push(" --btn-default-hover-bg: hsl(var(--foreground) / 0.06);");
405
- break;
406
- }
407
- case "gradient-stroke": {
408
- const grad = recipe.primaryStrokeGradient ?? "linear-gradient(135deg, hsl(var(--primary)), color-mix(in srgb, hsl(var(--primary)) 55%, white))";
409
- lines.push(" --btn-default-bg: transparent;");
410
- lines.push(" --btn-default-fg: hsl(var(--foreground));");
411
- lines.push(" --btn-default-border-width: 1.5px;");
412
- lines.push(" --btn-default-border: transparent;");
413
- lines.push(` --btn-default-stroke-gradient: ${grad};`);
414
- lines.push(" --btn-default-hover-bg: hsl(var(--primary) / 0.08);");
415
- break;
416
- }
417
- default:
418
- lines.push(" --btn-default-bg: hsl(var(--primary));");
419
- lines.push(" --btn-default-fg: hsl(var(--primary-foreground));");
420
- lines.push(" --btn-default-border-width: 0px;");
421
- lines.push(" --btn-default-border: transparent;");
422
- lines.push(" --btn-default-stroke-gradient: linear-gradient(transparent, transparent);");
423
- lines.push(
424
- " --btn-default-hover-bg: color-mix(in srgb, hsl(var(--primary)) 90%, transparent);"
425
- );
426
- break;
427
- }
428
- const badgeMode = recipe.badgeDefault ?? "match-action";
429
- if (badgeMode === "outline") {
430
- const edge = recipe.outlineBorder ?? "hsl(var(--border))";
431
- lines.push(" --badge-default-bg: transparent;");
432
- lines.push(" --badge-default-fg: hsl(var(--foreground));");
433
- lines.push(` --badge-default-border: ${edge};`);
434
- lines.push(" --badge-default-hover-bg: hsl(var(--foreground) / 0.06);");
435
- } else if (badgeMode === "muted") {
436
- lines.push(" --badge-default-bg: hsl(var(--secondary));");
437
- lines.push(" --badge-default-fg: hsl(var(--secondary-foreground));");
438
- lines.push(" --badge-default-border: transparent;");
439
- lines.push(" --badge-default-hover-bg: color-mix(in srgb, hsl(var(--secondary)) 90%, white);");
440
- } else if (badgeMode === "brand") {
441
- lines.push(" --badge-default-bg: hsl(var(--brand-mark, var(--accent)));");
442
- lines.push(
443
- " --badge-default-fg: hsl(var(--brand-mark-foreground, var(--accent-foreground)));"
444
- );
445
- lines.push(" --badge-default-border: transparent;");
446
- lines.push(
447
- " --badge-default-hover-bg: color-mix(in srgb, hsl(var(--brand-mark, var(--accent))) 85%, transparent);"
448
- );
449
- } else if (recipe.buttonDefault === "outline" || recipe.buttonDefault === "gradient-stroke") {
450
- const edge = recipe.outlineBorder ?? "hsl(var(--foreground))";
451
- lines.push(" --badge-default-bg: transparent;");
452
- lines.push(" --badge-default-fg: hsl(var(--foreground));");
453
- lines.push(` --badge-default-border: ${edge};`);
454
- lines.push(" --badge-default-hover-bg: hsl(var(--foreground) / 0.06);");
455
- } else {
456
- lines.push(" --badge-default-bg: hsl(var(--primary));");
457
- lines.push(" --badge-default-fg: hsl(var(--primary-foreground));");
458
- lines.push(" --badge-default-border: transparent;");
459
- lines.push(
460
- " --badge-default-hover-bg: color-mix(in srgb, hsl(var(--primary)) 80%, transparent);"
461
- );
462
- }
463
- if (recipe.density === "compact") {
464
- lines.push(" --btn-default-padding-y: 0.5rem;");
465
- lines.push(" --btn-default-padding-x: 0.875rem;");
466
- lines.push(" --control-height-tiny: 1.25rem;");
467
- lines.push(" --control-height-sm: 1.75rem;");
468
- lines.push(" --control-height-md: 2rem;");
469
- lines.push(" --control-height-lg: 2.5rem;");
470
- lines.push(" --control-height-icon-sm: 1.5rem;");
471
- lines.push(" --control-height-icon-md: 1.75rem;");
472
- lines.push(" --control-height-icon-lg: 2rem;");
473
- lines.push(" --control-font-size-md: 0.8125rem;");
474
- } else if (recipe.density === "spacious") {
475
- lines.push(" --btn-default-padding-y: 0.875rem;");
476
- lines.push(" --btn-default-padding-x: 1.5rem;");
477
- lines.push(" --control-height-tiny: 1.75rem;");
478
- lines.push(" --control-height-sm: 2.25rem;");
479
- lines.push(" --control-height-md: 2.75rem;");
480
- lines.push(" --control-height-lg: 3.25rem;");
481
- lines.push(" --control-height-icon-sm: 2rem;");
482
- lines.push(" --control-height-icon-md: 2.25rem;");
483
- lines.push(" --control-height-icon-lg: 2.5rem;");
484
- }
485
- return lines;
486
- }
487
- function emitFontFaces(faces) {
488
- if (!faces?.length) return [];
489
- const out = ["/* Brand font faces */"];
490
- for (const face of faces) {
491
- const src = face.src.map((s) => {
492
- const fmt = s.format ? ` format("${s.format}")` : "";
493
- return `url("${s.url}")${fmt}`;
494
- }).join(", ");
495
- out.push("@font-face {");
496
- out.push(` font-family: "${face.family}";`);
497
- out.push(` src: ${src};`);
498
- if (face.weight != null) out.push(` font-weight: ${face.weight};`);
499
- if (face.style) out.push(` font-style: ${face.style};`);
500
- out.push(` font-display: ${face.display ?? "swap"};`);
501
- if (face.unicodeRange) out.push(` unicode-range: ${face.unicodeRange};`);
502
- out.push("}");
503
- out.push("");
504
- }
505
- return out;
506
- }
507
- function emitGlobalSkinSelector(brandId, darkDefault) {
508
- if (darkDefault) {
509
- return `:root,
510
- .dark,
511
- html[data-brand="${brandId}"] {`;
512
- }
513
- return `:root,
514
- html[data-brand="${brandId}"] {`;
515
- }
516
- function emitLightModeSelector(brandId, mode) {
517
- if (mode === "scoped") return `html[data-brand="${brandId}"] {`;
518
- return `:root,
519
- html[data-brand="${brandId}"] {`;
520
- }
521
- function emitDarkModeSelector(brandId, mode) {
522
- if (mode === "scoped") return `html.dark[data-brand="${brandId}"] {`;
523
- return `.dark,
524
- html.dark[data-brand="${brandId}"] {`;
525
- }
526
- function emitColorVars(s, r) {
527
- const roleLines = [
528
- ` /* \u2500\u2500 Color roles (carrier) \u2500\u2500 */`,
529
- ` --role-canvas: ${r?.canvas ?? s.background};`,
530
- ` --role-canvas-fg: ${r?.canvasForeground ?? s.foreground};`,
531
- ` --role-surface: ${r?.surface ?? s.card};`,
532
- ` --role-surface-fg: ${r?.surfaceForeground ?? s.cardForeground};`,
533
- ` --role-action: ${r?.action ?? s.primary};`,
534
- ` --role-action-fg: ${r?.actionForeground ?? s.primaryForeground};`,
535
- ` --role-quiet: ${r?.quiet ?? s.secondary};`,
536
- ` --role-quiet-fg: ${r?.quietForeground ?? s.secondaryForeground};`,
537
- ` --role-muted: ${r?.muted ?? s.muted};`,
538
- ` --role-muted-fg: ${r?.mutedForeground ?? s.mutedForeground};`,
539
- ` --role-border: ${r?.border ?? s.border};`,
540
- ` --role-input: ${r?.input ?? s.input};`,
541
- ` --role-ring: ${r?.ring ?? s.ring};`
542
- ];
543
- if (r?.brand) {
544
- roleLines.push(` --role-brand: ${r.brand};`);
545
- roleLines.push(
546
- ` --role-brand-fg: ${r.brandForeground ?? r.actionForeground ?? s.primaryForeground};`
547
- );
548
- roleLines.push(` --brand-mark: ${r.brand};`);
549
- roleLines.push(
550
- ` --brand-mark-foreground: ${r.brandForeground ?? r.actionForeground ?? s.primaryForeground};`
551
- );
552
- }
553
- const semantic = [
554
- ``,
555
- ` /* \u2500\u2500 shadcn bridge (primary = action CTA) \u2500\u2500 */`,
556
- ` --background: ${s.background};`,
557
- ` --foreground: ${s.foreground};`,
558
- ` --card: ${s.card};`,
559
- ` --card-foreground: ${s.cardForeground};`,
560
- ` --popover: ${s.popover};`,
561
- ` --popover-foreground: ${s.popoverForeground};`,
562
- ` --primary: ${s.primary};`,
563
- ` --primary-foreground: ${s.primaryForeground};`,
564
- ` --secondary: ${s.secondary};`,
565
- ` --secondary-foreground: ${s.secondaryForeground};`,
566
- ` --muted: ${s.muted};`,
567
- ` --muted-foreground: ${s.mutedForeground};`,
568
- ` --accent: ${s.accent};`,
569
- ` --accent-foreground: ${s.accentForeground};`,
570
- ` --destructive: ${s.destructive};`,
571
- ` --destructive-foreground: ${s.destructiveForeground};`,
572
- ` --border: ${s.border};`,
573
- ` --input: ${s.input};`,
574
- ` --ring: ${s.ring};`
575
- ];
576
- if (s.success) semantic.push(` --success: ${s.success};`);
577
- if (s.successForeground) semantic.push(` --success-foreground: ${s.successForeground};`);
578
- if (s.warning) semantic.push(` --warning: ${s.warning};`);
579
- if (s.warningForeground) semantic.push(` --warning-foreground: ${s.warningForeground};`);
580
- if (s.info) semantic.push(` --info: ${s.info};`);
581
- if (s.infoForeground) semantic.push(` --info-foreground: ${s.infoForeground};`);
582
- semantic.push(
583
- ` --sidebar: ${s.card};`,
584
- ` --sidebar-foreground: ${s.foreground};`,
585
- ` --sidebar-primary: ${s.primary};`,
586
- ` --sidebar-primary-foreground: ${s.primaryForeground};`,
587
- ` --sidebar-accent: ${s.accent};`,
588
- ` --sidebar-accent-foreground: ${s.accentForeground};`,
589
- ` --sidebar-border: ${s.border};`,
590
- ` --sidebar-ring: ${s.ring};`,
591
- ` --brand-gradient: hsl(var(--primary));`,
592
- ` --brand-gradient-reverse: hsl(var(--primary));`,
593
- ` --brand-gradient-vertical: hsl(var(--primary));`,
594
- ` --brand-gradient-radial: hsl(var(--primary));`
595
- );
596
- return [...roleLines, ...semantic];
597
- }
598
- function emitBrandCss(brand, options = {}) {
599
- const mode = options.mode ?? "global";
600
- const b = normalizeBrandPackage(brand);
601
- const t = b.typography;
602
- const dual = isDualModeBrand(b);
603
- const parts = [
604
- `/**`,
605
- ` * Brand carrier skin: ${b.name} (${b.id}) v${b.version}`,
606
- ` * darkDefault=${b.darkDefault} dualMode=${dual} button=${b.recipe.buttonDefault}`,
607
- ` * fonts=${t.faces?.length ?? 0} mode=${mode}`,
608
- ` * Contract: roles.action \u2192 --primary; roles.brand \u2192 --brand-mark (never default CTA)`,
609
- ` */`,
610
- ``,
611
- ...emitFontFaces(t.faces)
612
- ];
613
- const fontSans = cssFontStack(t.fontSans);
614
- const fontDisplay = cssFontStack(t.fontDisplay ?? t.fontSans);
615
- const typeLines = [
616
- ` --font-sans: ${fontSans};`,
617
- ` --font-heading: ${fontDisplay};`,
618
- ` --font-display: ${fontDisplay};`
619
- ];
620
- if (t.fontMono) typeLines.push(` --font-mono: ${cssFontStack(t.fontMono)};`);
621
- if (t.headingWeight != null) {
622
- typeLines.push(` --font-weight-heading: ${t.headingWeight};`);
623
- }
624
- const cats = b.extensions?.categories;
625
- const decorative = b.extensions?.decorative;
626
- const extLines = [];
627
- if (cats) {
628
- for (const [key, value] of Object.entries(cats)) {
629
- const safe = key.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
630
- extLines.push(` --brand-category-${safe}: ${value};`);
631
- }
632
- }
633
- if (decorative) {
634
- for (const [key, value] of Object.entries(decorative)) {
635
- const safe = key.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
636
- extLines.push(` --brand-decorative-${safe}: ${value};`);
637
- }
638
- }
639
- const sharedChrome = [
640
- ``,
641
- ` /* Recipe (action language + free elev/radii) */`,
642
- ...recipeVars(b.recipe),
643
- ``,
644
- ` /* Typography */`,
645
- ...typeLines,
646
- ...motionVars(b.motion),
647
- ...spacingVars(b.spacing),
648
- ...extLines.length ? ["", " /* Taxonomy / decorative (not product CTA) */", ...extLines] : []
649
- ];
650
- if (dual && b.modes?.light?.semantic && b.modes?.dark?.semantic) {
651
- parts.push(emitLightModeSelector(b.id, mode));
652
- parts.push(
653
- ...emitColorVars(b.modes.light.semantic, b.modes.light.roles),
654
- ...sharedChrome,
655
- `}`,
656
- ``
657
- );
658
- parts.push(emitDarkModeSelector(b.id, mode));
659
- parts.push(...emitColorVars(b.modes.dark.semantic, b.modes.dark.roles), `}`, ``);
660
- } else {
661
- const selector = mode === "scoped" ? `html[data-brand="${b.id}"] {` : emitGlobalSkinSelector(b.id, b.darkDefault);
662
- parts.push(selector);
663
- parts.push(...emitColorVars(b.semantic, b.roles), ...sharedChrome, `}`, ``);
664
- }
665
- return parts.join("\n");
666
- }
667
-
668
- // src/brand-package/apply-brand.ts
669
- var BRAND_STYLE_ELEMENT_ID = "nebutra-brand-skin";
670
- var BRAND_STORAGE_KEY = "nebutra-brand-package";
671
- function targetDoc(doc) {
672
- if (doc) return doc;
673
- if (typeof document === "undefined") return null;
674
- return document;
675
- }
676
- function applyBrandCss(css, brandId, options = {}) {
677
- const d = targetDoc(options.doc);
678
- if (!d) return;
679
- let el = d.getElementById(BRAND_STYLE_ELEMENT_ID);
680
- if (!el) {
681
- el = d.createElement("style");
682
- el.id = BRAND_STYLE_ELEMENT_ID;
683
- el.setAttribute("data-nebutra-brand", brandId ?? "custom");
684
- d.head.appendChild(el);
685
- }
686
- el.textContent = css;
687
- if (brandId) {
688
- d.documentElement.dataset.brand = brandId;
689
- }
690
- }
691
- function applyBrandPackage(brand, options = {}) {
692
- const css = emitBrandCss(brand);
693
- applyBrandCss(css, brand.id, options);
694
- if (options.persist && typeof localStorage !== "undefined") {
695
- try {
696
- localStorage.setItem(BRAND_STORAGE_KEY, JSON.stringify(brand));
697
- } catch {
698
- }
699
- }
700
- }
701
- function clearBrand(options = {}) {
702
- const d = targetDoc(options.doc);
703
- if (!d) return;
704
- d.getElementById(BRAND_STYLE_ELEMENT_ID)?.remove();
705
- delete d.documentElement.dataset.brand;
706
- if (options.persist && typeof localStorage !== "undefined") {
707
- try {
708
- localStorage.removeItem(BRAND_STORAGE_KEY);
709
- } catch {
710
- }
711
- }
712
- }
713
- function restorePersistedBrand(options = {}) {
714
- if (typeof localStorage === "undefined") return null;
715
- try {
716
- const raw = localStorage.getItem(BRAND_STORAGE_KEY);
717
- if (!raw) return null;
718
- const brand = JSON.parse(raw);
719
- if (!brand?.id || !brand?.semantic || !brand?.recipe) return null;
720
- applyBrandPackage(brand, { ...options, persist: false });
721
- return brand;
722
- } catch {
723
- return null;
724
- }
725
- }
726
- function getActiveBrandId(doc) {
727
- const d = targetDoc(doc);
728
- return d?.documentElement.dataset.brand ?? null;
729
- }
730
-
731
- // src/brand-package/use-brand.ts
732
- function useBrand(options = {}) {
733
- const { autoRestore = false, persist = true } = options;
734
- const [brand, setBrand] = useState(null);
735
- const [brandId, setBrandId] = useState(null);
736
- useEffect(() => {
737
- if (!autoRestore) return;
738
- const restored = restorePersistedBrand({ persist: false });
739
- if (restored) {
740
- setBrand(restored);
741
- setBrandId(restored.id);
742
- } else {
743
- setBrandId(getActiveBrandId());
744
- }
745
- }, [autoRestore]);
746
- const apply = useCallback(
747
- (next) => {
748
- applyBrandPackage(next, { persist });
749
- setBrand(next);
750
- setBrandId(next.id);
751
- },
752
- [persist]
753
- );
754
- const clear = useCallback(() => {
755
- clearBrand({ persist });
756
- setBrand(null);
757
- setBrandId(null);
758
- }, [persist]);
759
- const restore = useCallback(() => {
760
- const restored = restorePersistedBrand({ persist: false });
761
- if (restored) {
762
- setBrand(restored);
763
- setBrandId(restored.id);
764
- }
765
- return restored;
766
- }, []);
767
- return { brand, brandId, apply, clear, restore };
768
- }
769
- function ensureIframeDoc(iframe) {
770
- if (!iframe) return null;
771
- try {
772
- return iframe.contentDocument;
773
- } catch {
774
- return null;
775
- }
776
- }
777
- function injectBaseStyles(doc, hrefs) {
778
- if (!hrefs?.length) return;
779
- for (const href of hrefs) {
780
- const id = `nebutra-base-${hashHref(href)}`;
781
- if (doc.getElementById(id)) continue;
782
- const link = doc.createElement("link");
783
- link.id = id;
784
- link.rel = "stylesheet";
785
- link.href = href;
786
- doc.head.appendChild(link);
787
- }
788
- }
789
- function hashHref(href) {
790
- let h = 0;
791
- for (let i = 0; i < href.length; i++) h = h * 31 + href.charCodeAt(i) | 0;
792
- return Math.abs(h).toString(36);
793
- }
794
- function useBrandIframePreview(options = {}) {
795
- const iframeRef = useRef(null);
796
- const [brand, setBrand] = useState(null);
797
- const optsRef = useRef(options);
798
- optsRef.current = options;
799
- const apply = useCallback((next) => {
800
- const doc = ensureIframeDoc(iframeRef.current);
801
- if (!doc?.documentElement) {
802
- setBrand(next);
803
- return;
804
- }
805
- injectBaseStyles(doc, optsRef.current.baseStylesheetHrefs);
806
- applyBrandPackage(next, { doc, persist: false });
807
- setBrand(next);
808
- optsRef.current.onApplied?.(next);
809
- }, []);
810
- const clear = useCallback(() => {
811
- const doc = ensureIframeDoc(iframeRef.current);
812
- if (doc) clearBrand({ doc, persist: false });
813
- setBrand(null);
814
- optsRef.current.onApplied?.(null);
815
- }, []);
816
- const writePreviewDocument = useCallback((next, bodyHtml = "") => {
817
- const iframe = iframeRef.current;
818
- if (!iframe) return;
819
- const css = emitBrandCss(next);
820
- const links = (optsRef.current.baseStylesheetHrefs ?? []).map((href) => `<link rel="stylesheet" href="${href}" />`).join("\n");
821
- const html = `<!DOCTYPE html>
822
- <html lang="en" data-brand="${next.id}" class="${next.darkDefault ? "dark" : ""}">
823
- <head>
824
- <meta charset="utf-8" />
825
- <meta name="viewport" content="width=device-width, initial-scale=1" />
826
- ${links}
827
- ${optsRef.current.headHtml ?? ""}
828
- <style id="nebutra-brand-skin">${css}</style>
829
- <style>
830
- html, body { margin: 0; min-height: 100%; }
831
- body {
832
- font-family: var(--font-sans, system-ui, sans-serif);
833
- background: hsl(var(--background));
834
- color: hsl(var(--foreground));
835
- }
836
- </style>
837
- </head>
838
- <body class="${optsRef.current.bodyClassName ?? ""} bg-background text-foreground">
839
- ${bodyHtml}
840
- </body>
841
- </html>`;
842
- iframe.srcdoc = html;
843
- setBrand(next);
844
- optsRef.current.onApplied?.(next);
845
- }, []);
846
- return { iframeRef, brand, apply, clear, writePreviewDocument };
847
- }
848
- function applyBrandToIframe(iframe, brand, options = {}) {
849
- const doc = iframe.contentDocument;
850
- if (!doc) return;
851
- injectBaseStyles(doc, options.baseStylesheetHrefs);
852
- applyBrandPackage(brand, { ...options, doc, persist: false });
853
- }
854
-
855
- export {
856
- hexToHslChannels,
857
- colorToHslChannels,
858
- tryHexToHsl,
859
- tryColorToHsl,
860
- elevationPresetToTokens,
861
- rolesFromSemantic,
862
- semanticFromRoles,
863
- normalizeModePalette,
864
- normalizeBrandPackage,
865
- isDualModeBrand,
866
- emitGlobalSkinSelector,
867
- emitLightModeSelector,
868
- emitDarkModeSelector,
869
- emitBrandCss,
870
- BRAND_STYLE_ELEMENT_ID,
871
- BRAND_STORAGE_KEY,
872
- applyBrandCss,
873
- applyBrandPackage,
874
- clearBrand,
875
- restorePersistedBrand,
876
- getActiveBrandId,
877
- useBrand,
878
- useBrandIframePreview,
879
- applyBrandToIframe
880
- };
881
- //# sourceMappingURL=chunk-ALJTP5HL.js.map