@forgespace/branding-mcp 0.7.0 → 0.7.1

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 (92) hide show
  1. package/README.md +41 -5
  2. package/dist/index.js +1 -1
  3. package/package.json +20 -3
  4. package/server.json +29 -0
  5. package/.env.example +0 -3
  6. package/.github/CODEOWNERS +0 -1
  7. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  8. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -22
  9. package/.github/PULL_REQUEST_TEMPLATE.md +0 -22
  10. package/.github/dependabot.yml +0 -17
  11. package/.github/workflows/ci.yml +0 -55
  12. package/.github/workflows/codeql.yml +0 -47
  13. package/.github/workflows/release-automation.yml +0 -56
  14. package/.github/workflows/secret-scan.yml +0 -13
  15. package/.github/workflows/security-scan.yml +0 -37
  16. package/.github/workflows/semgrep.yml +0 -12
  17. package/.github/workflows/trivy.yml +0 -17
  18. package/.gitleaks.toml +0 -14
  19. package/.husky/commit-msg +0 -1
  20. package/.nvmrc +0 -1
  21. package/.prettierrc.json +0 -10
  22. package/CHANGELOG.md +0 -114
  23. package/CONTRIBUTING.md +0 -203
  24. package/catalog-info.yaml +0 -16
  25. package/commitlint.config.cjs +0 -24
  26. package/data/README.md +0 -13
  27. package/docs/API.md +0 -110
  28. package/docs/DATA_SOURCES.md +0 -69
  29. package/docs/INTEGRATION.md +0 -58
  30. package/eslint.config.js +0 -52
  31. package/jest.config.js +0 -40
  32. package/knip.json +0 -10
  33. package/src/__tests__/integration/brand-generation.test.ts +0 -84
  34. package/src/__tests__/integration/mcp-server.test.ts +0 -18
  35. package/src/__tests__/unit/ai-interpreter.test.ts +0 -172
  36. package/src/__tests__/unit/border-system.test.ts +0 -77
  37. package/src/__tests__/unit/color-palette.test.ts +0 -161
  38. package/src/__tests__/unit/contrast-checker.test.ts +0 -124
  39. package/src/__tests__/unit/design-system-tool.test.ts +0 -176
  40. package/src/__tests__/unit/design-tokens.test.ts +0 -184
  41. package/src/__tests__/unit/favicon-generator.test.ts +0 -80
  42. package/src/__tests__/unit/gradient-system.test.ts +0 -122
  43. package/src/__tests__/unit/logo-generator.test.ts +0 -146
  44. package/src/__tests__/unit/motion-system.test.ts +0 -91
  45. package/src/__tests__/unit/og-image-generator.test.ts +0 -115
  46. package/src/__tests__/unit/shadow-system.test.ts +0 -63
  47. package/src/__tests__/unit/spacing-scale.test.ts +0 -60
  48. package/src/__tests__/unit/typography-system.test.ts +0 -71
  49. package/src/index.ts +0 -76
  50. package/src/lib/branding-core/ai/brand-interpreter.ts +0 -30
  51. package/src/lib/branding-core/ai/claude-interpreter.ts +0 -76
  52. package/src/lib/branding-core/ai/intent-applier.ts +0 -59
  53. package/src/lib/branding-core/ai/keyword-interpreter.ts +0 -95
  54. package/src/lib/branding-core/ai/prompts.ts +0 -93
  55. package/src/lib/branding-core/ai/types.ts +0 -36
  56. package/src/lib/branding-core/documents/html-generator.ts +0 -32
  57. package/src/lib/branding-core/documents/pdf-generator.ts +0 -21
  58. package/src/lib/branding-core/exporters/css-variables.ts +0 -71
  59. package/src/lib/branding-core/exporters/design-tokens.ts +0 -86
  60. package/src/lib/branding-core/exporters/figma-tokens.ts +0 -87
  61. package/src/lib/branding-core/exporters/react-theme.ts +0 -69
  62. package/src/lib/branding-core/exporters/sass-variables.ts +0 -74
  63. package/src/lib/branding-core/exporters/tailwind-preset.ts +0 -67
  64. package/src/lib/branding-core/generators/border-system.ts +0 -41
  65. package/src/lib/branding-core/generators/color-palette.ts +0 -147
  66. package/src/lib/branding-core/generators/favicon-generator.ts +0 -33
  67. package/src/lib/branding-core/generators/gradient-system.ts +0 -120
  68. package/src/lib/branding-core/generators/logo-generator.ts +0 -152
  69. package/src/lib/branding-core/generators/motion-system.ts +0 -98
  70. package/src/lib/branding-core/generators/og-image-generator.ts +0 -97
  71. package/src/lib/branding-core/generators/shadow-system.ts +0 -66
  72. package/src/lib/branding-core/generators/spacing-scale.ts +0 -29
  73. package/src/lib/branding-core/generators/typography-system.ts +0 -128
  74. package/src/lib/branding-core/index.ts +0 -28
  75. package/src/lib/branding-core/validators/brand-consistency.ts +0 -79
  76. package/src/lib/branding-core/validators/contrast-checker.ts +0 -37
  77. package/src/lib/branding-core/validators/token-schema.ts +0 -50
  78. package/src/lib/config.ts +0 -13
  79. package/src/lib/logger.ts +0 -12
  80. package/src/lib/types.ts +0 -236
  81. package/src/resources/brand-knowledge.ts +0 -60
  82. package/src/resources/brand-templates.ts +0 -385
  83. package/src/tools/create-brand-guidelines.ts +0 -94
  84. package/src/tools/export-design-tokens.ts +0 -52
  85. package/src/tools/generate-brand-assets.ts +0 -48
  86. package/src/tools/generate-brand-identity.ts +0 -115
  87. package/src/tools/generate-color-palette.ts +0 -43
  88. package/src/tools/generate-design-system.ts +0 -163
  89. package/src/tools/generate-typography-system.ts +0 -42
  90. package/src/tools/refine-brand-element.ts +0 -65
  91. package/src/tools/validate-brand-consistency.ts +0 -32
  92. package/tsconfig.json +0 -21
