@nebutra/tokens 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/brands/gsap/brand.json +17 -64
  2. package/brands/linear/brand.json +18 -57
  3. package/brands/notion/brand.json +18 -62
  4. package/brands/raycast/brand.json +17 -64
  5. package/brands/stripe/brand.json +16 -72
  6. package/brands/vanta/brand.json +16 -63
  7. package/brands/vercel/brand.json +16 -50
  8. package/dist/brand-package/index.d.ts +123 -0
  9. package/dist/brand-package/index.js +61 -0
  10. package/dist/brand-package/index.js.map +1 -0
  11. package/dist/brand-package/use-brand.d.ts +2 -0
  12. package/dist/brand-package/use-brand.js +12 -0
  13. package/dist/brand-package/use-brand.js.map +1 -0
  14. package/dist/chunk-ALJTP5HL.js +881 -0
  15. package/dist/chunk-ALJTP5HL.js.map +1 -0
  16. package/dist/chunk-RGUJQOLH.js +1582 -0
  17. package/dist/chunk-RGUJQOLH.js.map +1 -0
  18. package/dist/index.d.ts +81 -0
  19. package/dist/index.js +187 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/use-brand-C8d4DPqE.d.ts +371 -0
  22. package/package.json +34 -9
  23. package/recipe.css +322 -1
  24. package/skins/README.md +2 -4
  25. package/skins/gsap.css +31 -119
  26. package/skins/linear.css +33 -117
  27. package/skins/notion.css +27 -117
  28. package/skins/raycast.css +32 -119
  29. package/skins/stripe.css +27 -129
  30. package/skins/vanta.css +27 -122
  31. package/skins/vercel.css +30 -111
  32. package/styles.css +388 -113
  33. package/.turbo/turbo-build.log +0 -8
  34. package/.turbo/turbo-typecheck.log +0 -4
  35. package/AGENTS.md +0 -45
  36. package/DESIGN.md +0 -357
  37. package/scripts/compile-brand-run.ts +0 -79
  38. package/scripts/compile-brand.mjs +0 -25
  39. package/scripts/emit-skins-run.ts +0 -62
  40. package/scripts/emit-skins.mjs +0 -31
  41. package/scripts/sync-styles.mjs +0 -29
  42. package/src/brand-package/__tests__/compile-refero.test.ts +0 -427
  43. package/src/brand-package/__tests__/emit-css.test.ts +0 -130
  44. package/src/brand-package/apply-brand.ts +0 -92
  45. package/src/brand-package/compile-helpers.ts +0 -170
  46. package/src/brand-package/compile-refero.ts +0 -69
  47. package/src/brand-package/emit-css.ts +0 -468
  48. package/src/brand-package/hex-to-hsl.ts +0 -100
  49. package/src/brand-package/index.ts +0 -71
  50. package/src/brand-package/infer-recipe.ts +0 -198
  51. package/src/brand-package/normalize.ts +0 -295
  52. package/src/brand-package/presets/context.ts +0 -17
  53. package/src/brand-package/presets/generic.ts +0 -214
  54. package/src/brand-package/presets/gsap.ts +0 -146
  55. package/src/brand-package/presets/index.ts +0 -31
  56. package/src/brand-package/presets/linear.ts +0 -183
  57. package/src/brand-package/presets/notion.ts +0 -208
  58. package/src/brand-package/presets/raycast.ts +0 -138
  59. package/src/brand-package/presets/recipe.ts +0 -61
  60. package/src/brand-package/presets/stripe.ts +0 -207
  61. package/src/brand-package/presets/vanta.ts +0 -220
  62. package/src/brand-package/presets/vercel.ts +0 -187
  63. package/src/brand-package/types.ts +0 -285
  64. package/src/brand-package/use-brand.ts +0 -207
  65. package/src/brand-package/validate.ts +0 -108
  66. package/src/index.ts +0 -67
  67. package/src/theme-provider.tsx +0 -246
  68. package/tsconfig.json +0 -11
  69. package/turbo.json +0 -15
