@advantacode/brander 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AdvantaCode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,371 @@
1
+ # AdvantaCode Brander
2
+
3
+ AdvantaCode Brander is a design token generator that produces consistent branding tokens for modern web applications.
4
+
5
+ It converts a simple configuration into reusable outputs for multiple platforms including:
6
+
7
+ * CSS variables
8
+ * TypeScript tokens
9
+ * Tailwind presets
10
+ * Bootstrap / SCSS variables
11
+ * Figma tokens
12
+
13
+ AdvantaCode Brander uses OKLCH color space to generate perceptually consistent color scales.
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ npm install -D @advantacode/brander
19
+ npx --package @advantacode/brander advantacode-brander setup --out src/brander --style src/style.css
20
+ ```
21
+
22
+ This creates `brand.config.ts`, adds a `brand:generate` script, patches your stylesheet imports, and prepares the token output folder.
23
+
24
+ AdvantaCode Brander generates design tokens and framework adapters from a single brand configuration file. It allows applications, design systems, and design tools to share a consistent source of truth for colors and semantic tokens.
25
+
26
+ For architecture, development, testing, and publishing workflows, see [docs/TECH_OVERVIEW.md](docs/TECH_OVERVIEW.md).
27
+
28
+ ## Features
29
+
30
+ * Single source of truth for brand tokens
31
+ * OKLCH color scaling
32
+ * Multi-framework outputs
33
+ * CLI tool usable across any JavaScript stack
34
+ * Environment variable support
35
+ * Tailwind integration
36
+ * Design-tool exports for Figma
37
+
38
+ ## Installation
39
+
40
+ Node.js 20 or newer is required.
41
+
42
+ Recommended for app projects:
43
+
44
+ ```bash
45
+ npm install -D @advantacode/brander
46
+ ```
47
+
48
+ One-off usage with `npx`:
49
+
50
+ ```bash
51
+ npx --package @advantacode/brander advantacode-brander
52
+ ```
53
+
54
+ Global install:
55
+
56
+ ```bash
57
+ npm install -g @advantacode/brander
58
+ ```
59
+
60
+ Run:
61
+
62
+ ```bash
63
+ advantacode-brander
64
+ ```
65
+
66
+ ## CLI Usage
67
+
68
+ Examples:
69
+
70
+ ```text
71
+ advantacode-brander --help
72
+ advantacode-brander --version
73
+ advantacode-brander --out src/tokens
74
+ advantacode-brander --format css,tailwind,figma
75
+ advantacode-brander --theme dark
76
+ advantacode-brander --prefix ac
77
+ advantacode-brander setup --out src/brander --style src/style.css
78
+ advantacode-brander init --out src/brander
79
+ ```
80
+
81
+ Supported flags:
82
+
83
+ * `--out <dir>` writes generated files to a custom folder instead of `dist/brander`
84
+ * `--format <list>` limits output to specific formats: `all`, `css`, `json`, `typescript` or `ts`, `scss`, `tailwind`, `bootstrap`, `figma`
85
+ * `--theme <value>` limits theme CSS output to `light`, `dark`, or `both`
86
+ * `--prefix <value>` applies a CSS variable prefix like `ac`, producing variables such as `--ac-primary`
87
+ * `--version`, `-v` prints the installed package version
88
+ * `--help`, `-h` prints the CLI help text
89
+
90
+ Setup commands:
91
+
92
+ * `advantacode-brander setup` configures an existing app by creating `brand.config.ts` if needed, adding a `brand:generate` script, patching a stylesheet with token imports, and generating tokens
93
+ * `advantacode-brander init` runs the same setup flow for a freshly created app and is intended to be called by a higher-level scaffolder such as `advantacode-init`
94
+
95
+ ## Configuration
96
+
97
+ Create a `brand.config.ts` file in your project root.
98
+
99
+ Example:
100
+
101
+ ```ts
102
+ export default {
103
+ name: process.env.COMPANY_NAME || "My Company",
104
+ css: {
105
+ prefix: process.env.CSS_PREFIX ?? ""
106
+ },
107
+
108
+ colors: {
109
+ primary: process.env.PRIMARY_COLOR || "amber-500",
110
+ secondary: process.env.SECONDARY_COLOR || "zinc-700",
111
+ neutral: process.env.NEUTRAL_COLOR || process.env.SECONDARY_COLOR || "zinc-700",
112
+ accent: process.env.ACCENT_COLOR || "amber-400",
113
+ info: process.env.INFO_COLOR || "sky-500",
114
+ success: process.env.SUCCESS_COLOR || "green-500",
115
+ warning: process.env.WARNING_COLOR || "yellow-500",
116
+ danger: process.env.DANGER_COLOR || "red-500"
117
+ }
118
+ };
119
+ ```
120
+
121
+ Supported color inputs:
122
+
123
+ * Tailwind-style tokens like `amber-500`
124
+ * CSS color strings like `#f59e0b` or `rgb(245 158 11)`
125
+ * OKLCH values like `oklch(0.76859 0.164659 70.08)`
126
+
127
+ ## Environment Variables
128
+
129
+ Brander supports environment variables via `.env`.
130
+
131
+ Example:
132
+
133
+ ```dotenv
134
+ COMPANY_NAME="My Company"
135
+ CSS_PREFIX=
136
+ PRIMARY_COLOR=amber-500
137
+ SECONDARY_COLOR=zinc-700
138
+ NEUTRAL_COLOR=zinc-700
139
+ ACCENT_COLOR=amber-400
140
+ INFO_COLOR=sky-500
141
+ SUCCESS_COLOR=green-500
142
+ WARNING_COLOR=yellow-500
143
+ DANGER_COLOR=red-500
144
+ ```
145
+
146
+ ## Generated Outputs
147
+
148
+ Running the CLI with no flags generates all formats into `dist/brander` and writes both light and dark theme CSS.
149
+
150
+ ```text
151
+ dist/
152
+ brander/
153
+ tokens.css
154
+ tokens.scss
155
+ tokens.ts
156
+ tokens.json
157
+ metadata.json
158
+ themes/
159
+ light.css
160
+ dark.css
161
+ adapters/
162
+ tailwind.preset.ts
163
+ bootstrap.variables.scss
164
+ figma.tokens.json
165
+ ```
166
+
167
+ ## CSS Variables
168
+
169
+ Example generated `tokens.css`:
170
+
171
+ ```css
172
+ :root {
173
+ --primary-500: oklch(0.65 0.2 45);
174
+ --neutral-50: oklch(0.97 0.02 95);
175
+ }
176
+ ```
177
+
178
+ Usage:
179
+
180
+ ```css
181
+ :root {
182
+ --background: var(--neutral-50);
183
+ --text: var(--neutral-950);
184
+ }
185
+ ```
186
+
187
+ By default, Brander emits unprefixed variables for broader compatibility. If you want namespaced output, use `css.prefix` in `brand.config.ts`, `CSS_PREFIX` in `.env`, or `--prefix ac` on the CLI.
188
+
189
+ ## Theme Tokens
190
+
191
+ Example:
192
+
193
+ ```css
194
+ :root {
195
+ --background: var(--neutral-50);
196
+ --surface: var(--neutral-100);
197
+ --text: var(--neutral-950);
198
+ --muted: var(--neutral-100);
199
+ --card: var(--neutral-50);
200
+ --border: var(--neutral-200);
201
+ --primary: var(--primary-600);
202
+ --primary-foreground: var(--neutral-50);
203
+ }
204
+
205
+ [data-theme="dark"] {
206
+ --background: var(--neutral-950);
207
+ --surface: var(--neutral-900);
208
+ --text: var(--neutral-50);
209
+ }
210
+ ```
211
+
212
+ Core semantic tokens include `background`, `surface`, `text`, `muted`, `card`, `popover`, `border`, `input`, `ring`, `primary`, `secondary`, `accent`, `info`, `success`, `warning`, and `danger`, each with matching foreground tokens where appropriate.
213
+
214
+ ## TypeScript Tokens
215
+
216
+ Generated file:
217
+
218
+ ```text
219
+ dist/brander/tokens.ts
220
+ ```
221
+
222
+ Usage:
223
+
224
+ ```ts
225
+ import { tokens, metadata } from "./dist/brander/tokens";
226
+
227
+ console.log(tokens.color.semantic.light.primary.value);
228
+ console.log(metadata.adapters);
229
+ ```
230
+
231
+ This output is intended for typed apps, scripts, and build-time tooling.
232
+
233
+ ## SCSS Tokens
234
+
235
+ Generated file:
236
+
237
+ ```text
238
+ dist/brander/tokens.scss
239
+ ```
240
+
241
+ Usage:
242
+
243
+ ```scss
244
+ @use "./dist/brander/tokens.scss" as tokens;
245
+
246
+ .button {
247
+ background: tokens.$primary;
248
+ color: tokens.$primary-foreground;
249
+ }
250
+ ```
251
+
252
+ Light semantic tokens are emitted as Sass variables like `$background`, and dark semantic tokens are emitted as `$dark-background`. Prefixing works here too when configured.
253
+
254
+ ## Tailwind Integration
255
+
256
+ Generated preset:
257
+
258
+ ```text
259
+ dist/brander/adapters/tailwind.preset.ts
260
+ ```
261
+
262
+ Usage:
263
+
264
+ ```ts
265
+ import preset from "./dist/brander/adapters/tailwind.preset";
266
+
267
+ export default {
268
+ presets: [preset]
269
+ };
270
+ ```
271
+
272
+ Use tokens in Tailwind:
273
+
274
+ ```text
275
+ bg-primary
276
+ text-danger
277
+ border-secondary
278
+ ```
279
+
280
+ ## Bootstrap / SCSS Frameworks
281
+
282
+ Generated file:
283
+
284
+ ```text
285
+ dist/brander/adapters/bootstrap.variables.scss
286
+ ```
287
+
288
+ Example output:
289
+
290
+ ```scss
291
+ @use "../tokens.scss" as tokens;
292
+
293
+ $primary: tokens.$primary;
294
+ $secondary: tokens.$secondary;
295
+ $info: tokens.$info;
296
+ ```
297
+
298
+ ## Figma Token Export
299
+
300
+ Generated:
301
+
302
+ ```text
303
+ dist/brander/adapters/figma.tokens.json
304
+ ```
305
+
306
+ Example:
307
+
308
+ ```json
309
+ {
310
+ "color": {
311
+ "primary": {
312
+ "value": "oklch(0.65 0.2 45)"
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ This allows importing tokens into design tools.
319
+
320
+ ## Metadata
321
+
322
+ Generated file:
323
+
324
+ ```text
325
+ dist/brander/metadata.json
326
+ ```
327
+
328
+ Example:
329
+
330
+ ```json
331
+ {
332
+ "version": "0.1.0",
333
+ "generated": "2026-03-08T17:47:26.019Z",
334
+ "themes": ["light", "dark"],
335
+ "adapters": ["tailwind", "bootstrap", "figma"],
336
+ "artifacts": ["tokens.css", "themes/light.css", "themes/dark.css"],
337
+ "cssPrefix": ""
338
+ }
339
+ ```
340
+
341
+ ## Ecosystem
342
+
343
+ AdvantaCode Brander is part of the AdvantaCode ecosystem.
344
+
345
+ ```text
346
+ @advantacode/brander
347
+ advantacode-init
348
+ advantacode-starter
349
+ ```
350
+
351
+ This allows developers to bootstrap fully branded applications with a single command.
352
+
353
+ ## Contributing
354
+
355
+ AdvantaCode Brander is maintained under a closed governance model.
356
+
357
+ Issues and feature requests are welcome, but pull requests may not be accepted.
358
+
359
+ See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for details.
360
+
361
+ ## Trademark Notice
362
+
363
+ `AdvantaCode` and `AdvantaCode Brander` are trademarks of AdvantaCode.
364
+
365
+ The MIT license for this package does not grant permission to use the AdvantaCode name, logos, package names, domains, or branding, or to imply endorsement or affiliation.
366
+
367
+ See [docs/TRADEMARKS.md](docs/TRADEMARKS.md) for the trademark policy.
368
+
369
+ ## License
370
+
371
+ MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,39 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { scaleSteps } from "../engine/palette.js";
4
+ import { semanticTokenNames } from "../engine/semantics.js";
5
+ import { getVariableName, getVariableReference } from "./variables.js";
6
+ export function writeCssArtifacts(outputDir, tokenModel, theme, variableOptions) {
7
+ const themesDir = path.join(outputDir, "themes");
8
+ const writtenArtifacts = ["tokens.css"];
9
+ fs.mkdirSync(themesDir, { recursive: true });
10
+ fs.writeFileSync(path.join(outputDir, "tokens.css"), renderPrimitiveTokens(tokenModel, variableOptions));
11
+ if (theme === "light" || theme === "both") {
12
+ fs.writeFileSync(path.join(themesDir, "light.css"), renderThemeCss(":root", tokenModel, "light", variableOptions));
13
+ writtenArtifacts.push("themes/light.css");
14
+ }
15
+ if (theme === "dark" || theme === "both") {
16
+ fs.writeFileSync(path.join(themesDir, "dark.css"), renderThemeCss('[data-theme="dark"]', tokenModel, "dark", variableOptions));
17
+ writtenArtifacts.push("themes/dark.css");
18
+ }
19
+ return writtenArtifacts;
20
+ }
21
+ function renderPrimitiveTokens(tokenModel, variableOptions) {
22
+ let css = ":root {\n";
23
+ for (const [colorName, scale] of Object.entries(tokenModel.color.primitive)) {
24
+ for (const step of scaleSteps) {
25
+ css += ` ${getVariableName(`${colorName}-${step}`, variableOptions)}: ${scale[step]};\n`;
26
+ }
27
+ }
28
+ css += "}\n";
29
+ return css;
30
+ }
31
+ function renderThemeCss(selector, tokenModel, themeName, variableOptions) {
32
+ let css = `${selector} {\n`;
33
+ for (const semanticTokenName of semanticTokenNames) {
34
+ const token = tokenModel.color.semantic[themeName][semanticTokenName];
35
+ css += ` ${getVariableName(semanticTokenName, variableOptions)}: ${getVariableReference(token.ref, variableOptions)};\n`;
36
+ }
37
+ css += "}\n";
38
+ return css;
39
+ }
@@ -0,0 +1,28 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { scaleSteps } from "../engine/palette.js";
4
+ import { semanticTokenNames, themeNames } from "../engine/semantics.js";
5
+ export function writeFigmaAdapter(outputDir, tokenModel) {
6
+ const adaptersDir = path.join(outputDir, "adapters");
7
+ const figmaTokens = {
8
+ color: {
9
+ primitive: Object.fromEntries(Object.entries(tokenModel.color.primitive).map(([colorName, scale]) => [
10
+ colorName,
11
+ Object.fromEntries(scaleSteps.map((step) => [step, { value: scale[step] }]))
12
+ ])),
13
+ semantic: Object.fromEntries(themeNames.map((themeName) => [
14
+ themeName,
15
+ Object.fromEntries(semanticTokenNames.map((semanticTokenName) => [
16
+ semanticTokenName,
17
+ {
18
+ value: tokenModel.color.semantic[themeName][semanticTokenName].value,
19
+ ref: tokenModel.color.semantic[themeName][semanticTokenName].ref
20
+ }
21
+ ]))
22
+ ]))
23
+ }
24
+ };
25
+ fs.mkdirSync(adaptersDir, { recursive: true });
26
+ fs.writeFileSync(path.join(adaptersDir, "figma.tokens.json"), JSON.stringify(figmaTokens, null, 2));
27
+ return ["adapters/figma.tokens.json"];
28
+ }
@@ -0,0 +1,10 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function writeTokenModelJson(outputDir, tokenModel) {
4
+ fs.writeFileSync(path.join(outputDir, "tokens.json"), JSON.stringify(tokenModel, null, 2));
5
+ return ["tokens.json"];
6
+ }
7
+ export function writeMetadataJson(outputDir, metadata) {
8
+ fs.writeFileSync(path.join(outputDir, "metadata.json"), JSON.stringify(metadata, null, 2));
9
+ return ["metadata.json"];
10
+ }
@@ -0,0 +1,59 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { scaleSteps } from "../engine/palette.js";
4
+ import { semanticTokenNames } from "../engine/semantics.js";
5
+ import { getSassVariableName } from "./variables.js";
6
+ export function writeScssArtifacts(outputDir, tokenModel, options) {
7
+ const adaptersDir = path.join(outputDir, "adapters");
8
+ const writtenArtifacts = [];
9
+ const tokensScss = renderTokensScss(tokenModel, options.variableOptions);
10
+ const bootstrapScss = `@use "../tokens.scss" as tokens;
11
+
12
+ $body-bg: tokens.${getSassVariableName("background", options.variableOptions)};
13
+ $body-color: tokens.${getSassVariableName("text", options.variableOptions)};
14
+ $border-color: tokens.${getSassVariableName("border", options.variableOptions)};
15
+ $primary: tokens.${getSassVariableName("primary", options.variableOptions)};
16
+ $secondary: tokens.${getSassVariableName("secondary", options.variableOptions)};
17
+ $info: tokens.${getSassVariableName("info", options.variableOptions)};
18
+ $success: tokens.${getSassVariableName("success", options.variableOptions)};
19
+ $warning: tokens.${getSassVariableName("warning", options.variableOptions)};
20
+ $danger: tokens.${getSassVariableName("danger", options.variableOptions)};
21
+ $card-bg: tokens.${getSassVariableName("surface", options.variableOptions)};
22
+ `;
23
+ fs.mkdirSync(adaptersDir, { recursive: true });
24
+ if (options.includeTokensScss || options.includeBootstrapAdapter) {
25
+ fs.writeFileSync(path.join(outputDir, "tokens.scss"), tokensScss);
26
+ writtenArtifacts.push("tokens.scss");
27
+ }
28
+ if (options.includeBootstrapAdapter) {
29
+ fs.writeFileSync(path.join(adaptersDir, "bootstrap.variables.scss"), bootstrapScss);
30
+ writtenArtifacts.push("adapters/bootstrap.variables.scss");
31
+ }
32
+ return writtenArtifacts;
33
+ }
34
+ function renderTokensScss(tokenModel, variableOptions) {
35
+ let scss = "";
36
+ for (const [colorName, scale] of Object.entries(tokenModel.color.primitive)) {
37
+ for (const step of scaleSteps) {
38
+ scss += `${getSassVariableName(`${colorName}-${step}`, variableOptions)}: ${scale[step]};\n`;
39
+ }
40
+ }
41
+ scss += "\n";
42
+ for (const semanticTokenName of semanticTokenNames) {
43
+ scss += `${getSassVariableName(semanticTokenName, variableOptions)}: ${tokenModel.color.semantic.light[semanticTokenName].value};\n`;
44
+ }
45
+ scss += `\n${getSassVariableName("theme-light", variableOptions)}: (\n`;
46
+ for (const semanticTokenName of semanticTokenNames) {
47
+ scss += ` "${semanticTokenName}": ${getSassVariableName(semanticTokenName, variableOptions)},\n`;
48
+ }
49
+ scss += ");\n\n";
50
+ for (const semanticTokenName of semanticTokenNames) {
51
+ scss += `${getSassVariableName(`dark-${semanticTokenName}`, variableOptions)}: ${tokenModel.color.semantic.dark[semanticTokenName].value};\n`;
52
+ }
53
+ scss += `\n${getSassVariableName("theme-dark", variableOptions)}: (\n`;
54
+ for (const semanticTokenName of semanticTokenNames) {
55
+ scss += ` "${semanticTokenName}": ${getSassVariableName(`dark-${semanticTokenName}`, variableOptions)},\n`;
56
+ }
57
+ scss += ");\n";
58
+ return scss;
59
+ }
@@ -0,0 +1,18 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { semanticTokenNames } from "../engine/semantics.js";
4
+ import { getVariableReference } from "./variables.js";
5
+ export function writeTailwindAdapter(outputDir, tokenModel, variableOptions) {
6
+ const adaptersDir = path.join(outputDir, "adapters");
7
+ let preset = `export default {\n theme: {\n extend: {\n colors: {\n`;
8
+ fs.mkdirSync(adaptersDir, { recursive: true });
9
+ for (const semanticTokenName of semanticTokenNames) {
10
+ if (!tokenModel.color.semantic.light[semanticTokenName]) {
11
+ continue;
12
+ }
13
+ preset += ` "${semanticTokenName}": "${getVariableReference(semanticTokenName, variableOptions)}",\n`;
14
+ }
15
+ preset += " }\n }\n }\n};\n";
16
+ fs.writeFileSync(path.join(adaptersDir, "tailwind.preset.ts"), preset);
17
+ return ["adapters/tailwind.preset.ts"];
18
+ }
@@ -0,0 +1,13 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function writeTypeScriptArtifacts(outputDir, tokenModel, metadata) {
4
+ const source = `export const metadata = ${JSON.stringify(metadata, null, 2)} as const;
5
+
6
+ export const tokens = ${JSON.stringify(tokenModel, null, 2)} as const;
7
+
8
+ export type Metadata = typeof metadata;
9
+ export type Tokens = typeof tokens;
10
+ `;
11
+ fs.writeFileSync(path.join(outputDir, "tokens.ts"), source);
12
+ return ["tokens.ts"];
13
+ }
@@ -0,0 +1,21 @@
1
+ export function normalizeVariablePrefix(prefix) {
2
+ if (!prefix) {
3
+ return "";
4
+ }
5
+ return prefix.trim().replace(/^-+/, "").replace(/-+$/, "");
6
+ }
7
+ export function getVariableName(tokenName, options) {
8
+ const normalizedTokenName = tokenName.replace(".", "-");
9
+ return options.prefix
10
+ ? `--${options.prefix}-${normalizedTokenName}`
11
+ : `--${normalizedTokenName}`;
12
+ }
13
+ export function getVariableReference(tokenName, options) {
14
+ return `var(${getVariableName(tokenName, options)})`;
15
+ }
16
+ export function getSassVariableName(tokenName, options) {
17
+ const normalizedTokenName = tokenName.replace(".", "-");
18
+ return options.prefix
19
+ ? `$${options.prefix}-${normalizedTokenName}`
20
+ : `$${normalizedTokenName}`;
21
+ }
@@ -0,0 +1,64 @@
1
+ import { converter } from "culori";
2
+ import { tailwindColors } from "../tailwind-colors.js";
3
+ const toOklch = converter("oklch");
4
+ export const baseColorNames = [
5
+ "primary",
6
+ "secondary",
7
+ "accent",
8
+ "info",
9
+ "success",
10
+ "warning",
11
+ "danger",
12
+ "neutral"
13
+ ];
14
+ const defaultBaseColors = {
15
+ primary: "amber-500",
16
+ secondary: "zinc-700",
17
+ accent: "amber-400",
18
+ info: "sky-500",
19
+ success: "green-500",
20
+ warning: "yellow-500",
21
+ danger: "red-500",
22
+ neutral: "zinc-700"
23
+ };
24
+ export function resolveBaseColors(colors) {
25
+ const mergedColors = {
26
+ ...defaultBaseColors,
27
+ ...colors,
28
+ neutral: colors.neutral ?? colors.secondary ?? defaultBaseColors.neutral
29
+ };
30
+ return Object.fromEntries(Object.entries(mergedColors).map(([colorName, colorValue]) => [colorName, normalizeColorValue(colorName, colorValue)]));
31
+ }
32
+ function normalizeColorValue(colorName, colorValue) {
33
+ const resolvedColorValue = resolveTailwindColorToken(colorValue) ?? colorValue;
34
+ const oklchColor = toOklch(resolvedColorValue);
35
+ if (!oklchColor) {
36
+ throw new Error(`Unable to parse color "${colorName}" with value "${colorValue}". Use a valid CSS color, OKLCH string, or Tailwind-style token like "amber-500".`);
37
+ }
38
+ return formatOklchColor(oklchColor);
39
+ }
40
+ function resolveTailwindColorToken(colorValue) {
41
+ const match = colorValue.match(/^([a-z]+)-(\d{2,3})$/);
42
+ if (!match) {
43
+ return undefined;
44
+ }
45
+ const [, colorName, rawScale] = match;
46
+ const colorScale = Number(rawScale);
47
+ const palette = tailwindColors[colorName];
48
+ return palette?.[colorScale];
49
+ }
50
+ function formatOklchColor(color) {
51
+ const lightness = roundComponent(color.l, 6) ?? "none";
52
+ const chroma = roundComponent(color.c, 6) ?? "none";
53
+ const hue = roundComponent(color.h, 3) ?? "none";
54
+ const alpha = roundComponent(color.alpha, 3);
55
+ return alpha !== undefined && alpha < 1
56
+ ? `oklch(${lightness} ${chroma} ${hue} / ${alpha})`
57
+ : `oklch(${lightness} ${chroma} ${hue})`;
58
+ }
59
+ function roundComponent(value, precision) {
60
+ if (value === undefined) {
61
+ return undefined;
62
+ }
63
+ return Number(value.toFixed(precision));
64
+ }