@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,1582 +0,0 @@
1
- import {
2
- elevationPresetToTokens,
3
- emitBrandCss,
4
- normalizeBrandPackage,
5
- tryHexToHsl
6
- } from "./chunk-ALJTP5HL.js";
7
-
8
- // src/brand-package/compile-helpers.ts
9
- function leafHex(tree, path) {
10
- let cur = tree;
11
- for (const p of path) {
12
- if (!cur || typeof cur !== "object") return void 0;
13
- cur = cur[p];
14
- }
15
- if (!cur || typeof cur !== "object") return void 0;
16
- const v = cur.$value ?? cur.value;
17
- return typeof v === "string" ? v : void 0;
18
- }
19
- function detectPreset(idHint, colors) {
20
- const id = idHint.toLowerCase();
21
- if (id.includes("linear")) return "linear";
22
- if (id.includes("gsap")) return "gsap";
23
- if (id.includes("raycast")) return "raycast";
24
- if (id.includes("vercel")) return "vercel";
25
- if (id.includes("vanta")) return "vanta";
26
- if (id.includes("stripe")) return "stripe";
27
- if (id.includes("notion")) return "notion";
28
- if (colors["paper-white"] && colors.obsidian && colors.hairline) return "vercel";
29
- if (colors["coral-pulse"] || colors["void-black"] && colors.mist && colors.ink) {
30
- return "raycast";
31
- }
32
- if (colors["acid-lime"] || colors.void) return "linear";
33
- if (colors["shockingly-green"] || colors["surface-cream"] || colors["just-black"]) return "gsap";
34
- if (colors["just-black"] && colors["surface-cream"]) return "gsap";
35
- if ((colors["notion-blue"] || colors["paper-warmth"]) && (colors["paper-warmth"] || colors["ink-black"]) && (colors["sky-tint"] || colors.marigold || colors.coral)) {
36
- return "notion";
37
- }
38
- if (colors["indigo-ink"] && colors["pure-white"] && (colors.frost || colors["lavender-border"] || colors["midnight-ink"]) && !colors["paper-warmth"]) {
39
- return "stripe";
40
- }
41
- if ((colors["indigo-ink"] || colors["vivid-violet"]) && (colors.parchment || colors["lavender-wash"] || colors.paper) && !colors["pure-white"]) {
42
- return "vanta";
43
- }
44
- return "generic";
45
- }
46
- function pickUiFontFamily(font) {
47
- const preferUi = /(inter|geist|manrope|dm sans|sans|ui)/i;
48
- const avoidDisplay = /(reckless|serif|display|editorial|playfair|lora|source serif)/i;
49
- const entries = Object.entries(font);
50
- for (const [k, v] of entries) {
51
- if (!v || typeof v !== "object") continue;
52
- const name = String(v.$value ?? v.value ?? "");
53
- if (!name || avoidDisplay.test(k) || avoidDisplay.test(name)) continue;
54
- if (preferUi.test(k) || preferUi.test(name) || entries.length === 1) return name;
55
- }
56
- for (const [k, v] of entries) {
57
- if (!v || typeof v !== "object") continue;
58
- const name = String(v.$value ?? v.value ?? "");
59
- if (name && !avoidDisplay.test(k) && !avoidDisplay.test(name)) return name;
60
- }
61
- return void 0;
62
- }
63
- function pickDisplayFontFamily(font) {
64
- const prefer = /(reckless|serif|display|editorial|playfair|lora|source serif|mori)/i;
65
- for (const [k, v] of Object.entries(font)) {
66
- if (!v || typeof v !== "object") continue;
67
- const name = String(v.$value ?? v.value ?? "");
68
- if (name && (prefer.test(k) || prefer.test(name))) return name;
69
- }
70
- return void 0;
71
- }
72
- var COLOR_KEY_ALIASES = {
73
- background: ["background", "canvas", "page-canvas"],
74
- foreground: ["foreground", "ink", "ink-black"],
75
- card: ["card", "card-surface", "surface"],
76
- "card-foreground": ["card-foreground"],
77
- cardForeground: ["card-foreground"],
78
- primary: ["primary", "action"],
79
- "primary-foreground": ["primary-foreground"],
80
- primaryForeground: ["primary-foreground"],
81
- secondary: ["secondary", "quiet"],
82
- "secondary-foreground": ["secondary-foreground"],
83
- secondaryForeground: ["secondary-foreground"],
84
- muted: ["muted"],
85
- "muted-foreground": ["muted-foreground", "mutedForeground", "stone"],
86
- mutedForeground: ["muted-foreground", "stone"],
87
- accent: ["accent"],
88
- "accent-foreground": ["accent-foreground"],
89
- accentForeground: ["accent-foreground"],
90
- border: ["border", "hairline"],
91
- input: ["input"],
92
- ring: ["ring"],
93
- destructive: ["destructive"],
94
- "destructive-foreground": ["destructive-foreground"],
95
- destructiveForeground: ["destructive-foreground"],
96
- success: ["success"],
97
- popover: ["popover"],
98
- "popover-foreground": ["popover-foreground"],
99
- popoverForeground: ["popover-foreground"]
100
- };
101
- function isUsableColorValue(val) {
102
- const t = val.trim();
103
- if (!t) return false;
104
- if (t.startsWith("#")) return true;
105
- if (/^\d+(\.\d+)?\s+\d+(\.\d+)?%\s+\d+(\.\d+)?%$/.test(t)) return true;
106
- if (/^hsla?\(/i.test(t) || /^rgba?\(/i.test(t)) return true;
107
- return false;
108
- }
109
- function collectColors(colorRoot) {
110
- const raw = {};
111
- if (!colorRoot || typeof colorRoot !== "object") return raw;
112
- for (const [k, v] of Object.entries(colorRoot)) {
113
- if (!v || typeof v !== "object") continue;
114
- const val = v.$value ?? v.value;
115
- if (typeof val === "string" && isUsableColorValue(val)) raw[k] = val.trim();
116
- }
117
- const out = { ...raw };
118
- for (const [key, value] of Object.entries(raw)) {
119
- const aliases = COLOR_KEY_ALIASES[key];
120
- if (!aliases) continue;
121
- for (const alias of aliases) {
122
- if (!out[alias]) out[alias] = value;
123
- }
124
- }
125
- return out;
126
- }
127
- function collectSurfaces(surfaceRoot) {
128
- return collectColors(surfaceRoot);
129
- }
130
-
131
- // src/brand-package/infer-recipe.ts
132
- function inferRecipeFromDesignMd(designMd) {
133
- const t = designMd.toLowerCase();
134
- const notes = [];
135
- const hints = { notes };
136
- const outlineFirst = t.includes("outlined-only") || t.includes("outline-only") || t.includes("ghost pill") || t.includes("ghost-pill") || t.includes("no filled") || t.includes("don't add filled") || t.includes("do not add filled") || t.includes("never fill") || t.includes("outlined") && t.includes("button") && !t.includes("filled cta");
137
- const gradientStroke = t.includes("gradient-stroked") || t.includes("gradient stroke") || t.includes("gradient-stroked cta") || t.includes("border-image") || t.includes("gradient border") && (t.includes("cta") || t.includes("button"));
138
- const neutralFilled = t.includes("no chromatic") || t.includes("deliberately neutral") || t.includes("neutral rather than chromatic") || t.includes("no chromatic action") || t.includes("don't use chromatic action") || t.includes("do not use chromatic action") || t.includes("mist") && t.includes("filled") && (t.includes("iron") || t.includes("neutral"));
139
- const solidCta = neutralFilled || t.includes("filled") && (t.includes("primary") || t.includes("cta") || t.includes("download") || t.includes("action button") || t.includes("primary action") || t.includes("filled button"));
140
- if (gradientStroke) {
141
- hints.buttonDefault = "gradient-stroke";
142
- notes.push("DESIGN.md: gradient-stroke CTA");
143
- } else if (outlineFirst && !solidCta) {
144
- hints.buttonDefault = "outline";
145
- notes.push("DESIGN.md: outline-first controls");
146
- } else if (solidCta) {
147
- hints.buttonDefault = "solid";
148
- if (neutralFilled) notes.push("DESIGN.md: neutral filled CTA (not chromatic)");
149
- }
150
- const forbidsAnyShadow = t.includes("avoids shadows") || t.includes("avoid shadows") || t.includes("avoids shadows entirely") || t.includes("no card has a box-shadow") || t.includes("no button has a shadow") || t.includes("never from box-shadow") || t.includes("never from elevation") || t.includes("depth comes from background tint") || t.includes("depth comes from background") || t.includes("no shadows, blurs") || t.includes("do not use shadows") || t.includes("don't use shadows") || t.includes("rejects drop shadows") || t.includes("reject drop shadows") || t.includes("don't drop shadows") || t.includes("do not drop shadows") || t.includes("don't apply drop shadows") || t.includes("do not apply drop shadows") || t.includes("don't add drop-shadows") || t.includes("do not add drop-shadows") || t.includes("never use drop-shadows") || t.includes("no drop shadow") || t.includes("no box-shadow") || t.includes("never via box-shadow") || t.includes("border is the elevation") || t.includes("no shadow \u2014 the border") || t.includes("no shadow - the border") || t.includes("depth is communicated only") || t.includes("no shadow") && t.includes("border") || t.includes("flat") && t.includes("1px") && t.includes("border");
151
- const keyElev = t.includes("keyboard key") || t.includes("key shadow") || t.includes("key cap") || t.includes("inset top") && t.includes("highlight") && t.includes("shadow");
152
- const hairlineRingElev = !forbidsAnyShadow && (t.includes("stacked box-shadow") || t.includes("double-ring") || t.includes("build depth with hairline") || t.includes("hairline") && t.includes("box-shadow") && (t.includes("never with drop-shadow") || t.includes("never use drop-shadow") || t.includes("not with drop-shadow")));
153
- if (keyElev) {
154
- hints.elevationPreset = "key";
155
- notes.push("DESIGN.md: key/inset elevation");
156
- } else if (forbidsAnyShadow) {
157
- hints.elevationPreset = "none";
158
- notes.push("DESIGN.md: elevation=none (no box-shadow / tint+border depth)");
159
- } else if (hairlineRingElev) {
160
- hints.elevationPreset = "hairline";
161
- notes.push("DESIGN.md: hairline ring elevation");
162
- }
163
- if (/\*\*density:\*\*\s*comfortable|density:\s*comfortable|\bdensity\b[^\n]{0,20}comfortable/i.test(
164
- designMd
165
- )) {
166
- hints.density = "comfortable";
167
- } else if (/\*\*density:\*\*\s*spacious|density:\s*spacious/i.test(designMd)) {
168
- hints.density = "spacious";
169
- } else if (/\*\*density:\*\*\s*compact|density:\s*compact/i.test(designMd)) {
170
- hints.density = "compact";
171
- } else if ((t.includes("compact density") || t.includes("8\u201312px") || t.includes("8-12px")) && !t.includes("comfortable")) {
172
- hints.density = "compact";
173
- } else if (t.includes("comfortable") || t.includes("spacious")) {
174
- hints.density = t.includes("spacious") ? "spacious" : "comfortable";
175
- }
176
- const tableButton = designMd.match(/\|\s*buttons\s*\|\s*(\d+px|9999?px)\s*\|/i);
177
- const tableCards = designMd.match(/\|\s*cards\s*\|\s*(\d+px|9999?px)\s*\|/i);
178
- const tableButtonRadius = tableButton?.[1];
179
- const tableCardsRadius = tableCards?.[1];
180
- const radii = {};
181
- if (tableButtonRadius) {
182
- radii.button = tableButtonRadius;
183
- notes.push(`DESIGN.md: table buttons radius ${tableButtonRadius}`);
184
- } else if (t.includes("4px border-radius on all buttons") || t.includes("use 4px border-radius on all") || t.includes("never pill") && t.includes("4px") && t.includes("button")) {
185
- radii.button = "4px";
186
- notes.push("DESIGN.md: 4px control radius (not pill)");
187
- } else if ((t.includes("999px") || t.includes("9999px")) && (t.includes("button") || t.includes("pill-shaped")) && !t.includes("never pill") && !t.includes("not pill") && !t.includes("pills only") || t.includes("pill-shaped") && t.includes("button")) {
188
- radii.button = t.includes("9999px") ? "9999px" : "999px";
189
- notes.push("DESIGN.md: full pill control radius");
190
- } else if (/\bbuttons?\b[^\n.|]{0,48}\b8px\b/.test(t) || t.includes("8px for buttons")) {
191
- radii.button = "8px";
192
- } else if (/\bbuttons?\b[^\n.|]{0,48}\b6px\b/.test(t) || t.includes("button radius to 6px")) {
193
- radii.button = "6px";
194
- } else if (t.includes("100px") && (t.includes("button") || t.includes("pill button"))) {
195
- radii.button = "100px";
196
- }
197
- if (tableCardsRadius) {
198
- radii.card = tableCardsRadius;
199
- notes.push(`DESIGN.md: table cards radius ${tableCardsRadius}`);
200
- }
201
- if (radii.button != null || radii.card != null) {
202
- hints.radii = radii;
203
- }
204
- return hints;
205
- }
206
-
207
- // src/brand-package/presets/recipe.ts
208
- function buildRecipe(input) {
209
- const button = input.radii?.button ?? "0.375rem";
210
- const card = input.radii?.card ?? "0.75rem";
211
- const badge = input.radii?.badge ?? "9999px";
212
- const radii = {
213
- button,
214
- card,
215
- badge,
216
- input: input.radii?.input ?? button,
217
- pill: input.radii?.pill ?? "9999px"
218
- };
219
- const elevationPreset = input.elevationPreset ?? "soft";
220
- const elevationTokens = input.elevationTokens ?? elevationPresetToTokens(elevationPreset, input.cardShadow);
221
- const recipe = {
222
- buttonDefault: input.buttonDefault,
223
- density: input.density ?? "comfortable",
224
- radii,
225
- elevationTokens
226
- };
227
- if (input.badgeDefault) recipe.badgeDefault = input.badgeDefault;
228
- if (input.primaryStrokeGradient) recipe.primaryStrokeGradient = input.primaryStrokeGradient;
229
- if (input.outlineBorder) recipe.outlineBorder = input.outlineBorder;
230
- return recipe;
231
- }
232
-
233
- // src/brand-package/presets/generic.ts
234
- function buildGeneric(ctx) {
235
- ctx.warnings.push(
236
- "Unknown brand layout \u2014 compiled with heuristic recipe. Review mapping in Create Center."
237
- );
238
- ctx.warnings.push(...ctx.recipeHints.notes);
239
- const entries = Object.entries(ctx.colors);
240
- const pick = (...keys) => {
241
- for (const k of keys) {
242
- if (ctx.colors[k]) return ctx.colors[k];
243
- }
244
- return void 0;
245
- };
246
- const bg = pick(
247
- "paper-warmth",
248
- "paper-white",
249
- "page-canvas",
250
- "parchment",
251
- "pure-white",
252
- "paper",
253
- "background",
254
- "canvas",
255
- "void-black",
256
- "void",
257
- "just-black",
258
- "off-black"
259
- ) ?? "#0a0a0a";
260
- const isLightCanvas = (() => {
261
- try {
262
- const m = tryHexToHsl(bg, "0 0% 4%").match(/(\d+)%\s*$/);
263
- return m ? Number(m[1]) >= 50 : false;
264
- } catch {
265
- return false;
266
- }
267
- })();
268
- const fg = isLightCanvas ? pick(
269
- "ink-black",
270
- "carbon",
271
- "charcoal",
272
- "obsidian",
273
- "foreground",
274
- "ink",
275
- "midnight-ink",
276
- "deep-violet",
277
- "slate"
278
- ) ?? "#181822" : pick("pure-white", "paper", "surface-cream", "bone", "mist", "foreground", "white") ?? "#ffffff";
279
- const primary = pick(
280
- "notion-blue",
281
- "indigo-ink",
282
- "vivid-violet",
283
- "primary",
284
- "acid-lime",
285
- "obsidian",
286
- "shockingly-green",
287
- "brand",
288
- "accent",
289
- "mid-violet",
290
- "amethyst-edge",
291
- "signal-blue"
292
- ) ?? entries.find(
293
- ([k]) => !/void|black|canvas|graphite|paper|hairline|ash|parchment|fog|lavender|steel|slate|carbon|mist|frost|smoke|white|midnight|warmth|tint|wash|marigold|coral|saffron|mocha|vermillion|sky/i.test(
294
- k
295
- )
296
- )?.[1] ?? (isLightCanvas ? "#171717" : "#3b82f6");
297
- const brandMarkHex = pick("ink-black", "coral-pulse", "brand-mark", "logo", "wordmark", "charcoal", "deep-violet") ?? void 0;
298
- const border = isLightCanvas ? pick("frost", "hairline", "border", "ash", "carbon", "lilac-border", "slate") ?? "#e5e5e5" : pick("hairline", "border", "slate", "graphite", "surface-25", "smoke") ?? "#333333";
299
- const mutedFg = pick(
300
- "stone",
301
- "charcoal",
302
- "graphite",
303
- "steel",
304
- "slate",
305
- "muted",
306
- "smoke",
307
- "ash",
308
- "fog",
309
- "surface-50"
310
- ) ?? "#888888";
311
- const card = isLightCanvas ? pick("pure-white", "paper", "card-surface", "card") ?? "#ffffff" : pick("ink", "carbon", "off-black", "obsidian", "card") ?? bg;
312
- const quiet = isLightCanvas ? pick("sky-tint", "periwinkle-wash", "lavender-wash", "mist", "fog", "ash", "secondary") ?? border : pick("graphite", "obsidian", "smoke", "secondary") ?? border;
313
- const uiFont = pickUiFontFamily(ctx.font) ?? "Inter";
314
- const displayFont = pickDisplayFontFamily(ctx.font);
315
- let elevationPreset = ctx.recipeHints.elevationPreset ?? "soft";
316
- if (elevationPreset === "key" && isLightCanvas) elevationPreset = "hairline";
317
- const brand = {
318
- id: ctx.id,
319
- name: ctx.siteName,
320
- darkDefault: !isLightCanvas,
321
- version: "0.1.0",
322
- semantic: isLightCanvas ? {
323
- background: tryHexToHsl(bg, "0 0% 98%"),
324
- foreground: tryHexToHsl(fg, "0 0% 9%"),
325
- card: tryHexToHsl(card, "0 0% 100%"),
326
- cardForeground: tryHexToHsl(fg, "0 0% 9%"),
327
- popover: tryHexToHsl(card, "0 0% 100%"),
328
- popoverForeground: tryHexToHsl(fg, "0 0% 9%"),
329
- primary: tryHexToHsl(primary, "0 0% 9%"),
330
- primaryForeground: tryHexToHsl(card, "0 0% 100%"),
331
- secondary: tryHexToHsl(quiet, "0 0% 92%"),
332
- secondaryForeground: tryHexToHsl(fg, "0 0% 9%"),
333
- muted: tryHexToHsl(quiet, "0 0% 92%"),
334
- mutedForeground: tryHexToHsl(mutedFg, "0 0% 40%"),
335
- accent: tryHexToHsl(quiet, "0 0% 92%"),
336
- accentForeground: tryHexToHsl(fg, "0 0% 9%"),
337
- destructive: "0 72% 51%",
338
- destructiveForeground: "0 0% 100%",
339
- border: tryHexToHsl(border, "0 0% 20%"),
340
- input: tryHexToHsl(card, "0 0% 100%"),
341
- ring: tryHexToHsl(primary, "0 0% 9%")
342
- } : {
343
- background: tryHexToHsl(bg, "0 0% 4%"),
344
- foreground: tryHexToHsl(fg, "0 0% 98%"),
345
- card: tryHexToHsl(card, "0 0% 8%"),
346
- cardForeground: tryHexToHsl(fg, "0 0% 98%"),
347
- popover: tryHexToHsl(card, "0 0% 8%"),
348
- popoverForeground: tryHexToHsl(fg, "0 0% 98%"),
349
- primary: tryHexToHsl(primary, "217 91% 60%"),
350
- primaryForeground: tryHexToHsl(bg, "0 0% 4%"),
351
- secondary: tryHexToHsl(border, "0 0% 20%"),
352
- secondaryForeground: tryHexToHsl(fg, "0 0% 98%"),
353
- muted: tryHexToHsl(card, "0 0% 8%"),
354
- mutedForeground: tryHexToHsl(mutedFg, "0 0% 53%"),
355
- accent: tryHexToHsl(border, "0 0% 20%"),
356
- accentForeground: tryHexToHsl(primary, "217 91% 60%"),
357
- destructive: "0 72% 51%",
358
- destructiveForeground: "0 0% 100%",
359
- border: tryHexToHsl(border, "0 0% 20%"),
360
- input: tryHexToHsl(border, "0 0% 20%"),
361
- ring: tryHexToHsl(primary, "217 91% 60%")
362
- },
363
- recipe: buildRecipe({
364
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
365
- radii: {
366
- button: ctx.recipeHints.radii?.button ?? leafHex(ctx.radius, ["buttons"]) ?? leafHex(ctx.radius, ["md"]) ?? "0.375rem",
367
- card: ctx.recipeHints.radii?.card ?? leafHex(ctx.radius, ["cards"]) ?? leafHex(ctx.radius, ["xl"]) ?? leafHex(ctx.radius, ["lg"]) ?? "0.75rem",
368
- badge: leafHex(ctx.radius, ["pills"]) ?? leafHex(ctx.radius, ["badges"]) ?? "9999px",
369
- input: leafHex(ctx.radius, ["inputs"]) ?? ctx.recipeHints.radii?.button ?? "0.375rem"
370
- },
371
- elevationPreset,
372
- density: ctx.recipeHints.density ?? "comfortable",
373
- outlineBorder: isLightCanvas ? border : fg,
374
- badgeDefault: brandMarkHex ? "muted" : "match-action"
375
- }),
376
- typography: {
377
- fontSans: `'${uiFont}', ui-sans-serif, system-ui, sans-serif`,
378
- ...displayFont ? { fontDisplay: `'${displayFont}', ui-serif, Georgia, serif` } : {},
379
- headingWeight: isLightCanvas ? 500 : 600
380
- },
381
- extensions: {
382
- ...typeof ctx.refero.url === "string" ? { sourceUrl: ctx.refero.url } : {},
383
- ...brandMarkHex ? { categories: { brand: brandMarkHex } } : {},
384
- notes: [
385
- "Generic compile \u2014 verify primary + buttonDefault in Create Center.",
386
- ...brandMarkHex ? ["Detected separate brand-mark color (categories.brand \u2192 --brand-mark)."] : []
387
- ]
388
- }
389
- };
390
- return brand;
391
- }
392
-
393
- // src/brand-package/presets/gsap.ts
394
- function buildGsap(ctx) {
395
- const canvas = ctx.colors["just-black"] ?? ctx.colors.canvas ?? "#0e100f";
396
- const cream = ctx.colors["surface-cream"] ?? ctx.colors["cream-surface"] ?? "#fffce1";
397
- const muted = ctx.colors["surface-50"] ?? "#7c7c6f";
398
- const hairline = ctx.colors["surface-25"] ?? "#42433d";
399
- const nested = ctx.colors["off-black"] ?? ctx.colors["nested-panel"] ?? "#191919";
400
- const green = ctx.colors["shockingly-green"] ?? "#0ae448";
401
- ctx.warnings.push(
402
- "GSAP: shockingly-green is accent/link only \u2014 buttonDefault=outline (no solid green fill)."
403
- );
404
- const buttonDefault = ctx.recipeHints.buttonDefault ?? "gradient-stroke";
405
- const brand = {
406
- id: "gsap",
407
- name: "GSAP",
408
- darkDefault: true,
409
- version: "1.0.0",
410
- semantic: {
411
- // Primary for *links/accents* — filled solid CTAs are disabled by recipe
412
- background: tryHexToHsl(canvas, "150 8% 6%"),
413
- foreground: tryHexToHsl(cream, "54 100% 94%"),
414
- card: tryHexToHsl(nested, "0 0% 10%"),
415
- cardForeground: tryHexToHsl(cream, "54 100% 94%"),
416
- popover: tryHexToHsl(nested, "0 0% 10%"),
417
- popoverForeground: tryHexToHsl(cream, "54 100% 94%"),
418
- primary: tryHexToHsl(green, "136 91% 47%"),
419
- primaryForeground: tryHexToHsl(canvas, "150 8% 6%"),
420
- secondary: tryHexToHsl(hairline, "60 5% 25%"),
421
- secondaryForeground: tryHexToHsl(cream, "54 100% 94%"),
422
- muted: tryHexToHsl(nested, "0 0% 10%"),
423
- mutedForeground: tryHexToHsl(muted, "60 6% 46%"),
424
- accent: tryHexToHsl(hairline, "60 5% 25%"),
425
- accentForeground: tryHexToHsl(green, "136 91% 47%"),
426
- destructive: tryHexToHsl(ctx.colors["lipstick-pink"] ?? "#f100cb", "310 100% 47%"),
427
- destructiveForeground: tryHexToHsl(cream, "54 100% 94%"),
428
- border: tryHexToHsl(hairline, "60 5% 25%"),
429
- input: tryHexToHsl(hairline, "60 5% 25%"),
430
- ring: tryHexToHsl(green, "136 91% 47%"),
431
- info: tryHexToHsl(ctx.colors.blue ?? "#00bae2", "191 100% 44%"),
432
- infoForeground: tryHexToHsl(canvas, "150 8% 6%"),
433
- success: tryHexToHsl(green, "136 91% 47%"),
434
- successForeground: tryHexToHsl(canvas, "150 8% 6%")
435
- },
436
- recipe: buildRecipe({
437
- buttonDefault,
438
- radii: {
439
- button: ctx.recipeHints.radii?.button ?? leafHex(ctx.radius, ["full"]) ?? "100px",
440
- card: leafHex(ctx.radius, ["lg"]) ?? "8px"
441
- },
442
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
443
- density: ctx.recipeHints.density ?? "comfortable",
444
- outlineBorder: cream,
445
- primaryStrokeGradient: "linear-gradient(114.41deg, #0ae448 20.74%, #abff84 65.5%)"
446
- }),
447
- // Performs. This is the one language where motion is the message, so the
448
- // ramp is long enough to be watched and the spring is allowed to overshoot
449
- // well past its resting state.
450
- motion: {
451
- easeOut: "cubic-bezier(0.22, 1, 0.36, 1)",
452
- easeInOut: "cubic-bezier(0.65, 0, 0.35, 1)",
453
- easeSpring: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
454
- micro: 120,
455
- flow: 250,
456
- reveal: 400,
457
- cinematic: 700
458
- },
459
- // Room for the performance to land — more air than the product-chrome trio.
460
- spacing: {
461
- xs: "0.5rem",
462
- sm: "0.875rem",
463
- md: "1.25rem",
464
- lg: "1.75rem",
465
- xl: "2.5rem",
466
- "2xl": "3.5rem"
467
- },
468
- typography: {
469
- fontSans: `'Mori', 'Inter Tight', 'DM Sans', ui-sans-serif, system-ui, sans-serif`,
470
- fontDisplay: `'Mori', 'Inter Tight', ui-sans-serif, system-ui, sans-serif`,
471
- headingWeight: 600,
472
- faces: [
473
- {
474
- family: "Mori",
475
- // Placeholder — Create Center replaces with tenant-uploaded WOFF2
476
- src: [{ url: "/brand-assets/mori-regular.woff2", format: "woff2" }],
477
- weight: 400,
478
- display: "swap"
479
- },
480
- {
481
- family: "Mori",
482
- src: [{ url: "/brand-assets/mori-semibold.woff2", format: "woff2" }],
483
- weight: 600,
484
- display: "swap"
485
- }
486
- ]
487
- },
488
- extensions: {
489
- categories: {
490
- gsap: green,
491
- scroll: ctx.colors.pink ?? "#fec5fb",
492
- svg: ctx.colors.orangey ?? "#ff8709",
493
- text: ctx.colors.lilac ?? "#9d95ff",
494
- ui: ctx.colors.blue ?? "#00bae2",
495
- other: ctx.colors["light-green"] ?? "#abff84"
496
- },
497
- displaySizePx: 224,
498
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://gsap.com",
499
- notes: [
500
- "Outline-first product controls; category ctx.colors are marketing extensions.",
501
- "Replace typography.faces[].src with Create Center hosted ctx.font URLs."
502
- ]
503
- }
504
- };
505
- return brand;
506
- }
507
-
508
- // src/brand-package/presets/linear.ts
509
- function linearDarkSemantic(voidC, carbon, graphite, ash, paper, lime, coral, pulse, colors) {
510
- return {
511
- background: tryHexToHsl(voidC, "210 11% 4%"),
512
- foreground: tryHexToHsl(paper, "0 0% 100%"),
513
- card: tryHexToHsl(carbon, "210 6% 6%"),
514
- cardForeground: tryHexToHsl(paper, "0 0% 100%"),
515
- popover: tryHexToHsl(colors.obsidian ?? "#161718", "210 5% 9%"),
516
- popoverForeground: tryHexToHsl(paper, "0 0% 100%"),
517
- primary: tryHexToHsl(lime, "66 89% 54%"),
518
- primaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
519
- secondary: tryHexToHsl(graphite, "220 7% 15%"),
520
- secondaryForeground: tryHexToHsl(colors.mist ?? "#d0d6e0", "220 20% 85%"),
521
- muted: tryHexToHsl(colors.obsidian ?? "#161718", "210 5% 9%"),
522
- mutedForeground: tryHexToHsl(ash, "220 5% 41%"),
523
- accent: tryHexToHsl(graphite, "220 7% 15%"),
524
- accentForeground: tryHexToHsl(lime, "66 89% 54%"),
525
- destructive: tryHexToHsl(coral, "0 79% 63%"),
526
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
527
- border: tryHexToHsl(graphite, "220 7% 15%"),
528
- input: tryHexToHsl(graphite, "220 7% 15%"),
529
- ring: tryHexToHsl(lime, "66 89% 54%"),
530
- success: tryHexToHsl(pulse, "136 61% 40%"),
531
- successForeground: tryHexToHsl(paper, "0 0% 100%"),
532
- info: tryHexToHsl(colors["signal-teal"] ?? "#02b8cc", "187 98% 40%"),
533
- infoForeground: tryHexToHsl(paper, "0 0% 100%")
534
- };
535
- }
536
- function linearLightSemantic(voidC, ash, paper, lime, coral, pulse, colors) {
537
- return {
538
- background: "0 0% 100%",
539
- foreground: tryHexToHsl(voidC, "210 11% 4%"),
540
- card: "0 0% 98%",
541
- cardForeground: tryHexToHsl(voidC, "210 11% 4%"),
542
- popover: "0 0% 100%",
543
- popoverForeground: tryHexToHsl(voidC, "210 11% 4%"),
544
- primary: tryHexToHsl(lime, "66 89% 54%"),
545
- primaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
546
- secondary: "220 14% 96%",
547
- secondaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
548
- muted: "220 14% 96%",
549
- mutedForeground: tryHexToHsl(ash, "220 5% 41%"),
550
- accent: "220 14% 96%",
551
- accentForeground: tryHexToHsl(voidC, "210 11% 4%"),
552
- destructive: tryHexToHsl(coral, "0 79% 63%"),
553
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
554
- border: "220 13% 91%",
555
- input: "0 0% 100%",
556
- ring: tryHexToHsl(lime, "66 89% 54%"),
557
- success: tryHexToHsl(pulse, "136 61% 40%"),
558
- successForeground: tryHexToHsl(paper, "0 0% 100%"),
559
- info: tryHexToHsl(colors["signal-teal"] ?? "#02b8cc", "187 98% 40%"),
560
- infoForeground: tryHexToHsl(paper, "0 0% 100%")
561
- };
562
- }
563
- function buildLinear(ctx) {
564
- const voidC = ctx.colors.void ?? ctx.colors["just-black"] ?? "#08090a";
565
- const carbon = ctx.colors.carbon ?? "#0f1011";
566
- const graphite = ctx.colors.graphite ?? "#23252a";
567
- const ash = ctx.colors.ash ?? "#62666d";
568
- const paper = ctx.colors.paper ?? "#ffffff";
569
- const lime = ctx.colors["acid-lime"] ?? "#e4f222";
570
- const coral = ctx.colors["coral-red"] ?? "#eb5757";
571
- const pulse = ctx.colors["pulse-green"] ?? "#27a644";
572
- const dark = linearDarkSemantic(
573
- voidC,
574
- carbon,
575
- graphite,
576
- ash,
577
- paper,
578
- lime,
579
- coral,
580
- pulse,
581
- ctx.colors
582
- );
583
- const light = linearLightSemantic(voidC, ash, paper, lime, coral, pulse, ctx.colors);
584
- const brand = {
585
- id: "linear",
586
- name: "Linear",
587
- darkDefault: true,
588
- version: "1.0.0",
589
- semantic: dark,
590
- modes: {
591
- dark: { semantic: dark },
592
- light: { semantic: light }
593
- },
594
- recipe: buildRecipe({
595
- buttonDefault: "solid",
596
- radii: { button: "6px", card: "12px" },
597
- elevationPreset: "soft",
598
- density: "compact"
599
- }),
600
- // Resolves before the eye asks it to. Linear's interfaces answer on the
601
- // frame you click; the expo-out curve spends its whole budget decelerating,
602
- // which is what makes a 140ms move read as instant rather than abrupt. No
603
- // spring — nothing in this language overshoots.
604
- motion: {
605
- easeOut: "cubic-bezier(0.16, 1, 0.3, 1)",
606
- easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
607
- micro: 80,
608
- flow: 140,
609
- reveal: 200,
610
- cinematic: 320
611
- },
612
- // Sits close to the content it chrome — the densest of the seven.
613
- spacing: {
614
- xs: "0.375rem",
615
- sm: "0.5rem",
616
- md: "0.75rem",
617
- lg: "1rem",
618
- xl: "1.5rem",
619
- "2xl": "2rem"
620
- },
621
- typography: {
622
- fontSans: `'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif`,
623
- fontMono: `'Berkeley Mono', 'JetBrains Mono', ui-monospace, monospace`,
624
- headingWeight: 510,
625
- faces: [
626
- {
627
- family: "Inter Variable",
628
- src: [
629
- {
630
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
631
- format: "woff2"
632
- }
633
- ],
634
- weight: "100 900",
635
- display: "swap"
636
- }
637
- ]
638
- },
639
- extensions: {
640
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://linear.app",
641
- notes: ["Dual-mode: dark void default + light paper; acid-lime solid CTA in both modes."]
642
- }
643
- };
644
- return brand;
645
- }
646
-
647
- // src/brand-package/presets/notion.ts
648
- function buildNotion(ctx) {
649
- const paper = ctx.colors["paper-warmth"] ?? "#f6f5f4";
650
- const white = ctx.colors["pure-white"] ?? "#ffffff";
651
- const ink = ctx.colors["ink-black"] ?? "#000000";
652
- const charcoal = ctx.colors.charcoal ?? "#111111";
653
- const stone = ctx.colors.stone ?? "#757575";
654
- const graphite = ctx.colors.graphite ?? "#615d59";
655
- const blue = ctx.colors["notion-blue"] ?? "#0075de";
656
- const sky = ctx.colors["sky-tint"] ?? "#e6f3fe";
657
- const marigold = ctx.colors.marigold ?? "#ffb110";
658
- const coral = ctx.colors.coral ?? "#f64932";
659
- const midnight = ctx.colors["midnight-ink"] ?? "#02093a";
660
- const signal = ctx.colors["signal-blue"] ?? "#097fe8";
661
- ctx.warnings.push(
662
- "Notion: notion-blue is the only filled CTA; marigold/coral/midnight are decorative card washes (never default action)."
663
- );
664
- ctx.warnings.push(
665
- "Notion: content cards use 1px hairline + elev=none; soft shadows only for sticky nav / product mockups (raised slot)."
666
- );
667
- const brand = {
668
- id: "notion",
669
- name: "Notion",
670
- darkDefault: false,
671
- version: "1.0.0",
672
- roles: {
673
- canvas: tryHexToHsl(paper, "30 9% 96%"),
674
- canvasForeground: tryHexToHsl(ink, "0 0% 0%"),
675
- surface: tryHexToHsl(white, "0 0% 100%"),
676
- surfaceForeground: tryHexToHsl(ink, "0 0% 0%"),
677
- action: tryHexToHsl(blue, "208 100% 44%"),
678
- actionForeground: tryHexToHsl(white, "0 0% 100%"),
679
- // Logo / wordmark / ink hierarchy — not blue CTA
680
- brand: tryHexToHsl(ink, "0 0% 0%"),
681
- brandForeground: tryHexToHsl(white, "0 0% 100%"),
682
- // Ghost CTA wash
683
- quiet: tryHexToHsl(sky, "206 90% 95%"),
684
- quietForeground: tryHexToHsl(blue, "208 100% 44%"),
685
- muted: tryHexToHsl(sky, "206 90% 95%"),
686
- mutedForeground: tryHexToHsl(stone, "0 0% 46%"),
687
- // Approx hairline rgba(0,0,0,0.08) on warm paper
688
- border: "30 5% 88%",
689
- input: tryHexToHsl(white, "0 0% 100%"),
690
- ring: tryHexToHsl(blue, "208 100% 44%"),
691
- destructive: tryHexToHsl(coral, "6 91% 58%"),
692
- destructiveForeground: tryHexToHsl(white, "0 0% 100%"),
693
- warning: tryHexToHsl(marigold, "40 100% 53%"),
694
- warningForeground: tryHexToHsl(ink, "0 0% 0%"),
695
- info: tryHexToHsl(signal, "207 93% 47%"),
696
- infoForeground: tryHexToHsl(white, "0 0% 100%")
697
- },
698
- semantic: {
699
- background: tryHexToHsl(paper, "30 9% 96%"),
700
- foreground: tryHexToHsl(ink, "0 0% 0%"),
701
- card: tryHexToHsl(white, "0 0% 100%"),
702
- cardForeground: tryHexToHsl(ink, "0 0% 0%"),
703
- popover: tryHexToHsl(white, "0 0% 100%"),
704
- popoverForeground: tryHexToHsl(ink, "0 0% 0%"),
705
- primary: tryHexToHsl(blue, "208 100% 44%"),
706
- primaryForeground: tryHexToHsl(white, "0 0% 100%"),
707
- secondary: tryHexToHsl(sky, "206 90% 95%"),
708
- secondaryForeground: tryHexToHsl(blue, "208 100% 44%"),
709
- muted: tryHexToHsl(sky, "206 90% 95%"),
710
- mutedForeground: tryHexToHsl(stone, "0 0% 46%"),
711
- accent: tryHexToHsl(sky, "206 90% 95%"),
712
- accentForeground: tryHexToHsl(blue, "208 100% 44%"),
713
- destructive: tryHexToHsl(coral, "6 91% 58%"),
714
- destructiveForeground: tryHexToHsl(white, "0 0% 100%"),
715
- border: "30 5% 88%",
716
- input: tryHexToHsl(white, "0 0% 100%"),
717
- ring: tryHexToHsl(blue, "208 100% 44%"),
718
- warning: tryHexToHsl(marigold, "40 100% 53%"),
719
- warningForeground: tryHexToHsl(ink, "0 0% 0%"),
720
- info: tryHexToHsl(signal, "207 93% 47%"),
721
- infoForeground: tryHexToHsl(white, "0 0% 100%")
722
- },
723
- recipe: buildRecipe({
724
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
725
- radii: {
726
- button: ctx.recipeHints.radii?.button ?? leafHex(ctx.radius, ["buttons"]) ?? leafHex(ctx.radius, ["lg"]) ?? "8px",
727
- card: ctx.recipeHints.radii?.card ?? leafHex(ctx.radius, ["cards"]) ?? leafHex(ctx.radius, ["xl"]) ?? "12px",
728
- badge: leafHex(ctx.radius, ["pills"]) ?? leafHex(ctx.radius, ["full"]) ?? "9999px",
729
- input: leafHex(ctx.radius, ["buttons"]) ?? leafHex(ctx.radius, ["lg"]) ?? "8px"
730
- },
731
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
732
- density: ctx.recipeHints.density ?? "comfortable",
733
- badgeDefault: "muted",
734
- outlineBorder: ink,
735
- // Sticky nav soft shadow lives in raised; cards stay flat
736
- elevationTokens: {
737
- card: "0 0 #0000",
738
- control: "0 0 #0000",
739
- raised: "0px 0.7px 1.462px 0px rgb(0 0 0 / 0.015), 0px 3px 9px 0px rgb(0 0 0 / 0.03)"
740
- }
741
- }),
742
- // Settles rather than snaps. A document surface is read, not operated, so
743
- // motion here is slow enough to follow with the eye and never competes with
744
- // the text it is moving.
745
- motion: {
746
- easeOut: "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
747
- easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
748
- micro: 120,
749
- flow: 200,
750
- reveal: 280,
751
- cinematic: 460
752
- },
753
- // Editorial — a document wants margin, not chrome density.
754
- spacing: {
755
- xs: "0.625rem",
756
- sm: "1rem",
757
- md: "1.5rem",
758
- lg: "2rem",
759
- xl: "2.75rem",
760
- "2xl": "3.75rem"
761
- },
762
- typography: {
763
- fontSans: `'NotionInter', 'Inter', ui-sans-serif, system-ui, sans-serif`,
764
- fontDisplay: `'NotionInter', 'Inter', ui-sans-serif, system-ui, sans-serif`,
765
- headingWeight: 700,
766
- faces: [
767
- {
768
- family: "NotionInter",
769
- src: [
770
- {
771
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
772
- format: "woff2"
773
- }
774
- ],
775
- weight: "100 900",
776
- display: "swap"
777
- },
778
- {
779
- family: "Lyon Text",
780
- src: [{ url: "/brand-assets/lyon-text-regular.woff2", format: "woff2" }],
781
- weight: 400,
782
- display: "swap"
783
- }
784
- ]
785
- },
786
- extensions: {
787
- categories: {
788
- brand: ink,
789
- action: blue,
790
- ghost: sky,
791
- marigold,
792
- coral,
793
- midnight,
794
- charcoal,
795
- graphite
796
- },
797
- decorative: {
798
- marigold,
799
- coral,
800
- saffron: ctx.colors.saffron ?? "#e89d01",
801
- "sky-wash": ctx.colors["sky-wash"] ?? "#62aef0",
802
- midnight
803
- },
804
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://www.notion.com",
805
- notes: [
806
- "roles.action = Notion Blue (only filled CTA). Accent hues are decorative card washes.",
807
- "roles.brand = Ink Black (logo / wordmark / text hierarchy via alpha).",
808
- "Canvas = Paper Warmth; cards = Pure White \u2014 never invert.",
809
- "Card elev=none + hairline border; sticky nav soft shadow \u2192 elevation raised slot.",
810
- "Buttons 8px, cards 12px, pills 9999px.",
811
- "Lyon Text is editorial accent only \u2014 not product chrome UI."
812
- ]
813
- }
814
- };
815
- return brand;
816
- }
817
-
818
- // src/brand-package/presets/raycast.ts
819
- function buildRaycast(ctx) {
820
- const canvas = ctx.colors["void-black"] ?? ctx.colors.canvas ?? "#040506";
821
- const ink = ctx.colors.ink ?? ctx.colors.card ?? "#07080a";
822
- const obsidian = ctx.colors.obsidian ?? ctx.colors.recessed ?? "#111214";
823
- const graphite = ctx.colors.graphite ?? ctx.colors.badge ?? "#1b1c1e";
824
- const smoke = ctx.colors.smoke ?? "#6a6b6c";
825
- const ash = ctx.colors.ash ?? "#9c9c9d";
826
- const mist = ctx.colors.mist ?? "#e6e6e6";
827
- const iron = ctx.colors.iron ?? "#454647";
828
- const slate = ctx.colors.slate ?? "#2f3031";
829
- const paper = ctx.colors["pure-white"] ?? "#ffffff";
830
- const coral = ctx.colors["coral-pulse"] ?? "#ff6363";
831
- const success = ctx.colors["success-green"] ?? "#59d499";
832
- const info = ctx.colors["info-blue"] ?? "#56c2ff";
833
- ctx.warnings.push(
834
- "Raycast: coral-pulse is brand mark only \u2014 primary CTA is Mist/Iron neutral solid."
835
- );
836
- const brand = {
837
- id: "raycast",
838
- name: "Raycast",
839
- darkDefault: true,
840
- version: "1.0.0",
841
- semantic: {
842
- background: tryHexToHsl(canvas, "210 20% 2%"),
843
- foreground: tryHexToHsl(paper, "0 0% 100%"),
844
- card: tryHexToHsl(ink, "220 18% 3%"),
845
- cardForeground: tryHexToHsl(paper, "0 0% 100%"),
846
- popover: tryHexToHsl(ink, "220 18% 3%"),
847
- popoverForeground: tryHexToHsl(paper, "0 0% 100%"),
848
- // Filled CTA = Mist on dark (not coral)
849
- primary: tryHexToHsl(mist, "0 0% 90%"),
850
- primaryForeground: tryHexToHsl(iron, "210 1% 27%"),
851
- secondary: tryHexToHsl(graphite, "220 4% 11%"),
852
- secondaryForeground: tryHexToHsl(paper, "0 0% 100%"),
853
- muted: tryHexToHsl(obsidian, "220 6% 7%"),
854
- mutedForeground: tryHexToHsl(smoke, "240 1% 42%"),
855
- // Coral as accent for brand-adjacent UI (badges that opt-in to accent)
856
- accent: tryHexToHsl(coral, "0 100% 69%"),
857
- accentForeground: tryHexToHsl(paper, "0 0% 100%"),
858
- destructive: "0 72% 51%",
859
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
860
- border: tryHexToHsl(slate, "210 2% 19%"),
861
- input: tryHexToHsl(obsidian, "220 6% 7%"),
862
- ring: tryHexToHsl(ash, "240 1% 61%"),
863
- success: tryHexToHsl(success, "150 58% 59%"),
864
- successForeground: tryHexToHsl(canvas, "210 20% 2%"),
865
- info: tryHexToHsl(info, "200 100% 67%"),
866
- infoForeground: tryHexToHsl(canvas, "210 20% 2%")
867
- },
868
- recipe: buildRecipe({
869
- buttonDefault: "solid",
870
- radii: {
871
- button: ctx.recipeHints.radii?.button ?? "8px",
872
- card: "16px",
873
- badge: "6px",
874
- input: "8px"
875
- },
876
- elevationPreset: ctx.recipeHints.elevationPreset ?? "key",
877
- density: ctx.recipeHints.density ?? "comfortable",
878
- badgeDefault: "muted"
879
- }),
880
- // Native-feeling: quick, with a small overshoot on things that appear. The
881
- // spring is the point — a launcher should feel like it was already there.
882
- motion: {
883
- easeOut: "cubic-bezier(0.16, 1, 0.3, 1)",
884
- easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
885
- easeSpring: "cubic-bezier(0.34, 1.56, 0.64, 1)",
886
- micro: 90,
887
- flow: 160,
888
- reveal: 220,
889
- cinematic: 340
890
- },
891
- // Command-palette density — compact, but not as tight as Linear.
892
- spacing: {
893
- xs: "0.375rem",
894
- sm: "0.5rem",
895
- md: "0.75rem",
896
- lg: "1rem",
897
- xl: "1.375rem",
898
- "2xl": "2rem"
899
- },
900
- typography: {
901
- fontSans: `'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif`,
902
- fontMono: `'Geist Mono', 'GeistMono', ui-monospace, Menlo, monospace`,
903
- fontDisplay: `'Inter', ui-sans-serif, system-ui, sans-serif`,
904
- headingWeight: 400,
905
- faces: [
906
- {
907
- family: "Inter",
908
- src: [
909
- {
910
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
911
- format: "woff2"
912
- }
913
- ],
914
- weight: "100 900",
915
- display: "swap"
916
- }
917
- ]
918
- },
919
- extensions: {
920
- categories: {
921
- brand: coral,
922
- ember: ctx.colors["ember-hush"] ?? "#452324",
923
- sky: ctx.colors["electric-sky"] ?? "#63a1ff"
924
- },
925
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://raycast.com",
926
- notes: [
927
- "Primary CTA = Mist fill + Iron text (neutral solid).",
928
- "Coral Pulse is brand mark only \u2014 do not use for general product chrome CTAs.",
929
- "Cards use elevation=key (keyboard-key inset shadow stack)."
930
- ]
931
- }
932
- };
933
- return brand;
934
- }
935
-
936
- // src/brand-package/presets/stripe.ts
937
- function buildStripe(ctx) {
938
- const white = ctx.colors["pure-white"] ?? "#ffffff";
939
- const mist = ctx.colors.mist ?? "#f8fafd";
940
- const frost = ctx.colors.frost ?? "#e5edf5";
941
- const midnight = ctx.colors["midnight-ink"] ?? "#061b31";
942
- const slate = ctx.colors.slate ?? "#64748d";
943
- const steel = ctx.colors.steel ?? "#50617a";
944
- const indigo = ctx.colors["indigo-ink"] ?? "#533afd";
945
- const indigoHover = ctx.colors["indigo-hover"] ?? "#7389ff";
946
- const lavender = ctx.colors["lavender-border"] ?? "#b9b9f9";
947
- const periwinkle = ctx.colors["periwinkle-wash"] ?? "#e8e9ff";
948
- const deep = ctx.colors["deep-violet"] ?? "#182659";
949
- const smoke = ctx.colors.smoke ?? "#839bc8";
950
- ctx.warnings.push(
951
- "Stripe: indigo-ink is action CTA only; midnight-ink is brand-mark/wordmark (never default CTA fill)."
952
- );
953
- ctx.warnings.push(
954
- "Stripe: elevation=none \u2014 depth via white\u2192mist\u2192frost tints + 1px frost rules, never box-shadow."
955
- );
956
- const brand = {
957
- id: "stripe",
958
- name: "Stripe",
959
- darkDefault: false,
960
- version: "1.0.0",
961
- roles: {
962
- canvas: tryHexToHsl(white, "0 0% 100%"),
963
- canvasForeground: tryHexToHsl(midnight, "208 78% 11%"),
964
- surface: tryHexToHsl(white, "0 0% 100%"),
965
- surfaceForeground: tryHexToHsl(midnight, "208 78% 11%"),
966
- action: tryHexToHsl(indigo, "248 98% 61%"),
967
- actionForeground: tryHexToHsl(white, "0 0% 100%"),
968
- brand: tryHexToHsl(midnight, "208 78% 11%"),
969
- brandForeground: tryHexToHsl(white, "0 0% 100%"),
970
- quiet: tryHexToHsl(periwinkle, "238 100% 95%"),
971
- quietForeground: tryHexToHsl(indigo, "248 98% 61%"),
972
- muted: tryHexToHsl(mist, "210 56% 98%"),
973
- mutedForeground: tryHexToHsl(slate, "215 16% 47%"),
974
- border: tryHexToHsl(frost, "210 36% 93%"),
975
- input: tryHexToHsl(white, "0 0% 100%"),
976
- ring: tryHexToHsl(indigo, "248 98% 61%"),
977
- destructive: "0 72% 51%",
978
- destructiveForeground: tryHexToHsl(white, "0 0% 100%"),
979
- info: tryHexToHsl(indigoHover, "230 100% 73%"),
980
- infoForeground: tryHexToHsl(midnight, "208 78% 11%")
981
- },
982
- semantic: {
983
- background: tryHexToHsl(white, "0 0% 100%"),
984
- foreground: tryHexToHsl(midnight, "208 78% 11%"),
985
- card: tryHexToHsl(white, "0 0% 100%"),
986
- cardForeground: tryHexToHsl(midnight, "208 78% 11%"),
987
- popover: tryHexToHsl(white, "0 0% 100%"),
988
- popoverForeground: tryHexToHsl(midnight, "208 78% 11%"),
989
- primary: tryHexToHsl(indigo, "248 98% 61%"),
990
- primaryForeground: tryHexToHsl(white, "0 0% 100%"),
991
- secondary: tryHexToHsl(periwinkle, "238 100% 95%"),
992
- secondaryForeground: tryHexToHsl(indigo, "248 98% 61%"),
993
- muted: tryHexToHsl(mist, "210 56% 98%"),
994
- mutedForeground: tryHexToHsl(slate, "215 16% 47%"),
995
- accent: tryHexToHsl(indigo, "248 98% 61%"),
996
- accentForeground: tryHexToHsl(white, "0 0% 100%"),
997
- destructive: "0 72% 51%",
998
- destructiveForeground: tryHexToHsl(white, "0 0% 100%"),
999
- border: tryHexToHsl(frost, "210 36% 93%"),
1000
- input: tryHexToHsl(white, "0 0% 100%"),
1001
- ring: tryHexToHsl(indigo, "248 98% 61%"),
1002
- info: tryHexToHsl(indigoHover, "230 100% 73%"),
1003
- infoForeground: tryHexToHsl(midnight, "208 78% 11%")
1004
- },
1005
- recipe: buildRecipe({
1006
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
1007
- radii: {
1008
- button: ctx.recipeHints.radii?.button ?? "4px",
1009
- card: "4px",
1010
- badge: "9999px",
1011
- input: "4px"
1012
- },
1013
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
1014
- density: ctx.recipeHints.density ?? "comfortable",
1015
- badgeDefault: "muted",
1016
- // Ghost outline companion uses lavender hairline, not carbon
1017
- outlineBorder: lavender
1018
- }),
1019
- // Deliberate and even. Stripe's motion never draws attention to itself;
1020
- // the curve is symmetric so a panel leaves the way it arrived.
1021
- motion: {
1022
- easeOut: "cubic-bezier(0.215, 0.61, 0.355, 1)",
1023
- easeInOut: "cubic-bezier(0.645, 0.045, 0.355, 1)",
1024
- micro: 100,
1025
- flow: 180,
1026
- reveal: 260,
1027
- cinematic: 400
1028
- },
1029
- // Clean commerce chrome — a touch more generous than the app-shell trio.
1030
- spacing: {
1031
- xs: "0.5rem",
1032
- sm: "0.75rem",
1033
- md: "1.125rem",
1034
- lg: "1.75rem",
1035
- xl: "2.25rem",
1036
- "2xl": "3.25rem"
1037
- },
1038
- typography: {
1039
- fontSans: `'sohne-var', 'Inter Tight', 'Inter', ui-sans-serif, system-ui, sans-serif`,
1040
- fontDisplay: `'sohne-var', 'Inter Tight', ui-sans-serif, system-ui, sans-serif`,
1041
- // Whisper weight is the Stripe signature (even at 56px display)
1042
- headingWeight: 300,
1043
- faces: [
1044
- {
1045
- family: "sohne-var",
1046
- // Placeholder — Create Center / licensed Söhne Variable
1047
- src: [{ url: "/brand-assets/sohne-var.woff2", format: "woff2" }],
1048
- weight: "300 400",
1049
- display: "swap"
1050
- }
1051
- ]
1052
- },
1053
- extensions: {
1054
- categories: {
1055
- brand: midnight,
1056
- action: indigo,
1057
- link: indigo,
1058
- hover: indigoHover,
1059
- ghostBorder: lavender,
1060
- wash: periwinkle,
1061
- deep,
1062
- smoke,
1063
- steel
1064
- },
1065
- decorative: {
1066
- "section-band": mist,
1067
- frost
1068
- },
1069
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://stripe.com",
1070
- notes: [
1071
- "roles.action = Indigo Ink filled CTA; roles.brand = Midnight Ink wordmark.",
1072
- "Elevation none \u2014 tint ladder + 1px frost rules; never box-shadow.",
1073
- "Control ctx.radius 4px (not pill); tags may stay full-pill.",
1074
- "Typography weight 300 is the product signature (Inter Tight substitute).",
1075
- "Pair solid CTA with ghost outline (lavender border) as secondary."
1076
- ]
1077
- }
1078
- };
1079
- return brand;
1080
- }
1081
-
1082
- // src/brand-package/presets/vanta.ts
1083
- function buildVanta(ctx) {
1084
- const parchment = ctx.colors.parchment ?? ctx.colors["page-canvas"] ?? "#f7f8fa";
1085
- const paper = ctx.colors.paper ?? ctx.colors["card-surface"] ?? "#ffffff";
1086
- const carbon = ctx.colors.carbon ?? "#181822";
1087
- const graphite = ctx.colors.graphite ?? "#6d6e87";
1088
- const steel = ctx.colors.steel ?? "#9e9fb7";
1089
- const ash = ctx.colors.ash ?? "#dfdfe9";
1090
- const fog = ctx.colors.fog ?? "#eaeaf1";
1091
- const lavender = ctx.colors["lavender-wash"] ?? "#ddd6ff";
1092
- const vivid = ctx.colors["vivid-violet"] ?? "#5e05c4";
1093
- const indigo = ctx.colors["indigo-ink"] ?? "#260048";
1094
- const mid = ctx.colors["mid-violet"] ?? "#8f47d5";
1095
- const amber = ctx.colors["amber-signal"] ?? "#ffbe0f";
1096
- ctx.warnings.push(
1097
- "Vanta: vivid-violet is action CTA only; indigo-ink is brand-mark/logo (never default CTA fill)."
1098
- );
1099
- ctx.warnings.push("Vanta: elevation=none \u2014 cards use 1px carbon border, not box-shadow.");
1100
- const brand = {
1101
- id: "vanta",
1102
- name: "Vanta",
1103
- darkDefault: false,
1104
- version: "1.0.0",
1105
- roles: {
1106
- canvas: tryHexToHsl(parchment, "220 23% 97%"),
1107
- canvasForeground: tryHexToHsl(carbon, "240 14% 11%"),
1108
- surface: tryHexToHsl(paper, "0 0% 100%"),
1109
- surfaceForeground: tryHexToHsl(carbon, "240 14% 11%"),
1110
- // Action = single saturated CTA moment
1111
- action: tryHexToHsl(vivid, "268 95% 39%"),
1112
- actionForeground: tryHexToHsl(paper, "0 0% 100%"),
1113
- // Brand mark = logo / wordmark / decorative ink (≠ action)
1114
- brand: tryHexToHsl(indigo, "273 100% 14%"),
1115
- brandForeground: tryHexToHsl(paper, "0 0% 100%"),
1116
- // Quiet = lavender informational chips (not violet fill)
1117
- quiet: tryHexToHsl(lavender, "249 100% 92%"),
1118
- quietForeground: tryHexToHsl(indigo, "273 100% 14%"),
1119
- muted: tryHexToHsl(fog, "240 14% 93%"),
1120
- mutedForeground: tryHexToHsl(graphite, "237 11% 48%"),
1121
- border: tryHexToHsl(carbon, "240 14% 11%"),
1122
- input: tryHexToHsl(paper, "0 0% 100%"),
1123
- ring: tryHexToHsl(vivid, "268 95% 39%"),
1124
- destructive: "0 72% 51%",
1125
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
1126
- warning: tryHexToHsl(amber, "44 100% 53%"),
1127
- warningForeground: tryHexToHsl(carbon, "240 14% 11%"),
1128
- info: tryHexToHsl(mid, "269 63% 56%"),
1129
- infoForeground: tryHexToHsl(paper, "0 0% 100%")
1130
- },
1131
- // semantic filled by normalize from roles
1132
- semantic: {
1133
- background: tryHexToHsl(parchment, "220 23% 97%"),
1134
- foreground: tryHexToHsl(carbon, "240 14% 11%"),
1135
- card: tryHexToHsl(paper, "0 0% 100%"),
1136
- cardForeground: tryHexToHsl(carbon, "240 14% 11%"),
1137
- popover: tryHexToHsl(paper, "0 0% 100%"),
1138
- popoverForeground: tryHexToHsl(carbon, "240 14% 11%"),
1139
- primary: tryHexToHsl(vivid, "268 95% 39%"),
1140
- primaryForeground: tryHexToHsl(paper, "0 0% 100%"),
1141
- secondary: tryHexToHsl(lavender, "249 100% 92%"),
1142
- secondaryForeground: tryHexToHsl(indigo, "273 100% 14%"),
1143
- muted: tryHexToHsl(fog, "240 14% 93%"),
1144
- mutedForeground: tryHexToHsl(graphite, "237 11% 48%"),
1145
- accent: tryHexToHsl(indigo, "273 100% 14%"),
1146
- accentForeground: tryHexToHsl(paper, "0 0% 100%"),
1147
- destructive: "0 72% 51%",
1148
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
1149
- border: tryHexToHsl(carbon, "240 14% 11%"),
1150
- input: tryHexToHsl(paper, "0 0% 100%"),
1151
- ring: tryHexToHsl(vivid, "268 95% 39%"),
1152
- warning: tryHexToHsl(amber, "44 100% 53%"),
1153
- warningForeground: tryHexToHsl(carbon, "240 14% 11%"),
1154
- info: tryHexToHsl(mid, "269 63% 56%"),
1155
- infoForeground: tryHexToHsl(paper, "0 0% 100%")
1156
- },
1157
- recipe: buildRecipe({
1158
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
1159
- // Prefer structural ctx.radius tokens (full=pill, 2xl=cards) over free-text
1160
- radii: {
1161
- button: leafHex(ctx.radius, ["buttons"]) ?? leafHex(ctx.radius, ["full"]) ?? ctx.recipeHints.radii?.button ?? "999px",
1162
- card: leafHex(ctx.radius, ["cards"]) ?? leafHex(ctx.radius, ["2xl"]) ?? "16px",
1163
- badge: leafHex(ctx.radius, ["badges"]) ?? leafHex(ctx.radius, ["full"]) ?? ctx.recipeHints.radii?.button ?? "999px",
1164
- input: leafHex(ctx.radius, ["inputs"]) ?? leafHex(ctx.radius, ["full"]) ?? ctx.recipeHints.radii?.button ?? "999px"
1165
- },
1166
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
1167
- density: ctx.recipeHints.density ?? "comfortable",
1168
- // Informational chips = lavender wash + indigo (quiet), not violet CTA fill
1169
- badgeDefault: "muted",
1170
- outlineBorder: carbon
1171
- }),
1172
- // Atmospheric. Backdrops drift rather than move, so even the micro step is
1173
- // slow and nothing snaps.
1174
- motion: {
1175
- easeOut: "cubic-bezier(0.33, 1, 0.68, 1)",
1176
- easeInOut: "cubic-bezier(0.37, 0, 0.63, 1)",
1177
- micro: 120,
1178
- flow: 240,
1179
- reveal: 380,
1180
- cinematic: 620
1181
- },
1182
- // The most spacious of the seven — serif editorial wants the most air.
1183
- spacing: {
1184
- xs: "0.75rem",
1185
- sm: "1.125rem",
1186
- md: "1.75rem",
1187
- lg: "2.5rem",
1188
- xl: "3.5rem",
1189
- "2xl": "5rem"
1190
- },
1191
- typography: {
1192
- fontSans: `'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif`,
1193
- fontDisplay: `'Reckless', 'Source Serif 4', 'Lora', ui-serif, Georgia, serif`,
1194
- headingWeight: 500,
1195
- faces: [
1196
- {
1197
- family: "Inter Variable",
1198
- src: [
1199
- {
1200
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
1201
- format: "woff2"
1202
- }
1203
- ],
1204
- weight: "100 900",
1205
- display: "swap"
1206
- },
1207
- {
1208
- family: "Reckless",
1209
- // Placeholder — Create Center / tenant hosts the licensed Reckless cut
1210
- src: [{ url: "/brand-assets/reckless-regular.woff2", format: "woff2" }],
1211
- weight: 400,
1212
- display: "swap"
1213
- },
1214
- {
1215
- family: "Reckless",
1216
- src: [{ url: "/brand-assets/reckless-medium.woff2", format: "woff2" }],
1217
- weight: 500,
1218
- display: "swap"
1219
- }
1220
- ]
1221
- },
1222
- extensions: {
1223
- categories: {
1224
- brand: indigo,
1225
- action: vivid,
1226
- link: mid,
1227
- heroWash: lavender,
1228
- warning: amber,
1229
- steel,
1230
- ash
1231
- },
1232
- decorative: {
1233
- "hero-wash": lavender
1234
- },
1235
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://www.vanta.com",
1236
- notes: [
1237
- "roles.action = Vivid Violet (filled CTA only).",
1238
- "roles.brand = Indigo Ink (logo / wordmark / brand-mark).",
1239
- "Elevation none \u2014 1px carbon borders frame cards; no drop-shadow.",
1240
- "Full-pill controls (999px); cards 16px.",
1241
- "UI = Inter Variable; marketing display = Reckless serif.",
1242
- "Lavender wash is marketing hero surface (decorative), not product chrome fill."
1243
- ]
1244
- }
1245
- };
1246
- return brand;
1247
- }
1248
-
1249
- // src/brand-package/presets/vercel.ts
1250
- function vercelLightSemantic(paper, pure, hairline, charcoal, stone, obsidian, terminal) {
1251
- return {
1252
- background: tryHexToHsl(paper, "0 0% 98%"),
1253
- foreground: tryHexToHsl(obsidian, "0 0% 9%"),
1254
- card: tryHexToHsl(pure, "0 0% 100%"),
1255
- cardForeground: tryHexToHsl(obsidian, "0 0% 9%"),
1256
- popover: tryHexToHsl(pure, "0 0% 100%"),
1257
- popoverForeground: tryHexToHsl(obsidian, "0 0% 9%"),
1258
- // Filled black button
1259
- primary: tryHexToHsl(obsidian, "0 0% 9%"),
1260
- primaryForeground: tryHexToHsl(pure, "0 0% 100%"),
1261
- secondary: tryHexToHsl(hairline, "0 0% 92%"),
1262
- secondaryForeground: tryHexToHsl(charcoal, "0 0% 30%"),
1263
- muted: tryHexToHsl(hairline, "0 0% 92%"),
1264
- mutedForeground: tryHexToHsl(stone, "0 0% 40%"),
1265
- accent: tryHexToHsl(hairline, "0 0% 92%"),
1266
- accentForeground: tryHexToHsl(obsidian, "0 0% 9%"),
1267
- destructive: "0 72% 51%",
1268
- destructiveForeground: tryHexToHsl(pure, "0 0% 100%"),
1269
- border: tryHexToHsl(hairline, "0 0% 92%"),
1270
- input: tryHexToHsl(pure, "0 0% 100%"),
1271
- ring: tryHexToHsl(obsidian, "0 0% 9%"),
1272
- success: tryHexToHsl(terminal, "133 49% 32%"),
1273
- successForeground: tryHexToHsl(pure, "0 0% 100%"),
1274
- info: tryHexToHsl(charcoal, "0 0% 30%"),
1275
- infoForeground: tryHexToHsl(pure, "0 0% 100%")
1276
- };
1277
- }
1278
- function vercelDarkSemantic(terminal) {
1279
- return {
1280
- background: "0 0% 0%",
1281
- foreground: "0 0% 93%",
1282
- card: "0 0% 4%",
1283
- cardForeground: "0 0% 93%",
1284
- popover: "0 0% 4%",
1285
- popoverForeground: "0 0% 93%",
1286
- primary: "0 0% 100%",
1287
- primaryForeground: "0 0% 0%",
1288
- secondary: "0 0% 12%",
1289
- secondaryForeground: "0 0% 93%",
1290
- muted: "0 0% 12%",
1291
- mutedForeground: "0 0% 63%",
1292
- accent: "0 0% 12%",
1293
- accentForeground: "0 0% 93%",
1294
- destructive: "0 72% 51%",
1295
- destructiveForeground: "0 0% 100%",
1296
- border: "0 0% 16%",
1297
- input: "0 0% 4%",
1298
- ring: "0 0% 100%",
1299
- success: tryHexToHsl(terminal, "133 49% 40%"),
1300
- successForeground: "0 0% 100%",
1301
- info: "0 0% 70%",
1302
- infoForeground: "0 0% 0%"
1303
- };
1304
- }
1305
- function buildVercel(ctx) {
1306
- const paper = ctx.colors["paper-white"] ?? ctx.colors["page-canvas"] ?? "#fafafa";
1307
- const pure = ctx.colors["pure-white"] ?? ctx.colors["card-surface"] ?? "#ffffff";
1308
- const hairline = ctx.colors.hairline ?? "#ebebeb";
1309
- const charcoal = ctx.colors.charcoal ?? "#4d4d4d";
1310
- const stone = ctx.colors.stone ?? "#666666";
1311
- const obsidian = ctx.colors.obsidian ?? ctx.colors["inverted-surface"] ?? "#171717";
1312
- const carbon = ctx.colors.carbon ?? "#000000";
1313
- const terminal = ctx.colors["terminal-green"] ?? "#297a3a";
1314
- ctx.warnings.push(
1315
- "Vercel: monochrome dual-mode \u2014 no chromatic CTA; Terminal Green is support only."
1316
- );
1317
- const light = vercelLightSemantic(paper, pure, hairline, charcoal, stone, obsidian, terminal);
1318
- const dark = vercelDarkSemantic(terminal);
1319
- const brand = {
1320
- id: "vercel",
1321
- name: "Vercel",
1322
- darkDefault: false,
1323
- version: "1.0.0",
1324
- semantic: light,
1325
- modes: {
1326
- light: { semantic: light },
1327
- dark: { semantic: dark }
1328
- },
1329
- recipe: buildRecipe({
1330
- buttonDefault: "solid",
1331
- radii: {
1332
- button: ctx.recipeHints.radii?.button ?? "6px",
1333
- card: "6px",
1334
- badge: "6px",
1335
- input: "6px"
1336
- },
1337
- elevationPreset: ctx.recipeHints.elevationPreset === "key" ? "hairline" : ctx.recipeHints.elevationPreset ?? "hairline",
1338
- density: ctx.recipeHints.density ?? "compact",
1339
- badgeDefault: "muted",
1340
- cardShadow: "rgba(0, 0, 0, 0.08) 0px 0px 0px 1px, rgb(250, 250, 250) 0px 0px 0px 1px",
1341
- outlineBorder: hairline
1342
- }),
1343
- // Neutral and quick. Geist treats motion as feedback rather than
1344
- // expression, so the ramp is short and the curve is the platform default.
1345
- motion: {
1346
- easeOut: "cubic-bezier(0, 0, 0.2, 1)",
1347
- easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
1348
- micro: 100,
1349
- flow: 150,
1350
- reveal: 200,
1351
- cinematic: 300
1352
- },
1353
- // Geist's default rhythm — the middle of the seven, same figures as the
1354
- // shared fallback rail so a page with no language selected still matches it.
1355
- spacing: {
1356
- xs: "0.5rem",
1357
- sm: "0.75rem",
1358
- md: "1rem",
1359
- lg: "1.5rem",
1360
- xl: "2rem",
1361
- "2xl": "3rem"
1362
- },
1363
- typography: {
1364
- fontSans: `'Geist Sans', 'Geist', ui-sans-serif, system-ui, sans-serif`,
1365
- fontMono: `'Geist Mono', ui-monospace, Menlo, monospace`,
1366
- fontDisplay: `'Geist Sans', 'Geist', ui-sans-serif, system-ui, sans-serif`,
1367
- headingWeight: 450,
1368
- faces: [
1369
- {
1370
- family: "Geist Sans",
1371
- src: [
1372
- {
1373
- url: "https://cdn.jsdelivr.net/fontsource/fonts/geist-sans:vf@latest/latin-wght-normal.woff2",
1374
- format: "woff2"
1375
- }
1376
- ],
1377
- weight: "100 900",
1378
- display: "swap"
1379
- }
1380
- ]
1381
- },
1382
- extensions: {
1383
- categories: {
1384
- carbon,
1385
- terminal
1386
- },
1387
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://vercel.com",
1388
- notes: [
1389
- "Dual-mode monochrome \u2014 light: Obsidian CTA; dark: white CTA on carbon.",
1390
- "Elevation is hairline double-ring, never drop-shadow.",
1391
- "Spectrum/solar gradients are marketing-only decorative (not product chrome)."
1392
- ]
1393
- }
1394
- };
1395
- return brand;
1396
- }
1397
-
1398
- // src/brand-package/presets/index.ts
1399
- var PRESET_BUILDERS = {
1400
- linear: buildLinear,
1401
- gsap: buildGsap,
1402
- raycast: buildRaycast,
1403
- vercel: buildVercel,
1404
- notion: buildNotion,
1405
- stripe: buildStripe,
1406
- vanta: buildVanta
1407
- };
1408
- function buildPresetBrand(preset, ctx) {
1409
- if (preset === "generic") return buildGeneric(ctx);
1410
- return PRESET_BUILDERS[preset](ctx);
1411
- }
1412
-
1413
- // src/brand-package/compile-refero.ts
1414
- function finish(brand, warnings) {
1415
- const b = normalizeBrandPackage(brand);
1416
- return { brand: b, css: emitBrandCss(b), warnings };
1417
- }
1418
- function compileReferoTokens(input) {
1419
- const warnings = [];
1420
- const color = input.tokens.color ?? {};
1421
- const surface = input.tokens.surface ?? {};
1422
- const font = input.tokens.font ?? {};
1423
- const radius = input.tokens.radius ?? {};
1424
- const ext = input.tokens.$extensions ?? {};
1425
- const refero = ext["com.refero.extraction"] ?? {};
1426
- const colors = { ...collectColors(color), ...collectSurfaces(surface) };
1427
- const siteName = typeof refero.siteName === "string" && refero.siteName || input.name || "Custom Brand";
1428
- const id = input.id || siteName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "custom";
1429
- const preset = detectPreset(id, colors);
1430
- const recipeHints = inferRecipeFromDesignMd(input.designMd ?? "");
1431
- const brand = buildPresetBrand(preset, {
1432
- colors,
1433
- font,
1434
- radius,
1435
- refero,
1436
- recipeHints,
1437
- warnings,
1438
- id,
1439
- siteName
1440
- });
1441
- return finish(brand, warnings);
1442
- }
1443
-
1444
- // src/brand-package/validate.ts
1445
- var BUTTON_STYLES = /* @__PURE__ */ new Set(["solid", "outline", "gradient-stroke"]);
1446
- function validateBrandPackage(brand) {
1447
- const errors = [];
1448
- const warnings = [];
1449
- if (!brand || typeof brand !== "object") {
1450
- return { ok: false, errors: ["Brand package must be an object"], warnings };
1451
- }
1452
- let b;
1453
- try {
1454
- b = normalizeBrandPackage(brand);
1455
- } catch (e) {
1456
- return { ok: false, errors: [`normalize failed: ${e.message}`], warnings };
1457
- }
1458
- if (!b.id || typeof b.id !== "string") errors.push("id is required");
1459
- if (!b.name || typeof b.name !== "string") errors.push("name is required");
1460
- if (!b.version || typeof b.version !== "string") errors.push("version is required");
1461
- if (!b.roles) {
1462
- errors.push("roles missing after normalize");
1463
- } else {
1464
- for (const key of ["canvas", "action", "actionForeground", "border"]) {
1465
- if (!b.roles[key]) errors.push(`roles.${key} is required`);
1466
- }
1467
- if (b.roles.brand && b.roles.brand === b.roles.action) {
1468
- warnings.push(
1469
- "roles.brand equals roles.action \u2014 brand mark is not separated from CTA (often intentional)"
1470
- );
1471
- }
1472
- }
1473
- if (!b.semantic || typeof b.semantic !== "object") {
1474
- errors.push("semantic is required");
1475
- } else {
1476
- for (const key of [
1477
- "background",
1478
- "foreground",
1479
- "primary",
1480
- "primaryForeground",
1481
- "border",
1482
- "ring"
1483
- ]) {
1484
- if (!b.semantic[key]) errors.push(`semantic.${key} is required`);
1485
- }
1486
- if (b.roles && b.semantic.primary !== b.roles.action) {
1487
- errors.push("semantic.primary must equal roles.action (CTA bridge)");
1488
- }
1489
- }
1490
- if (!b.recipe || typeof b.recipe !== "object") {
1491
- errors.push("recipe is required");
1492
- } else {
1493
- if (!BUTTON_STYLES.has(b.recipe.buttonDefault)) {
1494
- errors.push(`recipe.buttonDefault must be one of ${[...BUTTON_STYLES].join(", ")}`);
1495
- }
1496
- if (!b.recipe.radii?.button) errors.push("recipe.radii.button is required");
1497
- if (!b.recipe.radii?.card) errors.push("recipe.radii.card is required");
1498
- if (!b.recipe.elevationTokens?.card) {
1499
- errors.push("recipe.elevationTokens.card is required (free CSS box-shadow)");
1500
- }
1501
- if (b.recipe.buttonDefault === "gradient-stroke" && !b.recipe.primaryStrokeGradient) {
1502
- warnings.push(
1503
- "gradient-stroke without primaryStrokeGradient \u2014 border falls back to solid primary"
1504
- );
1505
- }
1506
- }
1507
- if (!b.typography?.fontSans) errors.push("typography.fontSans is required");
1508
- if (b.typography?.faces) {
1509
- b.typography.faces.forEach((face, i) => {
1510
- if (!face.family) errors.push(`typography.faces[${i}].family is required`);
1511
- if (!face.src?.length) errors.push(`typography.faces[${i}].src must be non-empty`);
1512
- else {
1513
- for (const [j, src] of face.src.entries()) {
1514
- if (!src.url) errors.push(`typography.faces[${i}].src[${j}].url is required`);
1515
- }
1516
- }
1517
- });
1518
- }
1519
- for (const [key, value] of Object.entries(b.recipe?.elevationTokens ?? {})) {
1520
- if (typeof value === "string" && /var\(\s*--(?:shadow|elevation)-/.test(value)) {
1521
- errors.push(
1522
- `recipe.elevationTokens.${key} references a shadow/elevation variable \u2014 that is a cycle once the skin sets the source. Inline the value.`
1523
- );
1524
- }
1525
- }
1526
- if (b.motion) {
1527
- for (const key of ["micro", "flow", "reveal", "cinematic"]) {
1528
- const value = b.motion[key];
1529
- if (value == null) continue;
1530
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1531
- errors.push(`motion.${key} must be a non-negative number of milliseconds`);
1532
- } else if (value > 2e3) {
1533
- warnings.push(`motion.${key} is ${value}ms \u2014 long enough to read as a stall`);
1534
- }
1535
- }
1536
- for (const key of ["easeOut", "easeInOut", "easeSpring"]) {
1537
- const value = b.motion[key];
1538
- if (value == null) continue;
1539
- if (typeof value !== "string" || !value.trim()) {
1540
- errors.push(`motion.${key} must be a non-empty timing function`);
1541
- }
1542
- }
1543
- const { micro, flow, reveal, cinematic } = b.motion;
1544
- const ramp = [micro, flow, reveal, cinematic].filter((v) => typeof v === "number");
1545
- if (ramp.length > 1 && ramp.some((v, i) => i > 0 && v < (ramp[i - 1] ?? v))) {
1546
- warnings.push(
1547
- "motion durations are not ascending \u2014 micro should be the shortest and cinematic the longest"
1548
- );
1549
- }
1550
- }
1551
- if (b.spacing) {
1552
- const UNIT = /^-?\d*\.?\d+(rem|em|px|%|vh|vw|ch)$/;
1553
- const order = [];
1554
- for (const key of ["xs", "sm", "md", "lg", "xl", "2xl"]) {
1555
- const value = b.spacing[key];
1556
- if (value == null) continue;
1557
- if (typeof value !== "string" || !UNIT.test(value.trim())) {
1558
- errors.push(
1559
- `spacing.${key} must be a CSS length with a unit (e.g. "1rem"), got ${JSON.stringify(value)}`
1560
- );
1561
- continue;
1562
- }
1563
- order.push([key, Number.parseFloat(value)]);
1564
- }
1565
- if (order.length > 1 && order.some(([, v], i) => i > 0 && v < (order[i - 1]?.[1] ?? v))) {
1566
- warnings.push(
1567
- "spacing steps are not ascending \u2014 xs should be the smallest and 2xl the largest"
1568
- );
1569
- }
1570
- }
1571
- if (b.recipe?.buttonDefault === "solid" && b.semantic?.primary === b.semantic?.primaryForeground) {
1572
- warnings.push("primary and primaryForeground are identical \u2014 check contrast");
1573
- }
1574
- return { ok: errors.length === 0, errors, warnings };
1575
- }
1576
-
1577
- export {
1578
- inferRecipeFromDesignMd,
1579
- compileReferoTokens,
1580
- validateBrandPackage
1581
- };
1582
- //# sourceMappingURL=chunk-RGUJQOLH.js.map