@@ -1,214 +0,0 @@
1
- /**
2
- * Generic heuristic compile path for unknown brands.
3
- */
4
- import { leafHex, pickDisplayFontFamily, pickUiFontFamily } from "../compile-helpers";
5
- import { tryHexToHsl } from "../hex-to-hsl";
6
- import type { BrandPackage } from "../types";
7
- import type { CompileContext } from "./context";
8
- import { buildRecipe } from "./recipe";
9
-
10
- export function buildGeneric(ctx: CompileContext): BrandPackage {
11
- ctx.warnings.push(
12
- "Unknown brand layout — compiled with heuristic recipe. Review mapping in Create Center.",
13
- );
14
- ctx.warnings.push(...ctx.recipeHints.notes);
15
- const entries = Object.entries(ctx.colors);
16
- const pick = (...keys: string[]) => {
17
- for (const k of keys) {
18
- if (ctx.colors[k]) return ctx.colors[k];
19
- }
20
- return undefined;
21
- };
22
- // Page canvas first (warm paper / parchment), then pure white, then dark voids.
23
- // Notion: paper-warmth is canvas; pure-white is card — never invert.
24
- const bg =
25
- pick(
26
- "paper-warmth",
27
- "paper-white",
28
- "page-canvas",
29
- "parchment",
30
- "pure-white",
31
- "paper",
32
- "background",
33
- "canvas",
34
- "void-black",
35
- "void",
36
- "just-black",
37
- "off-black",
38
- ) ?? "#0a0a0a";
39
-
40
- // Detect light canvas early so fg/primary picks don't invert
41
- const isLightCanvas = (() => {
42
- try {
43
- const m = tryHexToHsl(bg, "0 0% 4%").match(/(\d+)%\s*$/);
44
- return m ? Number(m[1]) >= 50 : false;
45
- } catch {
46
- return false;
47
- }
48
- })();
49
-
50
- // Light: ink/carbon text — never pure-white; prefer ink-black over decorative midnight.
51
- const fg = isLightCanvas
52
- ? (pick(
53
- "ink-black",
54
- "carbon",
55
- "charcoal",
56
- "obsidian",
57
- "foreground",
58
- "ink",
59
- "midnight-ink",
60
- "deep-violet",
61
- "slate",
62
- ) ?? "#181822")
63
- : (pick("pure-white", "paper", "surface-cream", "bone", "mist", "foreground", "white") ??
64
- "#ffffff");
65
-
66
- // Chromatic *action* tokens only — never canvas/surface/decorative wash names
67
- const primary =
68
- pick(
69
- "notion-blue",
70
- "indigo-ink",
71
- "vivid-violet",
72
- "primary",
73
- "acid-lime",
74
- "obsidian",
75
- "shockingly-green",
76
- "brand",
77
- "accent",
78
- "mid-violet",
79
- "amethyst-edge",
80
- "signal-blue",
81
- ) ??
82
- entries.find(
83
- ([k]) =>
84
- !/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(
85
- k,
86
- ),
87
- )?.[1] ??
88
- (isLightCanvas ? "#171717" : "#3b82f6");
89
-
90
- // Brand-mark ≠ action: logo inks — not decorative midnight/coral washes by default
91
- const brandMarkHex =
92
- pick("ink-black", "coral-pulse", "brand-mark", "logo", "wordmark", "charcoal", "deep-violet") ??
93
- undefined;
94
-
95
- const border = isLightCanvas
96
- ? (pick("frost", "hairline", "border", "ash", "carbon", "lilac-border", "slate") ?? "#e5e5e5")
97
- : (pick("hairline", "border", "slate", "graphite", "surface-25", "smoke") ?? "#333333");
98
- const mutedFg =
99
- pick(
100
- "stone",
101
- "charcoal",
102
- "graphite",
103
- "steel",
104
- "slate",
105
- "muted",
106
- "smoke",
107
- "ash",
108
- "fog",
109
- "surface-50",
110
- ) ?? "#888888";
111
- const card = isLightCanvas
112
- ? (pick("pure-white", "paper", "card-surface", "card") ?? "#ffffff")
113
- : (pick("ink", "carbon", "off-black", "obsidian", "card") ?? bg);
114
-
115
- const quiet = isLightCanvas
116
- ? (pick("sky-tint", "periwinkle-wash", "lavender-wash", "mist", "fog", "ash", "secondary") ??
117
- border)
118
- : (pick("graphite", "obsidian", "smoke", "secondary") ?? border);
119
-
120
- const uiFont = pickUiFontFamily(ctx.font) ?? "Inter";
121
- const displayFont = pickDisplayFontFamily(ctx.font);
122
-
123
- let elevationPreset = ctx.recipeHints.elevationPreset ?? "soft";
124
- if (elevationPreset === "key" && isLightCanvas) elevationPreset = "hairline";
125
-
126
- const brand: BrandPackage = {
127
- id: ctx.id,
128
- name: ctx.siteName,
129
- darkDefault: !isLightCanvas,
130
- version: "0.1.0",
131
- semantic: isLightCanvas
132
- ? {
133
- background: tryHexToHsl(bg, "0 0% 98%"),
134
- foreground: tryHexToHsl(fg, "0 0% 9%"),
135
- card: tryHexToHsl(card, "0 0% 100%"),
136
- cardForeground: tryHexToHsl(fg, "0 0% 9%"),
137
- popover: tryHexToHsl(card, "0 0% 100%"),
138
- popoverForeground: tryHexToHsl(fg, "0 0% 9%"),
139
- primary: tryHexToHsl(primary, "0 0% 9%"),
140
- primaryForeground: tryHexToHsl(card, "0 0% 100%"),
141
- secondary: tryHexToHsl(quiet, "0 0% 92%"),
142
- secondaryForeground: tryHexToHsl(fg, "0 0% 9%"),
143
- muted: tryHexToHsl(quiet, "0 0% 92%"),
144
- mutedForeground: tryHexToHsl(mutedFg, "0 0% 40%"),
145
- accent: tryHexToHsl(quiet, "0 0% 92%"),
146
- accentForeground: tryHexToHsl(fg, "0 0% 9%"),
147
- destructive: "0 72% 51%",
148
- destructiveForeground: "0 0% 100%",
149
- border: tryHexToHsl(border, "0 0% 20%"),
150
- input: tryHexToHsl(card, "0 0% 100%"),
151
- ring: tryHexToHsl(primary, "0 0% 9%"),
152
- }
153
- : {
154
- background: tryHexToHsl(bg, "0 0% 4%"),
155
- foreground: tryHexToHsl(fg, "0 0% 98%"),
156
- card: tryHexToHsl(card, "0 0% 8%"),
157
- cardForeground: tryHexToHsl(fg, "0 0% 98%"),
158
- popover: tryHexToHsl(card, "0 0% 8%"),
159
- popoverForeground: tryHexToHsl(fg, "0 0% 98%"),
160
- primary: tryHexToHsl(primary, "217 91% 60%"),
161
- primaryForeground: tryHexToHsl(bg, "0 0% 4%"),
162
- secondary: tryHexToHsl(border, "0 0% 20%"),
163
- secondaryForeground: tryHexToHsl(fg, "0 0% 98%"),
164
- muted: tryHexToHsl(card, "0 0% 8%"),
165
- mutedForeground: tryHexToHsl(mutedFg, "0 0% 53%"),
166
- accent: tryHexToHsl(border, "0 0% 20%"),
167
- accentForeground: tryHexToHsl(primary, "217 91% 60%"),
168
- destructive: "0 72% 51%",
169
- destructiveForeground: "0 0% 100%",
170
- border: tryHexToHsl(border, "0 0% 20%"),
171
- input: tryHexToHsl(border, "0 0% 20%"),
172
- ring: tryHexToHsl(primary, "217 91% 60%"),
173
- },
174
- recipe: buildRecipe({
175
- buttonDefault: ctx.recipeHints.buttonDefault ?? "solid",
176
- radii: {
177
- button:
178
- ctx.recipeHints.radii?.button ??
179
- leafHex(ctx.radius, ["buttons"]) ??
180
- leafHex(ctx.radius, ["md"]) ??
181
- "0.375rem",
182
- card:
183
- ctx.recipeHints.radii?.card ??
184
- leafHex(ctx.radius, ["cards"]) ??
185
- leafHex(ctx.radius, ["xl"]) ??
186
- leafHex(ctx.radius, ["lg"]) ??
187
- "0.75rem",
188
- badge: leafHex(ctx.radius, ["pills"]) ?? leafHex(ctx.radius, ["badges"]) ?? "9999px",
189
- input: leafHex(ctx.radius, ["inputs"]) ?? ctx.recipeHints.radii?.button ?? "0.375rem",
190
- },
191
- elevationPreset,
192
- density: ctx.recipeHints.density ?? "comfortable",
193
- outlineBorder: isLightCanvas ? border : fg,
194
- badgeDefault: brandMarkHex ? "muted" : "match-action",
195
- }),
196
- typography: {
197
- fontSans: `'${uiFont}', ui-sans-serif, system-ui, sans-serif`,
198
- ...(displayFont ? { fontDisplay: `'${displayFont}', ui-serif, Georgia, serif` } : {}),
199
- headingWeight: isLightCanvas ? 500 : 600,
200
- },
201
- extensions: {
202
- ...(typeof ctx.refero.url === "string" ? { sourceUrl: ctx.refero.url } : {}),
203
- ...(brandMarkHex ? { categories: { brand: brandMarkHex } } : {}),
204
- notes: [
205
- "Generic compile — verify primary + buttonDefault in Create Center.",
206
- ...(brandMarkHex
207
- ? ["Detected separate brand-mark color (categories.brand → --brand-mark)."]
208
- : []),
209
- ],
210
- },
211
- };
212
-
213
- return brand;
214
- }
@@ -1,146 +0,0 @@
1
- /**
2
- * gsap design-language compile preset (stress fixture).
3
- */
4
- import { leafHex } from "../compile-helpers";
5
- import { tryHexToHsl } from "../hex-to-hsl";
6
- import type { BrandPackage, ButtonDefaultStyle, Density } from "../types";
7
- import type { CompileContext } from "./context";
8
- import { buildRecipe } from "./recipe";
9
-
10
- export function buildGsap(ctx: CompileContext): BrandPackage {
11
- const canvas = ctx.colors["just-black"] ?? ctx.colors.canvas ?? "#0e100f";
12
- const cream = ctx.colors["surface-cream"] ?? ctx.colors["cream-surface"] ?? "#fffce1";
13
- const muted = ctx.colors["surface-50"] ?? "#7c7c6f";
14
- const hairline = ctx.colors["surface-25"] ?? "#42433d";
15
- const nested = ctx.colors["off-black"] ?? ctx.colors["nested-panel"] ?? "#191919";
16
- const green = ctx.colors["shockingly-green"] ?? "#0ae448";
17
- // DESIGN: do NOT promote shockingly-green to filled primary CTA
18
- ctx.warnings.push(
19
- "GSAP: shockingly-green is accent/link only — buttonDefault=outline (no solid green fill).",
20
- );
21
-
22
- const buttonDefault: ButtonDefaultStyle = ctx.recipeHints.buttonDefault ?? "gradient-stroke";
23
-
24
- const brand: BrandPackage = {
25
- id: "gsap",
26
- name: "GSAP",
27
- darkDefault: true,
28
- version: "1.0.0",
29
- semantic: {
30
- // Primary for *links/accents* — filled solid CTAs are disabled by recipe
31
- background: tryHexToHsl(canvas, "150 8% 6%"),
32
- foreground: tryHexToHsl(cream, "54 100% 94%"),
33
- card: tryHexToHsl(nested, "0 0% 10%"),
34
- cardForeground: tryHexToHsl(cream, "54 100% 94%"),
35
- popover: tryHexToHsl(nested, "0 0% 10%"),
36
- popoverForeground: tryHexToHsl(cream, "54 100% 94%"),
37
- primary: tryHexToHsl(green, "136 91% 47%"),
38
- primaryForeground: tryHexToHsl(canvas, "150 8% 6%"),
39
- secondary: tryHexToHsl(hairline, "60 5% 25%"),
40
- secondaryForeground: tryHexToHsl(cream, "54 100% 94%"),
41
- muted: tryHexToHsl(nested, "0 0% 10%"),
42
- mutedForeground: tryHexToHsl(muted, "60 6% 46%"),
43
- accent: tryHexToHsl(hairline, "60 5% 25%"),
44
- accentForeground: tryHexToHsl(green, "136 91% 47%"),
45
- destructive: tryHexToHsl(ctx.colors["lipstick-pink"] ?? "#f100cb", "310 100% 47%"),
46
- destructiveForeground: tryHexToHsl(cream, "54 100% 94%"),
47
- border: tryHexToHsl(hairline, "60 5% 25%"),
48
- input: tryHexToHsl(hairline, "60 5% 25%"),
49
- ring: tryHexToHsl(green, "136 91% 47%"),
50
- info: tryHexToHsl(ctx.colors.blue ?? "#00bae2", "191 100% 44%"),
51
- infoForeground: tryHexToHsl(canvas, "150 8% 6%"),
52
- success: tryHexToHsl(green, "136 91% 47%"),
53
- successForeground: tryHexToHsl(canvas, "150 8% 6%"),
54
- },
55
- recipe: buildRecipe({
56
- buttonDefault,
57
- radii: {
58
- button: ctx.recipeHints.radii?.button ?? leafHex(ctx.radius, ["full"]) ?? "100px",
59
- card: leafHex(ctx.radius, ["lg"]) ?? "8px",
60
- },
61
- elevationPreset: ctx.recipeHints.elevationPreset ?? "none",
62
- density: (ctx.recipeHints.density ?? "comfortable") satisfies Density,
63
- outlineBorder: cream,
64
- primaryStrokeGradient: "linear-gradient(114.41deg, #0ae448 20.74%, #abff84 65.5%)",
65
- }),
66
- typography: {
67
- fontSans: `'Mori', 'Inter Tight', 'DM Sans', ui-sans-serif, system-ui, sans-serif`,
68
- fontDisplay: `'Mori', 'Inter Tight', ui-sans-serif, system-ui, sans-serif`,
69
- headingWeight: 600,
70
- faces: [
71
- {
72
- family: "Mori",
73
- // Placeholder — Create Center replaces with tenant-uploaded WOFF2
74
- src: [{ url: "/brand-assets/mori-regular.woff2", format: "woff2" }],
75
- weight: 400,
76
- display: "swap",
77
- },
78
- {
79
- family: "Mori",
80
- src: [{ url: "/brand-assets/mori-semibold.woff2", format: "woff2" }],
81
- weight: 600,
82
- display: "swap",
83
- },
84
- ],
85
- },
86
- zones: {
87
- product: {
88
- caption: { fontSize: "14px", lineHeight: 1.4, fontWeight: 400, letterSpacing: "-0.14px" },
89
- bodySm: { fontSize: "16px", lineHeight: 1.15, fontWeight: 400 },
90
- body: { fontSize: "19px", lineHeight: 1.15, fontWeight: 400 },
91
- bodyLg: { fontSize: "23px", lineHeight: 1.38, fontWeight: 400, letterSpacing: "-0.23px" },
92
- subheading: {
93
- fontSize: "34px",
94
- lineHeight: 1.2,
95
- fontWeight: 400,
96
- letterSpacing: "-0.34px",
97
- },
98
- heading: {
99
- fontSize: "44px",
100
- lineHeight: 1.2,
101
- fontWeight: 600,
102
- letterSpacing: "-0.44px",
103
- },
104
- },
105
- marketing: {
106
- body: { fontSize: "19px", lineHeight: 1.15, fontWeight: 400 },
107
- heading: {
108
- fontSize: "66px",
109
- lineHeight: 1.2,
110
- fontWeight: 600,
111
- letterSpacing: "-0.66px",
112
- },
113
- headingLg: {
114
- fontSize: "101px",
115
- lineHeight: 1,
116
- fontWeight: 600,
117
- letterSpacing: "-1.11px",
118
- },
119
- display: {
120
- fontSize: "224px",
121
- lineHeight: 0.9,
122
- fontWeight: 600,
123
- letterSpacing: "-4.48px",
124
- },
125
- },
126
- },
127
- extensions: {
128
- categories: {
129
- gsap: green,
130
- scroll: ctx.colors.pink ?? "#fec5fb",
131
- svg: ctx.colors.orangey ?? "#ff8709",
132
- text: ctx.colors.lilac ?? "#9d95ff",
133
- ui: ctx.colors.blue ?? "#00bae2",
134
- other: ctx.colors["light-green"] ?? "#abff84",
135
- },
136
- displaySizePx: 224,
137
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://gsap.com",
138
- notes: [
139
- "Outline-first product controls; category ctx.colors are marketing extensions.",
140
- "Replace typography.faces[].src with Create Center hosted ctx.font URLs.",
141
- 'Use data-zone="marketing" for hero/display; product zone for app chrome.',
142
- ],
143
- },
144
- };
145
- return brand;
146
- }
@@ -1,31 +0,0 @@
1
- import type { PresetId } from "../compile-helpers";
2
- import type { BrandPackage } from "../types";
3
- import type { CompileContext } from "./context";
4
- import { buildGeneric } from "./generic";
5
- import { buildGsap } from "./gsap";
6
- import { buildLinear } from "./linear";
7
- import { buildNotion } from "./notion";
8
- import { buildRaycast } from "./raycast";
9
- import { buildStripe } from "./stripe";
10
- import { buildVanta } from "./vanta";
11
- import { buildVercel } from "./vercel";
12
-
13
- export type { CompileContext } from "./context";
14
-
15
- const PRESET_BUILDERS: Record<
16
- Exclude<PresetId, "generic">,
17
- (ctx: CompileContext) => BrandPackage
18
- > = {
19
- linear: buildLinear,
20
- gsap: buildGsap,
21
- raycast: buildRaycast,
22
- vercel: buildVercel,
23
- notion: buildNotion,
24
- stripe: buildStripe,
25
- vanta: buildVanta,
26
- };
27
-
28
- export function buildPresetBrand(preset: PresetId, ctx: CompileContext): BrandPackage {
29
- if (preset === "generic") return buildGeneric(ctx);
30
- return PRESET_BUILDERS[preset](ctx);
31
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * linear design-language compile preset (stress fixture).
3
- * Dual-mode: dark void default + light monochrome paper (acid-lime CTA both modes).
4
- */
5
- import { tryHexToHsl } from "../hex-to-hsl";
6
- import type { BrandPackage, BrandSemanticColors } from "../types";
7
- import type { CompileContext } from "./context";
8
- import { buildRecipe } from "./recipe";
9
-
10
- function linearDarkSemantic(
11
- voidC: string,
12
- carbon: string,
13
- graphite: string,
14
- ash: string,
15
- paper: string,
16
- lime: string,
17
- coral: string,
18
- pulse: string,
19
- colors: Record<string, string>,
20
- ): BrandSemanticColors {
21
- return {
22
- background: tryHexToHsl(voidC, "210 11% 4%"),
23
- foreground: tryHexToHsl(paper, "0 0% 100%"),
24
- card: tryHexToHsl(carbon, "210 6% 6%"),
25
- cardForeground: tryHexToHsl(paper, "0 0% 100%"),
26
- popover: tryHexToHsl(colors.obsidian ?? "#161718", "210 5% 9%"),
27
- popoverForeground: tryHexToHsl(paper, "0 0% 100%"),
28
- primary: tryHexToHsl(lime, "66 89% 54%"),
29
- primaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
30
- secondary: tryHexToHsl(graphite, "220 7% 15%"),
31
- secondaryForeground: tryHexToHsl(colors.mist ?? "#d0d6e0", "220 20% 85%"),
32
- muted: tryHexToHsl(colors.obsidian ?? "#161718", "210 5% 9%"),
33
- mutedForeground: tryHexToHsl(ash, "220 5% 41%"),
34
- accent: tryHexToHsl(graphite, "220 7% 15%"),
35
- accentForeground: tryHexToHsl(lime, "66 89% 54%"),
36
- destructive: tryHexToHsl(coral, "0 79% 63%"),
37
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
38
- border: tryHexToHsl(graphite, "220 7% 15%"),
39
- input: tryHexToHsl(graphite, "220 7% 15%"),
40
- ring: tryHexToHsl(lime, "66 89% 54%"),
41
- success: tryHexToHsl(pulse, "136 61% 40%"),
42
- successForeground: tryHexToHsl(paper, "0 0% 100%"),
43
- info: tryHexToHsl(colors["signal-teal"] ?? "#02b8cc", "187 98% 40%"),
44
- infoForeground: tryHexToHsl(paper, "0 0% 100%"),
45
- };
46
- }
47
-
48
- /** Light shell — paper canvas, same acid-lime CTA, graphite chrome. */
49
- function linearLightSemantic(
50
- voidC: string,
51
- ash: string,
52
- paper: string,
53
- lime: string,
54
- coral: string,
55
- pulse: string,
56
- colors: Record<string, string>,
57
- ): BrandSemanticColors {
58
- return {
59
- background: "0 0% 100%",
60
- foreground: tryHexToHsl(voidC, "210 11% 4%"),
61
- card: "0 0% 98%",
62
- cardForeground: tryHexToHsl(voidC, "210 11% 4%"),
63
- popover: "0 0% 100%",
64
- popoverForeground: tryHexToHsl(voidC, "210 11% 4%"),
65
- primary: tryHexToHsl(lime, "66 89% 54%"),
66
- primaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
67
- secondary: "220 14% 96%",
68
- secondaryForeground: tryHexToHsl(voidC, "210 11% 4%"),
69
- muted: "220 14% 96%",
70
- mutedForeground: tryHexToHsl(ash, "220 5% 41%"),
71
- accent: "220 14% 96%",
72
- accentForeground: tryHexToHsl(voidC, "210 11% 4%"),
73
- destructive: tryHexToHsl(coral, "0 79% 63%"),
74
- destructiveForeground: tryHexToHsl(paper, "0 0% 100%"),
75
- border: "220 13% 91%",
76
- input: "0 0% 100%",
77
- ring: tryHexToHsl(lime, "66 89% 54%"),
78
- success: tryHexToHsl(pulse, "136 61% 40%"),
79
- successForeground: tryHexToHsl(paper, "0 0% 100%"),
80
- info: tryHexToHsl(colors["signal-teal"] ?? "#02b8cc", "187 98% 40%"),
81
- infoForeground: tryHexToHsl(paper, "0 0% 100%"),
82
- };
83
- }
84
-
85
- export function buildLinear(ctx: CompileContext): BrandPackage {
86
- const voidC = ctx.colors.void ?? ctx.colors["just-black"] ?? "#08090a";
87
- const carbon = ctx.colors.carbon ?? "#0f1011";
88
- const graphite = ctx.colors.graphite ?? "#23252a";
89
- const ash = ctx.colors.ash ?? "#62666d";
90
- const paper = ctx.colors.paper ?? "#ffffff";
91
- const lime = ctx.colors["acid-lime"] ?? "#e4f222";
92
- const coral = ctx.colors["coral-red"] ?? "#eb5757";
93
- const pulse = ctx.colors["pulse-green"] ?? "#27a644";
94
-
95
- const dark = linearDarkSemantic(
96
- voidC,
97
- carbon,
98
- graphite,
99
- ash,
100
- paper,
101
- lime,
102
- coral,
103
- pulse,
104
- ctx.colors,
105
- );
106
- const light = linearLightSemantic(voidC, ash, paper, lime, coral, pulse, ctx.colors);
107
-
108
- const brand: BrandPackage = {
109
- id: "linear",
110
- name: "Linear",
111
- darkDefault: true,
112
- version: "1.0.0",
113
- semantic: dark,
114
- modes: {
115
- dark: { semantic: dark },
116
- light: { semantic: light },
117
- },
118
- recipe: buildRecipe({
119
- buttonDefault: "solid",
120
- radii: { button: "6px", card: "12px" },
121
- elevationPreset: "soft",
122
- density: "compact",
123
- }),
124
- typography: {
125
- fontSans: `'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif`,
126
- fontMono: `'Berkeley Mono', 'JetBrains Mono', ui-monospace, monospace`,
127
- headingWeight: 510,
128
- faces: [
129
- {
130
- family: "Inter Variable",
131
- src: [
132
- {
133
- url: "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2",
134
- format: "woff2",
135
- },
136
- ],
137
- weight: "100 900",
138
- display: "swap",
139
- },
140
- ],
141
- },
142
- zones: {
143
- product: {
144
- caption: { fontSize: "13px", lineHeight: 1.2, fontWeight: 400 },
145
- bodySm: { fontSize: "14px", lineHeight: 1.5, fontWeight: 400 },
146
- body: { fontSize: "14px", lineHeight: 1.5, fontWeight: 400 },
147
- bodyLg: { fontSize: "16px", lineHeight: 1.5, fontWeight: 400 },
148
- heading: {
149
- fontSize: "24px",
150
- lineHeight: 1.25,
151
- fontWeight: 510,
152
- letterSpacing: "-0.012em",
153
- },
154
- display: {
155
- fontSize: "32px",
156
- lineHeight: 1.15,
157
- fontWeight: 510,
158
- letterSpacing: "-0.022em",
159
- },
160
- },
161
- marketing: {
162
- body: { fontSize: "15px", lineHeight: 1.6, fontWeight: 400 },
163
- heading: {
164
- fontSize: "48px",
165
- lineHeight: 1,
166
- fontWeight: 510,
167
- letterSpacing: "-0.022em",
168
- },
169
- display: {
170
- fontSize: "72px",
171
- lineHeight: 1,
172
- fontWeight: 510,
173
- letterSpacing: "-0.022em",
174
- },
175
- },
176
- },
177
- extensions: {
178
- sourceUrl: typeof ctx.refero.url === "string" ? ctx.refero.url : "https://linear.app",
179
- notes: ["Dual-mode: dark void default + light paper; acid-lime solid CTA in both modes."],
180
- },
181
- };
182
- return brand;
183
- }