@@ -1,76 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
- import type { BrandIntent, InterpreterOptions } from './types.js';
3
- import { buildRefinementPrompt, buildGenerationPrompt } from './prompts.js';
4
- import { logger } from '../../logger.js';
5
-
6
- async function callClaude(prompt: string, options: Required<InterpreterOptions>): Promise<string> {
7
- const response = await fetch('https://api.anthropic.com/v1/messages', {
8
- method: 'POST',
9
- headers: {
10
- 'Content-Type': 'application/json',
11
- 'x-api-key': options.anthropicApiKey,
12
- 'anthropic-version': '2023-06-01',
13
- },
14
- body: JSON.stringify({
15
- model: options.model,
16
- max_tokens: options.maxTokens,
17
- messages: [{ role: 'user', content: prompt }],
18
- }),
19
- });
20
-
21
- if (!response.ok) {
22
- const body = await response.text();
23
- throw new Error(`Anthropic API error ${response.status}: ${body}`);
24
- }
25
-
26
- const data = (await response.json()) as {
27
- content: Array<{ type: string; text: string }>;
28
- };
29
- const textBlock = data.content.find((b) => b.type === 'text');
30
- if (!textBlock) throw new Error('No text content in Anthropic response');
31
- return textBlock.text;
32
- }
33
-
34
- function parseIntentJson(raw: string): BrandIntent {
35
- const jsonMatch = raw.match(/\{[\s\S]*\}/);
36
- if (!jsonMatch) throw new Error('No JSON object found in AI response');
37
- return JSON.parse(jsonMatch[0]) as BrandIntent;
38
- }
39
-
40
- export async function interpretWithClaude(
41
- feedback: string,
42
- element: 'colors' | 'typography' | 'spacing',
43
- currentBrand: BrandIdentity,
44
- options: InterpreterOptions
45
- ): Promise<BrandIntent> {
46
- const opts: Required<InterpreterOptions> = {
47
- anthropicApiKey: options.anthropicApiKey ?? '',
48
- model: options.model ?? 'claude-sonnet-4-20250514',
49
- maxTokens: options.maxTokens ?? 512,
50
- };
51
-
52
- const prompt = buildRefinementPrompt(feedback, element, currentBrand);
53
- logger.info({ element, model: opts.model }, 'Interpreting feedback with Claude');
54
-
55
- const raw = await callClaude(prompt, opts);
56
- return parseIntentJson(raw);
57
- }
58
-
59
- export async function suggestBrandWithClaude(
60
- brandName: string,
61
- industry: string,
62
- description: string,
63
- options: InterpreterOptions
64
- ): Promise<BrandIntent> {
65
- const opts: Required<InterpreterOptions> = {
66
- anthropicApiKey: options.anthropicApiKey ?? '',
67
- model: options.model ?? 'claude-sonnet-4-20250514',
68
- maxTokens: options.maxTokens ?? 512,
69
- };
70
-
71
- const prompt = buildGenerationPrompt(brandName, industry, description);
72
- logger.info({ brandName, model: opts.model }, 'Generating brand suggestions with Claude');
73
-
74
- const raw = await callClaude(prompt, opts);
75
- return parseIntentJson(raw);
76
- }
@@ -1,59 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
- import type { BrandIntent } from './types.js';
3
- import { generateColorPalette, hexToHsl, hslToHex } from '../generators/color-palette.js';
4
- import { generateTypographySystem } from '../generators/typography-system.js';
5
-
6
- function clamp(value: number, min: number, max: number): number {
7
- return Math.max(min, Math.min(max, value));
8
- }
9
-
10
- function applyColorShifts(
11
- baseHex: string,
12
- hueShift: number,
13
- saturationShift: number,
14
- lightnessShift: number
15
- ): string {
16
- const hsl = hexToHsl(baseHex);
17
- return hslToHex(
18
- (hsl.h + hueShift + 360) % 360,
19
- clamp(hsl.s + saturationShift, 0, 100),
20
- clamp(hsl.l + lightnessShift, 5, 95)
21
- );
22
- }
23
-
24
- export function applyIntent(brand: BrandIdentity, intent: BrandIntent): BrandIdentity {
25
- const updated = { ...brand };
26
-
27
- if (intent.color) {
28
- const { baseColor, harmony, theme, hueShift, saturationShift, lightnessShift } = intent.color;
29
-
30
- let effectiveBase = baseColor ?? brand.colors.primary.hex;
31
-
32
- if (hueShift || saturationShift || lightnessShift) {
33
- effectiveBase = applyColorShifts(
34
- effectiveBase,
35
- hueShift ?? 0,
36
- saturationShift ?? 0,
37
- lightnessShift ?? 0
38
- );
39
- }
40
-
41
- updated.colors = generateColorPalette(effectiveBase, harmony ?? 'complementary', theme);
42
- }
43
-
44
- if (intent.typography) {
45
- const { headingCategory, bodyCategory, scaleRatio, baseSize } = intent.typography;
46
- updated.typography = generateTypographySystem(
47
- (headingCategory ?? brand.typography.headingFont.includes('serif')) ? 'serif' : 'sans-serif',
48
- bodyCategory ?? 'sans-serif',
49
- scaleRatio,
50
- baseSize ?? brand.typography.baseSize
51
- );
52
- }
53
-
54
- if (intent.style) {
55
- updated.style = intent.style;
56
- }
57
-
58
- return updated;
59
- }
@@ -1,95 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
- import type { BrandIntent, ColorIntent, TypographyIntent } from './types.js';
3
-
4
- const COLOR_KEYWORDS: Record<string, ColorIntent> = {
5
- warm: { harmony: 'analogous', hueShift: 15 },
6
- cool: { harmony: 'analogous', hueShift: -15 },
7
- subtle: { harmony: 'analogous', saturationShift: -15 },
8
- vibrant: { harmony: 'triadic', saturationShift: 15 },
9
- bold: { harmony: 'complementary', saturationShift: 10 },
10
- muted: { harmony: 'monochromatic', saturationShift: -20 },
11
- dark: { theme: 'dark', lightnessShift: -15 },
12
- light: { theme: 'light', lightnessShift: 15 },
13
- monochromatic: { harmony: 'monochromatic' },
14
- complementary: { harmony: 'complementary' },
15
- analogous: { harmony: 'analogous' },
16
- triadic: { harmony: 'triadic' },
17
- premium: { harmony: 'analogous', saturationShift: -10, lightnessShift: -10 },
18
- playful: { harmony: 'triadic', saturationShift: 15, lightnessShift: 5 },
19
- corporate: { harmony: 'complementary', saturationShift: -10 },
20
- organic: { harmony: 'analogous', hueShift: 30 },
21
- elegant: { harmony: 'monochromatic', saturationShift: -15, lightnessShift: -5 },
22
- };
23
-
24
- const TYPOGRAPHY_KEYWORDS: Record<string, TypographyIntent> = {
25
- serif: { headingCategory: 'serif' },
26
- 'sans-serif': { headingCategory: 'sans-serif' },
27
- mono: { bodyCategory: 'monospace' },
28
- monospace: { bodyCategory: 'monospace' },
29
- display: { headingCategory: 'display' },
30
- handwritten: { headingCategory: 'handwriting' },
31
- compact: { scaleRatio: 'minor-second' },
32
- tight: { scaleRatio: 'major-second' },
33
- spacious: { scaleRatio: 'perfect-fourth' },
34
- dramatic: { scaleRatio: 'golden-ratio' },
35
- larger: { baseSize: 18 },
36
- smaller: { baseSize: 14 },
37
- editorial: { headingCategory: 'serif', scaleRatio: 'perfect-fourth' },
38
- technical: { bodyCategory: 'monospace', headingCategory: 'sans-serif' },
39
- modern: { headingCategory: 'sans-serif', bodyCategory: 'sans-serif' },
40
- classic: { headingCategory: 'serif', bodyCategory: 'serif' },
41
- };
42
-
43
- function mergeColorIntents(intents: ColorIntent[]): ColorIntent {
44
- const merged: ColorIntent = {};
45
- for (const intent of intents) {
46
- if (intent.harmony) merged.harmony = intent.harmony;
47
- if (intent.theme) merged.theme = intent.theme;
48
- if (intent.baseColor) merged.baseColor = intent.baseColor;
49
- merged.saturationShift = (merged.saturationShift ?? 0) + (intent.saturationShift ?? 0);
50
- merged.lightnessShift = (merged.lightnessShift ?? 0) + (intent.lightnessShift ?? 0);
51
- merged.hueShift = (merged.hueShift ?? 0) + (intent.hueShift ?? 0);
52
- }
53
- return merged;
54
- }
55
-
56
- export function interpretWithKeywords(
57
- feedback: string,
58
- element: 'colors' | 'typography' | 'spacing',
59
- _currentBrand?: BrandIdentity
60
- ): BrandIntent {
61
- const lower = feedback.toLowerCase();
62
-
63
- if (element === 'colors') {
64
- const matched: ColorIntent[] = [];
65
- for (const [keyword, intent] of Object.entries(COLOR_KEYWORDS)) {
66
- if (lower.includes(keyword)) {
67
- matched.push(intent);
68
- }
69
- }
70
- return {
71
- color: matched.length > 0 ? mergeColorIntents(matched) : { harmony: 'complementary' },
72
- reasoning: `Keyword match: ${matched.length > 0 ? 'matched color keywords' : 'default fallback'}`,
73
- };
74
- }
75
-
76
- if (element === 'typography') {
77
- const matched: TypographyIntent[] = [];
78
- for (const [keyword, intent] of Object.entries(TYPOGRAPHY_KEYWORDS)) {
79
- if (lower.includes(keyword)) {
80
- matched.push(intent);
81
- }
82
- }
83
- const merged: TypographyIntent = {};
84
- for (const intent of matched) {
85
- Object.assign(merged, intent);
86
- }
87
- return {
88
- typography:
89
- Object.keys(merged).length > 0 ? merged : { headingCategory: 'sans-serif' as const },
90
- reasoning: `Keyword match: ${matched.length > 0 ? 'matched typography keywords' : 'default fallback'}`,
91
- };
92
- }
93
-
94
- return { reasoning: 'No actionable keywords found for spacing refinement' };
95
- }
@@ -1,93 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
-
3
- export function buildRefinementPrompt(
4
- feedback: string,
5
- element: 'colors' | 'typography' | 'spacing',
6
- currentBrand: BrandIdentity
7
- ): string {
8
- const brandContext = [
9
- `Brand: ${currentBrand.name}`,
10
- `Industry: ${currentBrand.industry}`,
11
- `Style: ${currentBrand.style}`,
12
- `Current primary color: ${currentBrand.colors.primary.hex}`,
13
- `Current heading font: ${currentBrand.typography.headingFont}`,
14
- `Current body font: ${currentBrand.typography.bodyFont}`,
15
- ].join('\n');
16
-
17
- const colorSchema = `{
18
- "color": {
19
- "baseColor": "#hex (optional, keep current if not changing)",
20
- "harmony": "complementary|analogous|triadic|split-complementary|tetradic|monochromatic",
21
- "theme": "light|dark|both",
22
- "saturationShift": number (-30 to 30),
23
- "lightnessShift": number (-30 to 30),
24
- "hueShift": number (-180 to 180)
25
- },
26
- "reasoning": "1-2 sentences explaining the design decision"
27
- }`;
28
-
29
- const typographySchema = `{
30
- "typography": {
31
- "headingCategory": "serif|sans-serif|monospace|display|handwriting",
32
- "bodyCategory": "serif|sans-serif|monospace|display|handwriting",
33
- "scaleRatio": "minor-second|major-second|minor-third|major-third|perfect-fourth|augmented-fourth|perfect-fifth|golden-ratio",
34
- "baseSize": number (12-24)
35
- },
36
- "reasoning": "1-2 sentences explaining the design decision"
37
- }`;
38
-
39
- const schema = element === 'colors' ? colorSchema : typographySchema;
40
-
41
- return [
42
- 'You are a brand identity expert. Interpret the user feedback and output a JSON object with specific design parameters.',
43
- '',
44
- 'Current brand context:',
45
- brandContext,
46
- '',
47
- `User wants to refine: ${element}`,
48
- `User feedback: "${feedback}"`,
49
- '',
50
- 'Respond with ONLY valid JSON matching this schema:',
51
- schema,
52
- '',
53
- 'Design principles:',
54
- '- Warm colors: shift hue toward red/orange (positive hueShift), use analogous harmony',
55
- '- Cool colors: shift hue toward blue/green (negative hueShift)',
56
- '- Premium feel: lower saturation, slightly darker, analogous or monochromatic',
57
- '- Playful: higher saturation, lighter, triadic harmony',
58
- '- Corporate: moderate saturation, complementary, clean sans-serif',
59
- '- Editorial: serif headings, perfect-fourth or larger scale ratio',
60
- '- Technical: monospace body, sans-serif headings, compact scale',
61
- '- WCAG: ensure contrast ratios remain accessible (>= 4.5:1 on white)',
62
- ].join('\n');
63
- }
64
-
65
- export function buildGenerationPrompt(
66
- brandName: string,
67
- industry: string,
68
- description: string
69
- ): string {
70
- return [
71
- 'You are a brand identity expert. Based on the brand description, suggest specific design parameters.',
72
- '',
73
- `Brand name: ${brandName}`,
74
- `Industry: ${industry}`,
75
- `Description: "${description}"`,
76
- '',
77
- 'Respond with ONLY valid JSON:',
78
- '{',
79
- ' "style": "minimal|bold|elegant|playful|corporate|tech|organic|retro",',
80
- ' "color": {',
81
- ' "baseColor": "#hex",',
82
- ' "harmony": "complementary|analogous|triadic|split-complementary|tetradic|monochromatic",',
83
- ' "theme": "light|dark|both"',
84
- ' },',
85
- ' "typography": {',
86
- ' "headingCategory": "serif|sans-serif|monospace|display|handwriting",',
87
- ' "bodyCategory": "serif|sans-serif|monospace|display|handwriting",',
88
- ' "scaleRatio": "minor-second|major-second|minor-third|major-third|perfect-fourth|augmented-fourth|perfect-fifth|golden-ratio"',
89
- ' },',
90
- ' "reasoning": "2-3 sentences explaining why these choices fit the brand"',
91
- '}',
92
- ].join('\n');
93
- }
@@ -1,36 +0,0 @@
1
- import type {
2
- BrandStyle,
3
- ColorHarmony,
4
- ColorTheme,
5
- FontCategory,
6
- TypeScaleRatio,
7
- } from '../../types.js';
8
-
9
- export interface ColorIntent {
10
- baseColor?: string;
11
- harmony?: ColorHarmony;
12
- theme?: ColorTheme;
13
- saturationShift?: number;
14
- lightnessShift?: number;
15
- hueShift?: number;
16
- }
17
-
18
- export interface TypographyIntent {
19
- headingCategory?: FontCategory;
20
- bodyCategory?: FontCategory;
21
- scaleRatio?: TypeScaleRatio;
22
- baseSize?: number;
23
- }
24
-
25
- export interface BrandIntent {
26
- color?: ColorIntent;
27
- typography?: TypographyIntent;
28
- style?: BrandStyle;
29
- reasoning?: string;
30
- }
31
-
32
- export interface InterpreterOptions {
33
- anthropicApiKey?: string;
34
- model?: string;
35
- maxTokens?: number;
36
- }
@@ -1,32 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
-
3
- export function generateBrandHtml(brand: BrandIdentity): string {
4
- const { colors, typography, spacing } = brand;
5
- return `<!DOCTYPE html>
6
- <html lang="en">
7
- <head>
8
- <meta charset="UTF-8">
9
- <title>${brand.name} Brand Book</title>
10
- </head>
11
- <body>
12
- <h1>${brand.name}</h1>
13
- ${brand.tagline ? `<p>${brand.tagline}</p>` : ''}
14
- <section>
15
- <h2>Colors</h2>
16
- <p>Primary: ${colors.primary.hex}</p>
17
- <p>Secondary: ${colors.secondary.hex}</p>
18
- <p>Accent: ${colors.accent.hex}</p>
19
- </section>
20
- <section>
21
- <h2>Typography</h2>
22
- <p>Heading: ${typography.headingFont}</p>
23
- <p>Body: ${typography.bodyFont}</p>
24
- <p>Scale: ${typography.scaleRatio}</p>
25
- </section>
26
- <section>
27
- <h2>Spacing</h2>
28
- <p>Base unit: ${spacing.unit}px</p>
29
- </section>
30
- </body>
31
- </html>`;
32
- }
@@ -1,21 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
-
3
- export function generateBrandPdfContent(brand: BrandIdentity): string {
4
- return [
5
- `${brand.name} Brand Guidelines`,
6
- brand.tagline ?? '',
7
- '',
8
- 'Colors',
9
- ` Primary: ${brand.colors.primary.hex}`,
10
- ` Secondary: ${brand.colors.secondary.hex}`,
11
- ` Accent: ${brand.colors.accent.hex}`,
12
- '',
13
- 'Typography',
14
- ` Heading: ${brand.typography.headingFont}`,
15
- ` Body: ${brand.typography.bodyFont}`,
16
- ` Base size: ${brand.typography.baseSize}px`,
17
- '',
18
- 'Spacing',
19
- ` Base unit: ${brand.spacing.unit}px`,
20
- ].join('\n');
21
- }
@@ -1,71 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
-
3
- export function exportCssVariables(brand: BrandIdentity): string {
4
- const { colors, typography, spacing } = brand;
5
- const lines: string[] = [':root {'];
6
-
7
- lines.push(` --color-primary: ${colors.primary.hex};`);
8
- lines.push(` --color-secondary: ${colors.secondary.hex};`);
9
- lines.push(` --color-accent: ${colors.accent.hex};`);
10
- for (const n of colors.neutral) {
11
- lines.push(` --color-${n.name}: ${n.hex};`);
12
- }
13
- lines.push(` --color-success: ${colors.semantic.success.hex};`);
14
- lines.push(` --color-warning: ${colors.semantic.warning.hex};`);
15
- lines.push(` --color-error: ${colors.semantic.error.hex};`);
16
- lines.push(` --color-info: ${colors.semantic.info.hex};`);
17
- lines.push('');
18
-
19
- lines.push(` --font-heading: '${typography.headingFont}', sans-serif;`);
20
- lines.push(` --font-body: '${typography.bodyFont}', sans-serif;`);
21
- lines.push(` --font-mono: '${typography.monoFont}', monospace;`);
22
- for (const step of typography.steps) {
23
- lines.push(` --text-${step.name}: ${step.size};`);
24
- lines.push(` --leading-${step.name}: ${step.lineHeight};`);
25
- }
26
- lines.push('');
27
-
28
- for (const [key, value] of Object.entries(spacing.values)) {
29
- lines.push(` --spacing-${key}: ${value};`);
30
- }
31
-
32
- if (brand.shadows) {
33
- lines.push('');
34
- for (const [name, level] of Object.entries(brand.shadows.levels)) {
35
- lines.push(` --shadow-${name}: ${level.cssValue};`);
36
- }
37
- }
38
-
39
- if (brand.borders) {
40
- lines.push('');
41
- for (const [name, value] of Object.entries(brand.borders.radii)) {
42
- lines.push(` --radius-${name}: ${value};`);
43
- }
44
- for (const [name, value] of Object.entries(brand.borders.widths)) {
45
- lines.push(` --border-${name}: ${value};`);
46
- }
47
- }
48
-
49
- if (brand.motion) {
50
- lines.push('');
51
- for (const [name, value] of Object.entries(brand.motion.durations)) {
52
- lines.push(` --duration-${name}: ${value};`);
53
- }
54
- for (const [name, value] of Object.entries(brand.motion.easings)) {
55
- lines.push(` --ease-${name}: ${value};`);
56
- }
57
- for (const [name, value] of Object.entries(brand.motion.transitions)) {
58
- lines.push(` --transition-${name}: ${value};`);
59
- }
60
- }
61
-
62
- if (brand.gradients) {
63
- lines.push('');
64
- for (const [name, gradient] of Object.entries(brand.gradients.presets)) {
65
- lines.push(` --gradient-${name}: ${gradient.cssValue};`);
66
- }
67
- }
68
-
69
- lines.push('}');
70
- return lines.join('\n');
71
- }
@@ -1,86 +0,0 @@
1
- import type { BrandIdentity, DesignTokens } from '../../types.js';
2
-
3
- export function exportDesignTokens(brand: BrandIdentity): DesignTokens {
4
- const { colors, typography, spacing } = brand;
5
-
6
- const colorTokens: DesignTokens['color'] = {
7
- primary: { value: { $value: colors.primary.hex, $type: 'color' } },
8
- secondary: { value: { $value: colors.secondary.hex, $type: 'color' } },
9
- accent: { value: { $value: colors.accent.hex, $type: 'color' } },
10
- success: { value: { $value: colors.semantic.success.hex, $type: 'color' } },
11
- warning: { value: { $value: colors.semantic.warning.hex, $type: 'color' } },
12
- error: { value: { $value: colors.semantic.error.hex, $type: 'color' } },
13
- info: { value: { $value: colors.semantic.info.hex, $type: 'color' } },
14
- };
15
-
16
- for (const neutral of colors.neutral) {
17
- colorTokens[neutral.name] = { value: { $value: neutral.hex, $type: 'color' } };
18
- }
19
-
20
- const typographyTokens: DesignTokens['typography'] = {};
21
- for (const step of typography.steps) {
22
- typographyTokens[step.name] = {
23
- fontSize: { $value: step.size, $type: 'dimension' },
24
- lineHeight: { $value: step.lineHeight, $type: 'number' },
25
- letterSpacing: { $value: step.letterSpacing, $type: 'dimension' },
26
- fontWeight: { $value: step.weight, $type: 'number' },
27
- };
28
- }
29
- typographyTokens['font-heading'] = {
30
- value: { $value: typography.headingFont, $type: 'fontFamily' },
31
- };
32
- typographyTokens['font-body'] = {
33
- value: { $value: typography.bodyFont, $type: 'fontFamily' },
34
- };
35
- typographyTokens['font-mono'] = {
36
- value: { $value: typography.monoFont, $type: 'fontFamily' },
37
- };
38
-
39
- const spacingTokens: DesignTokens['spacing'] = {};
40
- for (const [key, value] of Object.entries(spacing.values)) {
41
- spacingTokens[key] = { $value: value, $type: 'dimension' };
42
- }
43
-
44
- const result: DesignTokens = {
45
- $schema: 'https://design-tokens.github.io/community-group/format/',
46
- color: colorTokens,
47
- typography: typographyTokens,
48
- spacing: spacingTokens,
49
- };
50
-
51
- if (brand.shadows) {
52
- result.shadow = {};
53
- for (const [name, level] of Object.entries(brand.shadows.levels)) {
54
- result.shadow[name] = { $value: level.cssValue, $type: 'shadow' };
55
- }
56
- }
57
-
58
- if (brand.borders) {
59
- result.border = {};
60
- for (const [name, value] of Object.entries(brand.borders.radii)) {
61
- result.border[`radius-${name}`] = { $value: value, $type: 'dimension' };
62
- }
63
- for (const [name, value] of Object.entries(brand.borders.widths)) {
64
- result.border[`width-${name}`] = { $value: value, $type: 'dimension' };
65
- }
66
- }
67
-
68
- if (brand.motion) {
69
- result.motion = {};
70
- for (const [name, value] of Object.entries(brand.motion.durations)) {
71
- result.motion[`duration-${name}`] = { $value: value, $type: 'duration' };
72
- }
73
- for (const [name, value] of Object.entries(brand.motion.easings)) {
74
- result.motion[`easing-${name}`] = { $value: value, $type: 'cubicBezier' };
75
- }
76
- }
77
-
78
- if (brand.gradients) {
79
- result.gradient = {};
80
- for (const [name, gradient] of Object.entries(brand.gradients.presets)) {
81
- result.gradient[name] = { $value: gradient.cssValue, $type: 'gradient' };
82
- }
83
- }
84
-
85
- return result;
86
- }
@@ -1,87 +0,0 @@
1
- import type { BrandIdentity } from '../../types.js';
2
-
3
- interface FigmaToken {
4
- value: string | number;
5
- type: string;
6
- description?: string;
7
- }
8
-
9
- export function exportFigmaTokens(
10
- brand: BrandIdentity
11
- ): Record<string, Record<string, FigmaToken>> {
12
- const { colors, typography, spacing } = brand;
13
-
14
- const colorGroup: Record<string, FigmaToken> = {
15
- primary: { value: colors.primary.hex, type: 'color', description: 'Primary brand color' },
16
- secondary: { value: colors.secondary.hex, type: 'color' },
17
- accent: { value: colors.accent.hex, type: 'color' },
18
- success: { value: colors.semantic.success.hex, type: 'color' },
19
- warning: { value: colors.semantic.warning.hex, type: 'color' },
20
- error: { value: colors.semantic.error.hex, type: 'color' },
21
- info: { value: colors.semantic.info.hex, type: 'color' },
22
- };
23
- for (const n of colors.neutral) {
24
- colorGroup[n.name] = { value: n.hex, type: 'color' };
25
- }
26
-
27
- const typographyGroup: Record<string, FigmaToken> = {};
28
- for (const step of typography.steps) {
29
- typographyGroup[`${step.name}-size`] = { value: step.size, type: 'fontSizes' };
30
- typographyGroup[`${step.name}-weight`] = { value: step.weight, type: 'fontWeights' };
31
- typographyGroup[`${step.name}-lineHeight`] = { value: step.lineHeight, type: 'lineHeights' };
32
- }
33
- typographyGroup['font-heading'] = { value: typography.headingFont, type: 'fontFamilies' };
34
- typographyGroup['font-body'] = { value: typography.bodyFont, type: 'fontFamilies' };
35
- typographyGroup['font-mono'] = { value: typography.monoFont, type: 'fontFamilies' };
36
-
37
- const spacingGroup: Record<string, FigmaToken> = {};
38
- for (const [key, value] of Object.entries(spacing.values)) {
39
- spacingGroup[key] = { value, type: 'spacing' };
40
- }
41
-
42
- const result: Record<string, Record<string, FigmaToken>> = {
43
- color: colorGroup,
44
- typography: typographyGroup,
45
- spacing: spacingGroup,
46
- };
47
-
48
- if (brand.shadows) {
49
- const shadowGroup: Record<string, FigmaToken> = {};
50
- for (const [name, level] of Object.entries(brand.shadows.levels)) {
51
- shadowGroup[name] = { value: level.cssValue, type: 'boxShadow' };
52
- }
53
- result.shadow = shadowGroup;
54
- }
55
-
56
- if (brand.borders) {
57
- const borderGroup: Record<string, FigmaToken> = {};
58
- for (const [name, value] of Object.entries(brand.borders.radii)) {
59
- borderGroup[`radius-${name}`] = { value, type: 'borderRadius' };
60
- }
61
- for (const [name, value] of Object.entries(brand.borders.widths)) {
62
- borderGroup[`width-${name}`] = { value, type: 'borderWidth' };
63
- }
64
- result.border = borderGroup;
65
- }
66
-
67
- if (brand.motion) {
68
- const motionGroup: Record<string, FigmaToken> = {};
69
- for (const [name, value] of Object.entries(brand.motion.durations)) {
70
- motionGroup[`duration-${name}`] = { value, type: 'duration' };
71
- }
72
- for (const [name, value] of Object.entries(brand.motion.easings)) {
73
- motionGroup[`easing-${name}`] = { value, type: 'other' };
74
- }
75
- result.motion = motionGroup;
76
- }
77
-
78
- if (brand.gradients) {
79
- const gradientGroup: Record<string, FigmaToken> = {};
80
- for (const [name, gradient] of Object.entries(brand.gradients.presets)) {
81
- gradientGroup[name] = { value: gradient.cssValue, type: 'gradient' };
82
- }
83
- result.gradient = gradientGroup;
84
- }
85
-
86
- return result;
87
- }