@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.
- package/README.md +41 -5
- package/dist/index.js +1 -1
- package/package.json +20 -3
- package/server.json +29 -0
- package/.env.example +0 -3
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -22
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -22
- package/.github/dependabot.yml +0 -17
- package/.github/workflows/ci.yml +0 -55
- package/.github/workflows/codeql.yml +0 -47
- package/.github/workflows/release-automation.yml +0 -56
- package/.github/workflows/secret-scan.yml +0 -13
- package/.github/workflows/security-scan.yml +0 -37
- package/.github/workflows/semgrep.yml +0 -12
- package/.github/workflows/trivy.yml +0 -17
- package/.gitleaks.toml +0 -14
- package/.husky/commit-msg +0 -1
- package/.nvmrc +0 -1
- package/.prettierrc.json +0 -10
- package/CHANGELOG.md +0 -114
- package/CONTRIBUTING.md +0 -203
- package/catalog-info.yaml +0 -16
- package/commitlint.config.cjs +0 -24
- package/data/README.md +0 -13
- package/docs/API.md +0 -110
- package/docs/DATA_SOURCES.md +0 -69
- package/docs/INTEGRATION.md +0 -58
- package/eslint.config.js +0 -52
- package/jest.config.js +0 -40
- package/knip.json +0 -10
- package/src/__tests__/integration/brand-generation.test.ts +0 -84
- package/src/__tests__/integration/mcp-server.test.ts +0 -18
- package/src/__tests__/unit/ai-interpreter.test.ts +0 -172
- package/src/__tests__/unit/border-system.test.ts +0 -77
- package/src/__tests__/unit/color-palette.test.ts +0 -161
- package/src/__tests__/unit/contrast-checker.test.ts +0 -124
- package/src/__tests__/unit/design-system-tool.test.ts +0 -176
- package/src/__tests__/unit/design-tokens.test.ts +0 -184
- package/src/__tests__/unit/favicon-generator.test.ts +0 -80
- package/src/__tests__/unit/gradient-system.test.ts +0 -122
- package/src/__tests__/unit/logo-generator.test.ts +0 -146
- package/src/__tests__/unit/motion-system.test.ts +0 -91
- package/src/__tests__/unit/og-image-generator.test.ts +0 -115
- package/src/__tests__/unit/shadow-system.test.ts +0 -63
- package/src/__tests__/unit/spacing-scale.test.ts +0 -60
- package/src/__tests__/unit/typography-system.test.ts +0 -71
- package/src/index.ts +0 -76
- package/src/lib/branding-core/ai/brand-interpreter.ts +0 -30
- package/src/lib/branding-core/ai/claude-interpreter.ts +0 -76
- package/src/lib/branding-core/ai/intent-applier.ts +0 -59
- package/src/lib/branding-core/ai/keyword-interpreter.ts +0 -95
- package/src/lib/branding-core/ai/prompts.ts +0 -93
- package/src/lib/branding-core/ai/types.ts +0 -36
- package/src/lib/branding-core/documents/html-generator.ts +0 -32
- package/src/lib/branding-core/documents/pdf-generator.ts +0 -21
- package/src/lib/branding-core/exporters/css-variables.ts +0 -71
- package/src/lib/branding-core/exporters/design-tokens.ts +0 -86
- package/src/lib/branding-core/exporters/figma-tokens.ts +0 -87
- package/src/lib/branding-core/exporters/react-theme.ts +0 -69
- package/src/lib/branding-core/exporters/sass-variables.ts +0 -74
- package/src/lib/branding-core/exporters/tailwind-preset.ts +0 -67
- package/src/lib/branding-core/generators/border-system.ts +0 -41
- package/src/lib/branding-core/generators/color-palette.ts +0 -147
- package/src/lib/branding-core/generators/favicon-generator.ts +0 -33
- package/src/lib/branding-core/generators/gradient-system.ts +0 -120
- package/src/lib/branding-core/generators/logo-generator.ts +0 -152
- package/src/lib/branding-core/generators/motion-system.ts +0 -98
- package/src/lib/branding-core/generators/og-image-generator.ts +0 -97
- package/src/lib/branding-core/generators/shadow-system.ts +0 -66
- package/src/lib/branding-core/generators/spacing-scale.ts +0 -29
- package/src/lib/branding-core/generators/typography-system.ts +0 -128
- package/src/lib/branding-core/index.ts +0 -28
- package/src/lib/branding-core/validators/brand-consistency.ts +0 -79
- package/src/lib/branding-core/validators/contrast-checker.ts +0 -37
- package/src/lib/branding-core/validators/token-schema.ts +0 -50
- package/src/lib/config.ts +0 -13
- package/src/lib/logger.ts +0 -12
- package/src/lib/types.ts +0 -236
- package/src/resources/brand-knowledge.ts +0 -60
- package/src/resources/brand-templates.ts +0 -385
- package/src/tools/create-brand-guidelines.ts +0 -94
- package/src/tools/export-design-tokens.ts +0 -52
- package/src/tools/generate-brand-assets.ts +0 -48
- package/src/tools/generate-brand-identity.ts +0 -115
- package/src/tools/generate-color-palette.ts +0 -43
- package/src/tools/generate-design-system.ts +0 -163
- package/src/tools/generate-typography-system.ts +0 -42
- package/src/tools/refine-brand-element.ts +0 -65
- package/src/tools/validate-brand-consistency.ts +0 -32
- 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
|
-
}
|