Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
AI-powered brand identity skill for generating cohesive brand guidelines, color palettes, and visual identity.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/sync-brand-to-tokens.cjs
1#!/usr/bin/env node2/**3* sync-brand-to-tokens.cjs4*5* Syncs brand-guidelines.md colors โ design-tokens.json โ design-tokens.css6*7* Usage:8* node sync-brand-to-tokens.cjs9* node sync-brand-to-tokens.cjs --dry-run10*/1112const fs = require('fs');13const path = require('path');14const { execSync } = require('child_process');1516// Paths17const BRAND_GUIDELINES = 'docs/brand-guidelines.md';18const DESIGN_TOKENS_JSON = 'assets/design-tokens.json';19const DESIGN_TOKENS_CSS = 'assets/design-tokens.css';20const GENERATE_TOKENS_SCRIPT = '.claude/skills/design-system/scripts/generate-tokens.cjs';2122/**23* Extract color info from brand guidelines markdown24*/25function extractColorsFromMarkdown(content) {26const colors = {27primary: { name: 'primary', shades: {} },28secondary: { name: 'secondary', shades: {} },29accent: { name: 'accent', shades: {} }30};3132// Extract primary color name and hex from Quick Reference table33const quickRefMatch = content.match(/Primary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);34if (quickRefMatch) {35colors.primary.name = quickRefMatch[2].toLowerCase().replace(/\s+/g, '-');36colors.primary.base = `#${quickRefMatch[1]}`;37}3839const secondaryMatch = content.match(/Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);40if (secondaryMatch) {41colors.secondary.name = secondaryMatch[2].toLowerCase().replace(/\s+/g, '-');42colors.secondary.base = `#${secondaryMatch[1]}`;43}4445const accentMatch = content.match(/Accent Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);46if (accentMatch) {47colors.accent.name = accentMatch[2].toLowerCase().replace(/\s+/g, '-');48colors.accent.base = `#${accentMatch[1]}`;49}5051// Extract all shades from Primary Colors table52const primarySection = content.match(/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);53if (primarySection) {54const hexMatches = primarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);55for (const match of hexMatches) {56const name = match[1].trim().toLowerCase();57const hex = `#${match[2]}`;58if (name.includes('dark')) colors.primary.dark = hex;59else if (name.includes('light')) colors.primary.light = hex;60else colors.primary.base = hex;61}62}6364// Extract secondary shades65const secondarySection = content.match(/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);66if (secondarySection) {67const hexMatches = secondarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);68for (const match of hexMatches) {69const name = match[1].trim().toLowerCase();70const hex = `#${match[2]}`;71if (name.includes('dark')) colors.secondary.dark = hex;72else if (name.includes('light')) colors.secondary.light = hex;73else colors.secondary.base = hex;74}75}7677// Extract accent shades78const accentSection = content.match(/### Accent Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);79if (accentSection) {80const hexMatches = accentSection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);81for (const match of hexMatches) {82const name = match[1].trim().toLowerCase();83const hex = `#${match[2]}`;84if (name.includes('dark')) colors.accent.dark = hex;85else if (name.includes('light')) colors.accent.light = hex;86else colors.accent.base = hex;87}88}8990return colors;91}9293/**94* Generate color scale from base color (simple approach)95*/96function generateColorScale(baseHex, darkHex, lightHex) {97// Use provided shades or generate approximations98return {99"50": { "$value": lightHex || adjustBrightness(baseHex, 0.9), "$type": "color" },100"100": { "$value": lightHex || adjustBrightness(baseHex, 0.8), "$type": "color" },101"200": { "$value": adjustBrightness(baseHex, 0.6), "$type": "color" },102"300": { "$value": adjustBrightness(baseHex, 0.4), "$type": "color" },103"400": { "$value": adjustBrightness(baseHex, 0.2), "$type": "color" },104"500": { "$value": baseHex, "$type": "color" },105"600": { "$value": darkHex || adjustBrightness(baseHex, -0.15), "$type": "color" },106"700": { "$value": adjustBrightness(baseHex, -0.3), "$type": "color" },107"800": { "$value": adjustBrightness(baseHex, -0.45), "$type": "color" },108"900": { "$value": adjustBrightness(baseHex, -0.6), "$type": "color" }109};110}111112/**113* Adjust hex color brightness114*/115function adjustBrightness(hex, percent) {116const num = parseInt(hex.replace('#', ''), 16);117const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));118const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent)));119const b = Math.min(255, Math.max(0, (num & 0x0000FF) + Math.round(255 * percent)));120return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase()}`;121}122123/**124* Update design tokens JSON125*/126function updateDesignTokens(tokens, colors) {127// Update brand name128const brandName = `ClaudeKit Marketing - ${colors.primary.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}`;129tokens.brand = brandName;130131// Update primitive colors with new names132const primitiveColors = tokens.primitive?.color || {};133134// Remove old color keys, add new ones135delete primitiveColors.coral;136delete primitiveColors.purple;137delete primitiveColors.mint;138139// Add new named colors140primitiveColors[colors.primary.name] = generateColorScale(141colors.primary.base,142colors.primary.dark,143colors.primary.light144);145primitiveColors[colors.secondary.name] = generateColorScale(146colors.secondary.base,147colors.secondary.dark,148colors.secondary.light149);150primitiveColors[colors.accent.name] = generateColorScale(151colors.accent.base,152colors.accent.dark,153colors.accent.light154);155156tokens.primitive.color = primitiveColors;157158// Update ALL semantic color references159if (tokens.semantic?.color) {160const sem = tokens.semantic.color;161const p = colors.primary.name;162const s = colors.secondary.name;163const a = colors.accent.name;164165// Primary variants166sem.primary = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };167sem['primary-hover'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };168sem['primary-active'] = { "$value": `{primitive.color.${p}.700}`, "$type": "color" };169sem['primary-light'] = { "$value": `{primitive.color.${p}.400}`, "$type": "color" };170sem['primary-lighter'] = { "$value": `{primitive.color.${p}.100}`, "$type": "color" };171sem['primary-dark'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };172173// Secondary variants174sem.secondary = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };175sem['secondary-hover'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };176sem['secondary-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };177sem['secondary-dark'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };178179// Accent variants180sem.accent = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };181sem['accent-hover'] = { "$value": `{primitive.color.${a}.600}`, "$type": "color" };182sem['accent-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };183184// Status colors (use accent for success, primary for error/info)185sem.success = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };186sem['success-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };187sem.error = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };188sem['error-light'] = { "$value": `{primitive.color.${p}.300}`, "$type": "color" };189sem.info = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };190sem['info-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };191}192193// Update component references (button uses primary color with opacity)194if (tokens.component?.button?.secondary) {195const primaryBase = colors.primary.base;196tokens.component.button.secondary['bg-hover'] = {197"$value": `${primaryBase}1A`,198"$type": "color"199};200}201202return tokens;203}204205/**206* Main207*/208function main() {209const dryRun = process.argv.includes('--dry-run');210211console.log('๐ Syncing brand guidelines โ design tokens\n');212213// Read brand guidelines214const guidelinesPath = path.resolve(process.cwd(), BRAND_GUIDELINES);215if (!fs.existsSync(guidelinesPath)) {216console.error(`โ Brand guidelines not found: ${guidelinesPath}`);217process.exit(1);218}219const guidelinesContent = fs.readFileSync(guidelinesPath, 'utf-8');220221// Extract colors222const colors = extractColorsFromMarkdown(guidelinesContent);223console.log('๐ Extracted colors:');224console.log(` Primary: ${colors.primary.name} (${colors.primary.base})`);225console.log(` Secondary: ${colors.secondary.name} (${colors.secondary.base})`);226console.log(` Accent: ${colors.accent.name} (${colors.accent.base})\n`);227228// Read existing tokens229const tokensPath = path.resolve(process.cwd(), DESIGN_TOKENS_JSON);230let tokens = {};231if (fs.existsSync(tokensPath)) {232tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));233}234235// Update tokens236tokens = updateDesignTokens(tokens, colors);237238if (dryRun) {239console.log('๐ Would update design-tokens.json:');240console.log(JSON.stringify(tokens.primitive.color, null, 2).slice(0, 500) + '...');241console.log('\nโญ๏ธ Dry run - no files changed');242return;243}244245// Write updated tokens246fs.writeFileSync(tokensPath, JSON.stringify(tokens, null, 2));247console.log(`โ Updated: ${DESIGN_TOKENS_JSON}`);248249// Regenerate CSS250const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT);251if (fs.existsSync(generateScript)) {252try {253execSync(`node ${generateScript} --config ${DESIGN_TOKENS_JSON} -o ${DESIGN_TOKENS_CSS}`, {254cwd: process.cwd(),255stdio: 'inherit'256});257console.log(`โ Regenerated: ${DESIGN_TOKENS_CSS}`);258} catch (e) {259console.error('โ ๏ธ Failed to regenerate CSS:', e.message);260}261}262263console.log('\nโจ Brand sync complete!');264}265266main();267