mirror of
https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git
synced 2026-04-25 11:18:17 +00:00
feat(skills): add brand skill
This commit is contained in:
parent
56a1e8f3d3
commit
6512820396
97
.claude/skills/brand/SKILL.md
Normal file
97
.claude/skills/brand/SKILL.md
Normal file
@ -0,0 +1,97 @@
|
||||
---
|
||||
name: ckm:brand
|
||||
description: Brand voice, visual identity, messaging frameworks, asset management, brand consistency. Activate for branded content, tone of voice, marketing assets, brand compliance, style guides.
|
||||
argument-hint: "[update|review|create] [args]"
|
||||
metadata:
|
||||
author: claudekit
|
||||
version: "1.0.0"
|
||||
---
|
||||
|
||||
# Brand
|
||||
|
||||
Brand identity, voice, messaging, asset management, and consistency frameworks.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Brand voice definition and content tone guidance
|
||||
- Visual identity standards and style guide development
|
||||
- Messaging framework creation
|
||||
- Brand consistency review and audit
|
||||
- Asset organization, naming, and approval
|
||||
- Color palette management and typography specs
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Inject brand context into prompts:**
|
||||
```bash
|
||||
node scripts/inject-brand-context.cjs
|
||||
node scripts/inject-brand-context.cjs --json
|
||||
```
|
||||
|
||||
**Validate an asset:**
|
||||
```bash
|
||||
node scripts/validate-asset.cjs <asset-path>
|
||||
```
|
||||
|
||||
**Extract/compare colors:**
|
||||
```bash
|
||||
node scripts/extract-colors.cjs --palette
|
||||
node scripts/extract-colors.cjs <image-path>
|
||||
```
|
||||
|
||||
## Brand Sync Workflow
|
||||
|
||||
```bash
|
||||
# 1. Edit docs/brand-guidelines.md (or use /brand update)
|
||||
# 2. Sync to design tokens
|
||||
node scripts/sync-brand-to-tokens.cjs
|
||||
# 3. Verify
|
||||
node scripts/inject-brand-context.cjs --json | head -20
|
||||
```
|
||||
|
||||
**Files synced:**
|
||||
- `docs/brand-guidelines.md` → Source of truth
|
||||
- `assets/design-tokens.json` → Token definitions
|
||||
- `assets/design-tokens.css` → CSS variables
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Subcommand | Description | Reference |
|
||||
|------------|-------------|-----------|
|
||||
| `update` | Update brand identity and sync to all design systems | `references/update.md` |
|
||||
|
||||
## References
|
||||
|
||||
| Topic | File |
|
||||
|-------|------|
|
||||
| Voice Framework | `references/voice-framework.md` |
|
||||
| Visual Identity | `references/visual-identity.md` |
|
||||
| Messaging | `references/messaging-framework.md` |
|
||||
| Consistency | `references/consistency-checklist.md` |
|
||||
| Guidelines Template | `references/brand-guideline-template.md` |
|
||||
| Asset Organization | `references/asset-organization.md` |
|
||||
| Color Management | `references/color-palette-management.md` |
|
||||
| Typography | `references/typography-specifications.md` |
|
||||
| Logo Usage | `references/logo-usage-rules.md` |
|
||||
| Approval Checklist | `references/approval-checklist.md` |
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `scripts/inject-brand-context.cjs` | Extract brand context for prompt injection |
|
||||
| `scripts/sync-brand-to-tokens.cjs` | Sync brand-guidelines.md → design-tokens.json/css |
|
||||
| `scripts/validate-asset.cjs` | Validate asset naming, size, format |
|
||||
| `scripts/extract-colors.cjs` | Extract and compare colors against palette |
|
||||
|
||||
## Templates
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `templates/brand-guidelines-starter.md` | Complete starter template for new brands |
|
||||
|
||||
## Routing
|
||||
|
||||
1. Parse subcommand from `$ARGUMENTS` (first word)
|
||||
2. Load corresponding `references/{subcommand}.md`
|
||||
3. Execute with remaining arguments
|
||||
169
.claude/skills/brand/references/approval-checklist.md
Normal file
169
.claude/skills/brand/references/approval-checklist.md
Normal file
@ -0,0 +1,169 @@
|
||||
# Asset Approval Checklist
|
||||
|
||||
Comprehensive checklist for reviewing marketing assets before approval.
|
||||
|
||||
## Quick Review
|
||||
|
||||
Before detailed review, verify:
|
||||
- [ ] Asset serves stated purpose
|
||||
- [ ] Target audience appropriate
|
||||
- [ ] No obvious errors or issues
|
||||
- [ ] Aligns with campaign goals
|
||||
|
||||
## Visual Elements
|
||||
|
||||
### Logo Usage
|
||||
- [ ] Correct logo variant for context
|
||||
- [ ] Proper clear space maintained
|
||||
- [ ] Minimum size requirements met
|
||||
- [ ] Approved colors only
|
||||
- [ ] No unauthorized modifications
|
||||
- [ ] Appropriate for background
|
||||
|
||||
### Color Compliance
|
||||
- [ ] Uses brand palette colors only
|
||||
- [ ] Primary/secondary ratio appropriate (60/30/10)
|
||||
- [ ] Semantic colors used correctly
|
||||
- [ ] No off-brand colors introduced
|
||||
- [ ] Consistent across all elements
|
||||
|
||||
### Typography
|
||||
- [ ] Brand fonts used throughout
|
||||
- [ ] Correct font weights applied
|
||||
- [ ] Proper type hierarchy
|
||||
- [ ] Appropriate sizes for medium
|
||||
- [ ] Line heights adequate
|
||||
- [ ] No orphans/widows in body text
|
||||
|
||||
### Imagery
|
||||
- [ ] Matches brand photography style
|
||||
- [ ] Appropriate subjects/content
|
||||
- [ ] Quality meets requirements
|
||||
- [ ] Properly licensed/credited
|
||||
- [ ] Optimized for intended use
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Visual Accessibility
|
||||
- [ ] Text contrast ratio >= 4.5:1 (AA)
|
||||
- [ ] Large text contrast >= 3:1
|
||||
- [ ] Interactive elements have visible focus
|
||||
- [ ] Color not sole indicator of meaning
|
||||
- [ ] Alt text for all images
|
||||
|
||||
### Content Accessibility
|
||||
- [ ] Clear and scannable layout
|
||||
- [ ] Readable font sizes
|
||||
- [ ] Logical reading order
|
||||
- [ ] Meaningful headings structure
|
||||
- [ ] Links describe destination
|
||||
|
||||
## Content Quality
|
||||
|
||||
### Copy Review
|
||||
- [ ] Matches brand voice
|
||||
- [ ] Appropriate tone for context
|
||||
- [ ] No prohibited terms used
|
||||
- [ ] Value proposition clear
|
||||
- [ ] CTA compelling and clear
|
||||
- [ ] Proofread for errors
|
||||
|
||||
### Messaging
|
||||
- [ ] Aligns with key messages
|
||||
- [ ] Differentiators highlighted
|
||||
- [ ] Benefits over features
|
||||
- [ ] Target audience addressed
|
||||
- [ ] No conflicting claims
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
### File Specifications
|
||||
- [ ] Correct file format
|
||||
- [ ] Appropriate resolution
|
||||
- [ ] File size optimized
|
||||
- [ ] Proper naming convention
|
||||
- [ ] Metadata included
|
||||
|
||||
### Platform Requirements
|
||||
| Platform | Verified |
|
||||
|----------|----------|
|
||||
| Instagram | [ ] Correct dimensions |
|
||||
| Twitter/X | [ ] Meets requirements |
|
||||
| LinkedIn | [ ] Professional standards |
|
||||
| Facebook | [ ] Guidelines compliant |
|
||||
| Email | [ ] Size under 1MB |
|
||||
| Web | [ ] Optimized for web |
|
||||
|
||||
## Legal & Compliance
|
||||
|
||||
### Intellectual Property
|
||||
- [ ] Stock images licensed
|
||||
- [ ] Music/audio cleared
|
||||
- [ ] No trademark violations
|
||||
- [ ] User content authorized
|
||||
- [ ] Credits included where needed
|
||||
|
||||
### Regulatory
|
||||
- [ ] Required disclosures present
|
||||
- [ ] No misleading claims
|
||||
- [ ] Pricing accurate
|
||||
- [ ] Terms linked where needed
|
||||
- [ ] Privacy compliant
|
||||
|
||||
## Review Status
|
||||
|
||||
### Reviewer Sign-off
|
||||
|
||||
| Review Area | Reviewer | Date | Status |
|
||||
|-------------|----------|------|--------|
|
||||
| Visual Design | | | [ ] Pass / [ ] Revisions |
|
||||
| Copy/Content | | | [ ] Pass / [ ] Revisions |
|
||||
| Brand Compliance | | | [ ] Pass / [ ] Revisions |
|
||||
| Technical | | | [ ] Pass / [ ] Revisions |
|
||||
| Legal | | | [ ] Pass / [ ] Revisions |
|
||||
|
||||
### Final Approval
|
||||
|
||||
- [ ] All review areas passed
|
||||
- [ ] Revisions completed (if any)
|
||||
- [ ] Final version uploaded
|
||||
- [ ] Metadata updated
|
||||
- [ ] Ready for publish/use
|
||||
|
||||
**Approved By:** _______________
|
||||
|
||||
**Date:** _______________
|
||||
|
||||
**Version:** _______________
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Logo too small | Increase to minimum size |
|
||||
| Wrong font | Replace with brand font |
|
||||
| Low contrast | Adjust colors for accessibility |
|
||||
| Off-brand color | Replace with palette color |
|
||||
| Blurry image | Use higher resolution source |
|
||||
| Missing alt text | Add descriptive alt text |
|
||||
| Weak CTA | Strengthen action-oriented copy |
|
||||
|
||||
## Automation Support
|
||||
|
||||
The `validate-asset.cjs` script can auto-check:
|
||||
- Color palette compliance
|
||||
- Minimum dimensions
|
||||
- File format/size
|
||||
- Naming convention
|
||||
- Basic metadata
|
||||
|
||||
Run: `node .claude/skills/brand/scripts/validate-asset.cjs <asset-path>`
|
||||
|
||||
## Archival
|
||||
|
||||
After approval:
|
||||
1. Update asset status in manifest.json
|
||||
2. Add approver and timestamp
|
||||
3. Move previous versions to archive
|
||||
4. Update campaign tracking
|
||||
5. Notify relevant teams
|
||||
157
.claude/skills/brand/references/asset-organization.md
Normal file
157
.claude/skills/brand/references/asset-organization.md
Normal file
@ -0,0 +1,157 @@
|
||||
# Asset Organization Guide
|
||||
|
||||
Guidelines for organizing marketing assets in a structured, searchable system.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── .assets/ # Git-tracked metadata
|
||||
│ ├── manifest.json # Central asset registry
|
||||
│ ├── tags.json # Tagging system
|
||||
│ ├── versions/ # Version history
|
||||
│ │ └── {asset-id}/
|
||||
│ │ └── v{n}.json
|
||||
│ └── metadata/ # Type-specific metadata
|
||||
│ ├── designs.json
|
||||
│ ├── banners.json
|
||||
│ ├── logos.json
|
||||
│ └── videos.json
|
||||
├── assets/ # Raw files
|
||||
│ ├── designs/
|
||||
│ │ ├── campaigns/ # Campaign-specific designs
|
||||
│ │ ├── web/ # Website graphics
|
||||
│ │ └── print/ # Print materials
|
||||
│ ├── banners/
|
||||
│ │ ├── social-media/ # Platform banners
|
||||
│ │ ├── email-headers/ # Email template headers
|
||||
│ │ └── landing-pages/ # Hero/section images
|
||||
│ ├── logos/
|
||||
│ │ ├── full-horizontal/ # Full logo with wordmark
|
||||
│ │ ├── icon-only/ # Symbol only
|
||||
│ │ ├── monochrome/ # Single color versions
|
||||
│ │ └── variations/ # Special versions
|
||||
│ ├── videos/
|
||||
│ │ ├── ads/ # Promotional videos
|
||||
│ │ ├── tutorials/ # How-to content
|
||||
│ │ └── testimonials/ # Customer videos
|
||||
│ ├── infographics/ # Data visualizations
|
||||
│ └── generated/ # AI-generated assets
|
||||
│ └── {YYYYMMDD}/ # Date-organized
|
||||
```
|
||||
|
||||
## Naming Convention
|
||||
|
||||
### Format
|
||||
```
|
||||
{type}_{campaign}_{description}_{timestamp}_{variant}.{ext}
|
||||
```
|
||||
|
||||
### Components
|
||||
| Component | Format | Required | Examples |
|
||||
|-----------|--------|----------|----------|
|
||||
| type | lowercase | Yes | banner, logo, design, video |
|
||||
| campaign | kebab-case | Yes* | claude-launch, q1-promo, evergreen |
|
||||
| description | kebab-case | Yes | hero-image, email-header |
|
||||
| timestamp | YYYYMMDD | Yes | 20251209 |
|
||||
| variant | kebab-case | No | dark-mode, 1x1, mobile |
|
||||
|
||||
*Use "evergreen" for non-campaign assets
|
||||
|
||||
### Examples
|
||||
```
|
||||
banner_claude-launch_hero-image_20251209_16-9.png
|
||||
logo_brand-refresh_horizontal-full-color_20251209.svg
|
||||
design_holiday-campaign_email-hero_20251209_dark-mode.psd
|
||||
video_product-demo_feature-walkthrough_20251209.mp4
|
||||
infographic_evergreen_pricing-comparison_20251209.png
|
||||
```
|
||||
|
||||
## Metadata Schema
|
||||
|
||||
### Asset Entry (manifest.json)
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"name": "Campaign Hero Banner",
|
||||
"type": "banner",
|
||||
"path": "assets/banners/landing-pages/banner_claude-launch_hero-image_20251209.png",
|
||||
"dimensions": { "width": 1920, "height": 1080 },
|
||||
"fileSize": 245760,
|
||||
"mimeType": "image/png",
|
||||
"tags": ["campaign", "hero", "launch"],
|
||||
"status": "approved",
|
||||
"source": {
|
||||
"model": "imagen-4",
|
||||
"prompt": "...",
|
||||
"createdAt": "2025-12-09T10:30:00Z"
|
||||
},
|
||||
"version": 2,
|
||||
"createdBy": "agent:content-creator",
|
||||
"approvedBy": "user:john",
|
||||
"approvedAt": "2025-12-09T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Version Entry (versions/{id}/v{n}.json)
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"previousVersion": 1,
|
||||
"path": "assets/banners/landing-pages/banner_claude-launch_hero-image_20251209_v2.png",
|
||||
"changes": "Updated CTA button color to match brand refresh",
|
||||
"createdAt": "2025-12-09T12:00:00Z",
|
||||
"createdBy": "agent:ui-designer"
|
||||
}
|
||||
```
|
||||
|
||||
## Tagging System
|
||||
|
||||
### Standard Tags
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| status | draft, review, approved, archived |
|
||||
| platform | instagram, twitter, linkedin, facebook, youtube, email, web |
|
||||
| content-type | promotional, educational, brand, product, testimonial |
|
||||
| format | 1x1, 4x5, 9x16, 16x9, story, reel, banner |
|
||||
| source | imagen-4, veo-3, user-upload, canva, figma |
|
||||
|
||||
### Tag Usage
|
||||
- Each asset should have: status + platform + content-type
|
||||
- Optional: format, source, campaign
|
||||
|
||||
## File Organization Best Practices
|
||||
|
||||
1. **One file per variant** - Don't combine dark/light in one file
|
||||
2. **Source files separate** - Keep .psd/.fig in same structure
|
||||
3. **AI assets timestamped** - Auto-organize by generation date
|
||||
4. **Archive don't delete** - Move to `archived/` with date prefix
|
||||
5. **Large files external** - Videos > 100MB use cloud storage links
|
||||
|
||||
## Search Patterns
|
||||
|
||||
### By Type
|
||||
```bash
|
||||
# Find all banners
|
||||
ls assets/banners/**/*
|
||||
```
|
||||
|
||||
### By Campaign
|
||||
```bash
|
||||
# Find all assets for specific campaign
|
||||
grep -l "claude-launch" .assets/manifest.json
|
||||
```
|
||||
|
||||
### By Status
|
||||
```bash
|
||||
# Find approved assets only
|
||||
jq '.assets[] | select(.status == "approved")' .assets/manifest.json
|
||||
```
|
||||
|
||||
## Cleanup Workflow
|
||||
|
||||
1. Run `extract-colors.cjs` on new assets
|
||||
2. Validate against brand guidelines
|
||||
3. Update manifest.json with new entries
|
||||
4. Tag appropriately
|
||||
5. Remove duplicates/outdated versions
|
||||
140
.claude/skills/brand/references/brand-guideline-template.md
Normal file
140
.claude/skills/brand/references/brand-guideline-template.md
Normal file
@ -0,0 +1,140 @@
|
||||
# Brand Guidelines Template
|
||||
|
||||
Use this template to create comprehensive brand guidelines for any project.
|
||||
|
||||
## Document Structure
|
||||
|
||||
```markdown
|
||||
# Brand Guidelines v{X.Y}
|
||||
|
||||
## Quick Reference
|
||||
- **Primary Color:** #XXXXXX
|
||||
- **Secondary Color:** #XXXXXX
|
||||
- **Primary Font:** {font-family}
|
||||
- **Voice:** {3 key traits}
|
||||
|
||||
## 1. Color Palette
|
||||
|
||||
### Primary Colors
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| {Name} | #{hex} | rgb({r},{g},{b}) | Primary brand color, CTAs, headers |
|
||||
| {Name} | #{hex} | rgb({r},{g},{b}) | Supporting accent |
|
||||
|
||||
### Secondary Colors
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| {Name} | #{hex} | rgb({r},{g},{b}) | Secondary elements |
|
||||
| {Name} | #{hex} | rgb({r},{g},{b}) | Highlights |
|
||||
|
||||
### Neutral Palette
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| Background | #{hex} | rgb({r},{g},{b}) | Page backgrounds |
|
||||
| Text Primary | #{hex} | rgb({r},{g},{b}) | Body text |
|
||||
| Text Secondary | #{hex} | rgb({r},{g},{b}) | Captions, muted text |
|
||||
| Border | #{hex} | rgb({r},{g},{b}) | Dividers, borders |
|
||||
|
||||
### Accessibility
|
||||
- Text/Background Contrast: {ratio}:1 (WCAG {level})
|
||||
- CTA Contrast: {ratio}:1
|
||||
- All interactive elements meet WCAG 2.1 AA
|
||||
|
||||
## 2. Typography
|
||||
|
||||
### Font Stack
|
||||
```css
|
||||
--font-heading: '{Font}', sans-serif;
|
||||
--font-body: '{Font}', sans-serif;
|
||||
--font-mono: '{Font}', monospace;
|
||||
```
|
||||
|
||||
### Type Scale
|
||||
| Element | Font | Weight | Size (Desktop/Mobile) | Line Height |
|
||||
|---------|------|--------|----------------------|-------------|
|
||||
| H1 | {font} | 700 | 48px / 32px | 1.2 |
|
||||
| H2 | {font} | 600 | 36px / 28px | 1.25 |
|
||||
| H3 | {font} | 600 | 28px / 24px | 1.3 |
|
||||
| H4 | {font} | 600 | 24px / 20px | 1.35 |
|
||||
| Body | {font} | 400 | 16px / 16px | 1.5 |
|
||||
| Small | {font} | 400 | 14px / 14px | 1.5 |
|
||||
| Caption | {font} | 400 | 12px / 12px | 1.4 |
|
||||
|
||||
## 3. Logo Usage
|
||||
|
||||
### Variants
|
||||
- **Primary:** Full horizontal logo with wordmark
|
||||
- **Stacked:** Vertical arrangement for square spaces
|
||||
- **Icon:** Symbol only for favicons, app icons
|
||||
- **Monochrome:** Single color for limited palettes
|
||||
|
||||
### Clear Space
|
||||
Minimum clear space = height of logo mark
|
||||
|
||||
### Minimum Size
|
||||
- Digital: 80px width minimum
|
||||
- Print: 25mm width minimum
|
||||
|
||||
### Don'ts
|
||||
- Don't rotate or skew
|
||||
- Don't change colors outside approved palette
|
||||
- Don't add effects (shadows, gradients)
|
||||
- Don't crop or modify proportions
|
||||
- Don't place on busy backgrounds
|
||||
|
||||
## 4. Voice & Tone
|
||||
|
||||
### Brand Personality
|
||||
{Trait 1}: {Description}
|
||||
{Trait 2}: {Description}
|
||||
{Trait 3}: {Description}
|
||||
|
||||
### Voice Chart
|
||||
| Trait | We Are | We Are Not |
|
||||
|-------|--------|------------|
|
||||
| {Trait} | {Description} | {Anti-description} |
|
||||
|
||||
### Tone by Context
|
||||
| Context | Tone | Example |
|
||||
|---------|------|---------|
|
||||
| Marketing | {tone} | "{example}" |
|
||||
| Support | {tone} | "{example}" |
|
||||
| Error Messages | {tone} | "{example}" |
|
||||
| Success | {tone} | "{example}" |
|
||||
|
||||
### Prohibited Terms
|
||||
- {term 1} (reason)
|
||||
- {term 2} (reason)
|
||||
|
||||
## 5. Imagery Guidelines
|
||||
|
||||
### Photography Style
|
||||
- {Lighting preference}
|
||||
- {Subject guidelines}
|
||||
- {Color treatment}
|
||||
|
||||
### Illustrations
|
||||
- Style: {description}
|
||||
- Colors: Brand palette only
|
||||
- Stroke: {weight}px
|
||||
|
||||
### Icons
|
||||
- Style: {outlined/filled/duotone}
|
||||
- Size: 24px base grid
|
||||
- Corner radius: {value}px
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Copy template above
|
||||
2. Fill in brand-specific values
|
||||
3. Save as `docs/brand-guidelines.md`
|
||||
4. Reference in content workflows
|
||||
|
||||
## Extractable Fields
|
||||
|
||||
Scripts can extract:
|
||||
- `colors.primary`, `colors.secondary`, `colors.neutral`
|
||||
- `typography.heading`, `typography.body`
|
||||
- `voice.traits`, `voice.prohibited`
|
||||
- `logo.variants`, `logo.minSize`
|
||||
186
.claude/skills/brand/references/color-palette-management.md
Normal file
186
.claude/skills/brand/references/color-palette-management.md
Normal file
@ -0,0 +1,186 @@
|
||||
# Color Palette Management
|
||||
|
||||
Guidelines for defining, extracting, and enforcing brand colors.
|
||||
|
||||
## Color System Structure
|
||||
|
||||
### Hierarchy
|
||||
```
|
||||
Primary Colors (1-2)
|
||||
├── Main brand color - Used for CTAs, headers, key elements
|
||||
└── Supporting primary - Secondary emphasis
|
||||
|
||||
Secondary Colors (2-3)
|
||||
├── Accent colors - Highlights, interactive states
|
||||
└── Supporting visuals - Icons, illustrations
|
||||
|
||||
Neutral Palette (3-5)
|
||||
├── Background colors - Page, card, modal backgrounds
|
||||
├── Text colors - Headings, body, muted text
|
||||
└── UI elements - Borders, dividers, shadows
|
||||
|
||||
Semantic Colors (4)
|
||||
├── Success - #22C55E (green)
|
||||
├── Warning - #F59E0B (amber)
|
||||
├── Error - #EF4444 (red)
|
||||
└── Info - #3B82F6 (blue)
|
||||
```
|
||||
|
||||
## Color Documentation Format
|
||||
|
||||
### Markdown Table
|
||||
```markdown
|
||||
| Name | Hex | RGB | HSL | Usage |
|
||||
|------|-----|-----|-----|-------|
|
||||
| Primary Blue | #2563EB | rgb(37,99,235) | hsl(217,91%,53%) | CTAs, links |
|
||||
```
|
||||
|
||||
### CSS Variables
|
||||
```css
|
||||
:root {
|
||||
/* Primary */
|
||||
--color-primary: #2563EB;
|
||||
--color-primary-light: #3B82F6;
|
||||
--color-primary-dark: #1D4ED8;
|
||||
|
||||
/* Secondary */
|
||||
--color-secondary: #8B5CF6;
|
||||
--color-accent: #F59E0B;
|
||||
|
||||
/* Neutral */
|
||||
--color-background: #FFFFFF;
|
||||
--color-surface: #F9FAFB;
|
||||
--color-text-primary: #111827;
|
||||
--color-text-secondary: #6B7280;
|
||||
--color-border: #E5E7EB;
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind Config
|
||||
```javascript
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: '#2563EB',
|
||||
50: '#EFF6FF',
|
||||
100: '#DBEAFE',
|
||||
500: '#3B82F6',
|
||||
600: '#2563EB',
|
||||
700: '#1D4ED8',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility Requirements
|
||||
|
||||
### Contrast Ratios (WCAG 2.1)
|
||||
| Level | Normal Text | Large Text | UI Components |
|
||||
|-------|-------------|------------|---------------|
|
||||
| AA | 4.5:1 | 3:1 | 3:1 |
|
||||
| AAA | 7:1 | 4.5:1 | 4.5:1 |
|
||||
|
||||
### Checking Contrast
|
||||
```javascript
|
||||
// Formula for relative luminance
|
||||
function luminance(r, g, b) {
|
||||
const [rs, gs, bs] = [r, g, b].map(v => {
|
||||
v /= 255;
|
||||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
||||
}
|
||||
|
||||
function contrastRatio(l1, l2) {
|
||||
const lighter = Math.max(l1, l2);
|
||||
const darker = Math.min(l1, l2);
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
```
|
||||
|
||||
## Color Extraction
|
||||
|
||||
### From Images
|
||||
Use `extract-colors.cjs` script to:
|
||||
1. Load image file
|
||||
2. Extract dominant colors using k-means clustering
|
||||
3. Map to nearest brand colors
|
||||
4. Report compliance percentage
|
||||
|
||||
### From Brand Guidelines
|
||||
Parse markdown to extract:
|
||||
- Hex values from tables
|
||||
- CSS variable definitions
|
||||
- Color names and usage descriptions
|
||||
|
||||
## Brand Compliance Validation
|
||||
|
||||
### Rules
|
||||
1. **Primary color ratio**: 60-70% of design
|
||||
2. **Secondary color ratio**: 20-30% of design
|
||||
3. **Accent color ratio**: 5-10% of design
|
||||
4. **Off-brand tolerance**: Max 20% non-palette colors
|
||||
|
||||
### Validation Output
|
||||
```json
|
||||
{
|
||||
"compliance": 85,
|
||||
"colors": {
|
||||
"brand": ["#2563EB", "#8B5CF6", "#FFFFFF"],
|
||||
"offBrand": ["#FF5500"],
|
||||
"dominant": "#2563EB"
|
||||
},
|
||||
"issues": [
|
||||
"Off-brand color #FF5500 detected (15% coverage)",
|
||||
"Primary color underused (45% vs 60% target)"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Color Usage Guidelines
|
||||
|
||||
### Do's
|
||||
- Use primary for main CTAs and key elements
|
||||
- Maintain consistent hover/active states
|
||||
- Test all combinations for accessibility
|
||||
- Document color decisions
|
||||
|
||||
### Don'ts
|
||||
- Use more than 2-3 colors in single component
|
||||
- Mix warm and cool tones without intent
|
||||
- Use pure black (#000) for text (use #111 or similar)
|
||||
- Rely solely on color for meaning (use icons/text too)
|
||||
|
||||
## Color Palette Examples
|
||||
|
||||
### Tech/SaaS
|
||||
```
|
||||
Primary: #2563EB (Blue)
|
||||
Secondary: #8B5CF6 (Purple)
|
||||
Accent: #10B981 (Emerald)
|
||||
Background: #F9FAFB
|
||||
Text: #111827
|
||||
```
|
||||
|
||||
### Marketing/Creative
|
||||
```
|
||||
Primary: #F97316 (Orange)
|
||||
Secondary: #EC4899 (Pink)
|
||||
Accent: #14B8A6 (Teal)
|
||||
Background: #FFFFFF
|
||||
Text: #1F2937
|
||||
```
|
||||
|
||||
### Professional/Corporate
|
||||
```
|
||||
Primary: #1E40AF (Navy)
|
||||
Secondary: #475569 (Slate)
|
||||
Accent: #0EA5E9 (Sky)
|
||||
Background: #F8FAFC
|
||||
Text: #0F172A
|
||||
```
|
||||
|
||||
## Tools & Resources
|
||||
|
||||
- [Coolors](https://coolors.co) - Palette generation
|
||||
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
||||
- [Tailwind Color Reference](https://tailwindcss.com/docs/customizing-colors)
|
||||
- [Color Hunt](https://colorhunt.co) - Curated palettes
|
||||
94
.claude/skills/brand/references/consistency-checklist.md
Normal file
94
.claude/skills/brand/references/consistency-checklist.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Brand Consistency Checklist
|
||||
|
||||
## Visual Consistency
|
||||
|
||||
### Logo
|
||||
- [ ] Correct logo version used
|
||||
- [ ] Proper clear space maintained
|
||||
- [ ] Approved colors only
|
||||
- [ ] Legible at all sizes
|
||||
- [ ] No unauthorized modifications
|
||||
|
||||
### Colors
|
||||
- [ ] Only brand palette colors
|
||||
- [ ] Consistent color application
|
||||
- [ ] Proper contrast for accessibility
|
||||
- [ ] Color ratios maintained
|
||||
|
||||
### Typography
|
||||
- [ ] Brand fonts used
|
||||
- [ ] Correct weights/styles
|
||||
- [ ] Proper hierarchy
|
||||
- [ ] Consistent formatting
|
||||
|
||||
### Imagery
|
||||
- [ ] Matches brand style
|
||||
- [ ] Consistent editing/filters
|
||||
- [ ] Appropriate subjects
|
||||
- [ ] Quality standards met
|
||||
|
||||
## Voice Consistency
|
||||
|
||||
### Tone
|
||||
- [ ] Matches brand personality
|
||||
- [ ] Appropriate for context
|
||||
- [ ] Consistent across channels
|
||||
- [ ] No conflicting messages
|
||||
|
||||
### Language
|
||||
- [ ] Brand terminology used
|
||||
- [ ] Consistent capitalization
|
||||
- [ ] Proper abbreviations
|
||||
- [ ] Jargon level appropriate
|
||||
|
||||
### Messaging
|
||||
- [ ] Aligns with key messages
|
||||
- [ ] Value prop clear
|
||||
- [ ] Differentiators highlighted
|
||||
- [ ] CTAs consistent
|
||||
|
||||
## Channel Audit
|
||||
|
||||
### Website
|
||||
- [ ] Homepage
|
||||
- [ ] Product pages
|
||||
- [ ] Blog/content
|
||||
- [ ] Footer/navigation
|
||||
|
||||
### Social Media
|
||||
- [ ] Profile images
|
||||
- [ ] Cover images
|
||||
- [ ] Bio/about sections
|
||||
- [ ] Post templates
|
||||
|
||||
### Email
|
||||
- [ ] Header/footer
|
||||
- [ ] Templates
|
||||
- [ ] Signatures
|
||||
- [ ] Automated messages
|
||||
|
||||
### Collateral
|
||||
- [ ] Presentations
|
||||
- [ ] One-pagers
|
||||
- [ ] Business cards
|
||||
- [ ] Promotional materials
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Outdated logo | Replace with current version |
|
||||
| Off-brand colors | Update to palette |
|
||||
| Wrong font | Replace with brand font |
|
||||
| Inconsistent voice | Apply style guide |
|
||||
| Mixed messaging | Align to framework |
|
||||
|
||||
## Audit Frequency
|
||||
|
||||
| Asset Type | Frequency |
|
||||
|------------|-----------|
|
||||
| Website | Monthly |
|
||||
| Social profiles | Quarterly |
|
||||
| Email templates | Quarterly |
|
||||
| Sales materials | Quarterly |
|
||||
| Full brand audit | Annually |
|
||||
185
.claude/skills/brand/references/logo-usage-rules.md
Normal file
185
.claude/skills/brand/references/logo-usage-rules.md
Normal file
@ -0,0 +1,185 @@
|
||||
# Logo Usage Rules
|
||||
|
||||
Guidelines for proper logo implementation across all marketing materials.
|
||||
|
||||
## Logo Variants
|
||||
|
||||
### Primary Variants
|
||||
| Variant | File Name | Use Case |
|
||||
|---------|-----------|----------|
|
||||
| Full Horizontal | logo-full-horizontal.{ext} | Website headers, documents |
|
||||
| Stacked | logo-stacked.{ext} | Square spaces, social avatars |
|
||||
| Icon Only | logo-icon.{ext} | Favicons, app icons, small spaces |
|
||||
| Wordmark Only | logo-wordmark.{ext} | When icon already present |
|
||||
|
||||
### Color Variants
|
||||
| Variant | Use Case |
|
||||
|---------|----------|
|
||||
| Full Color | Default on white/light backgrounds |
|
||||
| Reversed | On dark backgrounds |
|
||||
| Monochrome Dark | On light backgrounds when color not possible |
|
||||
| Monochrome Light | On dark backgrounds when color not possible |
|
||||
|
||||
## Clear Space
|
||||
|
||||
### Minimum Clear Space
|
||||
The clear space around the logo should equal the height of the logo mark (icon portion).
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ [x] │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ │ │
|
||||
[x] │ │ [LOGO] │ [x] │
|
||||
│ │ │ │
|
||||
│ └───────────────────┘ │
|
||||
│ [x] │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
Where [x] = height of logo mark
|
||||
|
||||
## Minimum Size
|
||||
|
||||
### Digital
|
||||
| Format | Minimum Width | Notes |
|
||||
|--------|---------------|-------|
|
||||
| Full Logo | 120px | All elements legible |
|
||||
| Icon Only | 24px | Favicon/small icons |
|
||||
| Icon Only | 32px | UI elements |
|
||||
|
||||
### Print
|
||||
| Format | Minimum Width | Notes |
|
||||
|--------|---------------|-------|
|
||||
| Full Logo | 35mm | Business cards, letterhead |
|
||||
| Icon Only | 10mm | Small print items |
|
||||
|
||||
## Color Usage
|
||||
|
||||
### Approved Backgrounds
|
||||
| Background | Logo Version |
|
||||
|------------|--------------|
|
||||
| White | Full color or dark mono |
|
||||
| Light gray (#F5F5F5+) | Full color or dark mono |
|
||||
| Brand primary | Reversed (white) |
|
||||
| Dark (#333 or darker) | Reversed (white) |
|
||||
| Photography | Ensure sufficient contrast |
|
||||
|
||||
### Color Rules
|
||||
1. Never change logo colors outside approved palette
|
||||
2. Don't use gradients on the logo
|
||||
3. Don't apply transparency to logo elements
|
||||
4. Don't add shadows or effects
|
||||
|
||||
## Incorrect Usage
|
||||
|
||||
### Absolute Don'ts
|
||||
- ❌ Stretch or compress logo
|
||||
- ❌ Rotate at angles
|
||||
- ❌ Add drop shadows
|
||||
- ❌ Apply gradient fills
|
||||
- ❌ Use unapproved colors
|
||||
- ❌ Add strokes or outlines
|
||||
- ❌ Place on busy backgrounds
|
||||
- ❌ Crop any portion
|
||||
- ❌ Rearrange elements
|
||||
- ❌ Add additional elements
|
||||
|
||||
### Visual Examples
|
||||
```
|
||||
WRONG: Stretched WRONG: Rotated WRONG: Wrong color
|
||||
┌──────────────┐ ┌────────┐ ┌────────┐
|
||||
│ L O G O │ │ / │ │ LOGO │ <- wrong color
|
||||
└──────────────┘ │ /LOGO │ └────────┘
|
||||
└───────/
|
||||
```
|
||||
|
||||
## Co-branding
|
||||
|
||||
### Partner Logo Guidelines
|
||||
1. Equal visual weight (same height)
|
||||
2. Adequate separation between logos
|
||||
3. Use divider line if needed
|
||||
4. Both logos in their approved colors
|
||||
5. Clear space applies to both
|
||||
|
||||
### Layout Options
|
||||
```
|
||||
Option A: Side by side with divider
|
||||
[OUR LOGO] | [PARTNER LOGO]
|
||||
|
||||
Option B: Stacked
|
||||
[OUR LOGO]
|
||||
+
|
||||
[PARTNER LOGO]
|
||||
```
|
||||
|
||||
## File Formats
|
||||
|
||||
### Recommended Formats
|
||||
| Usage | Format | Notes |
|
||||
|-------|--------|-------|
|
||||
| Web | SVG | Preferred, scalable |
|
||||
| Web fallback | PNG | With transparency |
|
||||
| Print | PDF | Vector, high quality |
|
||||
| Print alt | EPS | Legacy systems |
|
||||
| Documents | PNG | High res (300dpi) |
|
||||
|
||||
### File Organization
|
||||
```
|
||||
assets/logos/
|
||||
├── full-horizontal/
|
||||
│ ├── logo-full-color.svg
|
||||
│ ├── logo-full-color.png
|
||||
│ ├── logo-reversed.svg
|
||||
│ ├── logo-mono-dark.svg
|
||||
│ └── logo-mono-light.svg
|
||||
├── icon-only/
|
||||
│ ├── icon-full-color.svg
|
||||
│ ├── icon-reversed.svg
|
||||
│ └── favicon.ico
|
||||
└── monochrome/
|
||||
├── logo-black.svg
|
||||
└── logo-white.svg
|
||||
```
|
||||
|
||||
## Platform-Specific Guidelines
|
||||
|
||||
### Social Media
|
||||
| Platform | Format | Size | Notes |
|
||||
|----------|--------|------|-------|
|
||||
| LinkedIn | PNG | 300x300px | Icon only |
|
||||
| Twitter/X | PNG | 400x400px | Icon only |
|
||||
| Facebook | PNG | 180x180px | Icon only |
|
||||
| Instagram | PNG | 320x320px | Icon only |
|
||||
|
||||
### Website
|
||||
| Location | Variant | Size |
|
||||
|----------|---------|------|
|
||||
| Header | Full horizontal | 120-200px width |
|
||||
| Footer | Full horizontal | 100-150px width |
|
||||
| Favicon | Icon only | 32x32px |
|
||||
| Apple Touch | Icon only | 180x180px |
|
||||
|
||||
### Documents
|
||||
| Document | Variant | Placement |
|
||||
|----------|---------|-----------|
|
||||
| Letterhead | Full horizontal | Top left |
|
||||
| Presentation | Icon + wordmark | Title slide |
|
||||
| Report | Full horizontal | Cover + footer |
|
||||
|
||||
## Logo Approval Process
|
||||
|
||||
### Before Using Logo
|
||||
1. Verify you have the correct version
|
||||
2. Check background compatibility
|
||||
3. Ensure minimum size requirements
|
||||
4. Confirm clear space allocation
|
||||
5. Review against these guidelines
|
||||
|
||||
### Requesting Approval
|
||||
For non-standard uses:
|
||||
1. Submit mockup showing proposed usage
|
||||
2. Include context (medium, audience)
|
||||
3. Wait for brand team approval
|
||||
4. Document approved exceptions
|
||||
85
.claude/skills/brand/references/messaging-framework.md
Normal file
85
.claude/skills/brand/references/messaging-framework.md
Normal file
@ -0,0 +1,85 @@
|
||||
# Messaging Framework
|
||||
|
||||
## Framework Structure
|
||||
|
||||
```
|
||||
Mission (Why we exist)
|
||||
↓
|
||||
Vision (Where we're going)
|
||||
↓
|
||||
Value Proposition (What we offer)
|
||||
↓
|
||||
Positioning Statement (How we're different)
|
||||
↓
|
||||
Key Messages (What we say)
|
||||
↓
|
||||
Proof Points (Why to believe)
|
||||
```
|
||||
|
||||
## Core Statements
|
||||
|
||||
### Mission Statement
|
||||
```
|
||||
We [action] for [audience] by [method] so they can [outcome].
|
||||
```
|
||||
|
||||
### Vision Statement
|
||||
```
|
||||
A world where [aspiration/change we want to see].
|
||||
```
|
||||
|
||||
### Value Proposition
|
||||
```
|
||||
For [target customer] who [need/problem],
|
||||
[Product/Brand] is a [category]
|
||||
that [key benefit].
|
||||
Unlike [competitors],
|
||||
we [unique differentiator].
|
||||
```
|
||||
|
||||
### Positioning Statement
|
||||
```
|
||||
[Brand] is the [category] for [audience]
|
||||
who want [desired outcome]
|
||||
because [reason to believe].
|
||||
```
|
||||
|
||||
## Message Architecture
|
||||
|
||||
### Primary Message
|
||||
One sentence that captures your core value.
|
||||
|
||||
### Supporting Messages (3-5)
|
||||
Each addresses a different benefit or audience need.
|
||||
|
||||
| Message | Audience Need | Proof Point |
|
||||
|---------|---------------|-------------|
|
||||
| [Message 1] | [Need] | [Evidence] |
|
||||
| [Message 2] | [Need] | [Evidence] |
|
||||
| [Message 3] | [Need] | [Evidence] |
|
||||
|
||||
### Elevator Pitches
|
||||
|
||||
**10-second:**
|
||||
[One sentence that sparks interest]
|
||||
|
||||
**30-second:**
|
||||
[Problem + solution + differentiation]
|
||||
|
||||
**60-second:**
|
||||
[Full pitch with proof points]
|
||||
|
||||
## Message by Audience
|
||||
|
||||
| Audience | Pain Point | Key Message | CTA |
|
||||
|----------|------------|-------------|-----|
|
||||
| [Segment 1] | [Pain] | [Message] | [Action] |
|
||||
| [Segment 2] | [Pain] | [Message] | [Action] |
|
||||
|
||||
## Message Testing
|
||||
|
||||
1. Is it clear? (No jargon)
|
||||
2. Is it differentiated? (Competitors can't say it)
|
||||
3. Is it credible? (Can we prove it)
|
||||
4. Is it compelling? (Does audience care)
|
||||
5. Is it consistent? (Aligns with brand)
|
||||
214
.claude/skills/brand/references/typography-specifications.md
Normal file
214
.claude/skills/brand/references/typography-specifications.md
Normal file
@ -0,0 +1,214 @@
|
||||
# Typography Specifications
|
||||
|
||||
Guidelines for defining and implementing brand typography.
|
||||
|
||||
## Font Stack Structure
|
||||
|
||||
### Primary Fonts
|
||||
```css
|
||||
/* Headings - Display font for impact */
|
||||
--font-heading: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
|
||||
/* Body - Readable for long-form content */
|
||||
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
|
||||
/* Monospace - Code, technical content */
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
```
|
||||
|
||||
### Font Loading
|
||||
```html
|
||||
<!-- Google Fonts (recommended) -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
## Type Scale
|
||||
|
||||
### Base System
|
||||
- Base size: 16px (1rem)
|
||||
- Scale ratio: 1.25 (Major Third)
|
||||
|
||||
### Scale Definition
|
||||
| Element | Size (rem) | Size (px) | Weight | Line Height |
|
||||
|---------|------------|-----------|--------|-------------|
|
||||
| Display | 3.815rem | 61px | 700 | 1.1 |
|
||||
| H1 | 3.052rem | 49px | 700 | 1.2 |
|
||||
| H2 | 2.441rem | 39px | 600 | 1.25 |
|
||||
| H3 | 1.953rem | 31px | 600 | 1.3 |
|
||||
| H4 | 1.563rem | 25px | 600 | 1.35 |
|
||||
| H5 | 1.25rem | 20px | 600 | 1.4 |
|
||||
| Body Large | 1.125rem | 18px | 400 | 1.6 |
|
||||
| Body | 1rem | 16px | 400 | 1.5 |
|
||||
| Small | 0.875rem | 14px | 400 | 1.5 |
|
||||
| Caption | 0.75rem | 12px | 400 | 1.4 |
|
||||
|
||||
### Responsive Adjustments
|
||||
```css
|
||||
/* Mobile (< 768px) */
|
||||
h1 { font-size: 2rem; } /* 32px */
|
||||
h2 { font-size: 1.5rem; } /* 24px */
|
||||
h3 { font-size: 1.25rem; } /* 20px */
|
||||
body { font-size: 1rem; } /* 16px */
|
||||
|
||||
/* Desktop (>= 768px) */
|
||||
h1 { font-size: 3rem; } /* 48px */
|
||||
h2 { font-size: 2.25rem; } /* 36px */
|
||||
h3 { font-size: 1.75rem; } /* 28px */
|
||||
body { font-size: 1rem; } /* 16px */
|
||||
```
|
||||
|
||||
## Font Weights
|
||||
|
||||
### Weight Scale
|
||||
| Name | Value | Usage |
|
||||
|------|-------|-------|
|
||||
| Regular | 400 | Body text, paragraphs |
|
||||
| Medium | 500 | Buttons, nav items |
|
||||
| Semibold | 600 | Subheadings, emphasis |
|
||||
| Bold | 700 | Headings, CTAs |
|
||||
|
||||
### Weight Pairing
|
||||
- Headings: 600-700
|
||||
- Body: 400
|
||||
- Links: 500
|
||||
- Buttons: 600
|
||||
|
||||
## Line Height Guidelines
|
||||
|
||||
### Rules
|
||||
| Content Type | Line Height | Notes |
|
||||
|--------------|-------------|-------|
|
||||
| Headings | 1.1-1.3 | Tighter for visual impact |
|
||||
| Body text | 1.5-1.6 | Optimal readability |
|
||||
| Small text | 1.4-1.5 | Slightly tighter |
|
||||
| Long-form | 1.6-1.75 | Extra comfortable |
|
||||
|
||||
## Letter Spacing
|
||||
|
||||
### Guidelines
|
||||
| Element | Tracking | Value |
|
||||
|---------|----------|-------|
|
||||
| Display | Tighter | -0.02em |
|
||||
| Headings | Normal | 0 |
|
||||
| Body | Normal | 0 |
|
||||
| All caps | Wider | 0.05em |
|
||||
| Small caps | Wider | 0.1em |
|
||||
|
||||
## Paragraph Spacing
|
||||
|
||||
### Margins
|
||||
```css
|
||||
/* Heading spacing */
|
||||
h1, h2 { margin-top: 2rem; margin-bottom: 1rem; }
|
||||
h3, h4 { margin-top: 1.5rem; margin-bottom: 0.75rem; }
|
||||
|
||||
/* Paragraph spacing */
|
||||
p { margin-bottom: 1rem; }
|
||||
p + p { margin-top: 0; }
|
||||
```
|
||||
|
||||
### Maximum Line Length
|
||||
- Body text: 65-75 characters (optimal)
|
||||
- Headings: Can be wider
|
||||
- Code blocks: 80-100 characters
|
||||
|
||||
```css
|
||||
.prose {
|
||||
max-width: 65ch;
|
||||
}
|
||||
```
|
||||
|
||||
## CSS Implementation
|
||||
|
||||
### Full Variables
|
||||
```css
|
||||
:root {
|
||||
/* Font Families */
|
||||
--font-heading: 'Inter', system-ui, sans-serif;
|
||||
--font-body: 'Inter', system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
/* Font Sizes */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
--text-5xl: 3rem;
|
||||
|
||||
/* Font Weights */
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
|
||||
/* Line Heights */
|
||||
--leading-none: 1;
|
||||
--leading-tight: 1.25;
|
||||
--leading-snug: 1.375;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind Config
|
||||
```javascript
|
||||
theme: {
|
||||
fontFamily: {
|
||||
heading: ['Inter', 'system-ui', 'sans-serif'],
|
||||
body: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'monospace'],
|
||||
},
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1rem' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.25rem' }],
|
||||
base: ['1rem', { lineHeight: '1.5rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
||||
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
||||
'5xl': ['3rem', { lineHeight: '1.1' }],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Font Pairings
|
||||
|
||||
### Clean & Modern
|
||||
- Heading: Inter
|
||||
- Body: Inter
|
||||
|
||||
### Professional
|
||||
- Heading: Playfair Display
|
||||
- Body: Source Sans Pro
|
||||
|
||||
### Startup/Tech
|
||||
- Heading: Poppins
|
||||
- Body: Open Sans
|
||||
|
||||
### Editorial
|
||||
- Heading: Merriweather
|
||||
- Body: Lato
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Minimum Sizes
|
||||
- Body text: 16px minimum
|
||||
- Small text: 14px minimum, not for long content
|
||||
- Caption: 12px minimum, use sparingly
|
||||
|
||||
### Contrast Requirements
|
||||
- Text on background: 4.5:1 minimum (AA)
|
||||
- Large text (18px+): 3:1 minimum
|
||||
|
||||
### Best Practices
|
||||
- Don't use all caps for long text
|
||||
- Avoid justified text (use left-align)
|
||||
- Ensure adequate line spacing
|
||||
- Don't use thin weights (<400) at small sizes
|
||||
118
.claude/skills/brand/references/update.md
Normal file
118
.claude/skills/brand/references/update.md
Normal file
@ -0,0 +1,118 @@
|
||||
Update brand colors, typography, and style - automatically syncs to all design system files.
|
||||
|
||||
<args>$ARGUMENTS</args>
|
||||
|
||||
## Overview
|
||||
|
||||
This command systematically updates:
|
||||
1. `docs/brand-guidelines.md` - Human-readable brand doc
|
||||
2. `assets/design-tokens.json` - Token source of truth
|
||||
3. `assets/design-tokens.css` - Generated CSS variables
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Gather Brand Input
|
||||
|
||||
Use `AskUserQuestion` to collect:
|
||||
|
||||
**Theme Selection:**
|
||||
- Theme name (e.g., "Ocean Professional", "Electric Creative", "Forest Calm")
|
||||
|
||||
**Primary Color:**
|
||||
- Color name (e.g., "Ocean Blue", "Coral", "Forest Green")
|
||||
- Hex code (e.g., #3B82F6)
|
||||
|
||||
**Secondary Color:**
|
||||
- Color name (e.g., "Golden Amber", "Electric Purple")
|
||||
- Hex code
|
||||
|
||||
**Accent Color:**
|
||||
- Color name (e.g., "Emerald", "Neon Mint")
|
||||
- Hex code
|
||||
|
||||
**Brand Mood (for AI image generation):**
|
||||
- Mood keywords (e.g., "professional, trustworthy, premium" or "bold, creative, energetic")
|
||||
|
||||
### Step 2: Update Brand Guidelines
|
||||
|
||||
Edit `docs/brand-guidelines.md`:
|
||||
|
||||
1. **Quick Reference table** - Update color names and hex codes
|
||||
2. **Brand Concept section** - Update theme name and description
|
||||
3. **Color Palette section** - Update Primary, Secondary, Accent colors with shades
|
||||
4. **AI Image Generation section** - Update base prompt, keywords, mood descriptors
|
||||
|
||||
### Step 3: Sync to Design Tokens
|
||||
|
||||
Run the sync script:
|
||||
```bash
|
||||
node .claude/skills/brand/scripts/sync-brand-to-tokens.cjs
|
||||
```
|
||||
|
||||
This will:
|
||||
- Update `assets/design-tokens.json` with new color names and values
|
||||
- Regenerate `assets/design-tokens.css` with correct CSS variables
|
||||
|
||||
### Step 4: Verify Sync
|
||||
|
||||
Confirm all files are updated:
|
||||
```bash
|
||||
# Check brand context extraction
|
||||
node .claude/skills/brand/scripts/inject-brand-context.cjs --json | head -30
|
||||
|
||||
# Check CSS variables
|
||||
grep "primary" assets/design-tokens.css | head -5
|
||||
```
|
||||
|
||||
### Step 5: Report
|
||||
|
||||
Output summary:
|
||||
- Theme: [name]
|
||||
- Primary: [name] ([hex])
|
||||
- Secondary: [name] ([hex])
|
||||
- Accent: [name] ([hex])
|
||||
- Files updated: brand-guidelines.md, design-tokens.json, design-tokens.css
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `docs/brand-guidelines.md` | Human-readable brand documentation |
|
||||
| `assets/design-tokens.json` | Token definitions (primitive→semantic→component) |
|
||||
| `assets/design-tokens.css` | CSS variables for UI components |
|
||||
|
||||
## Skills Used
|
||||
|
||||
- `brand` - Brand context extraction and sync
|
||||
- `design-system` - Token generation
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Interactive mode
|
||||
/brand:update
|
||||
|
||||
# With theme hint
|
||||
/brand:update "Ocean Professional"
|
||||
|
||||
# Quick preset
|
||||
/brand:update "midnight purple"
|
||||
```
|
||||
|
||||
## Color Presets
|
||||
|
||||
If user specifies a preset name, use these defaults:
|
||||
|
||||
| Preset | Primary | Secondary | Accent |
|
||||
|--------|---------|-----------|--------|
|
||||
| ocean-professional | #3B82F6 Ocean Blue | #F59E0B Golden Amber | #10B981 Emerald |
|
||||
| electric-creative | #FF6B6B Coral | #9B5DE5 Electric Purple | #00F5D4 Neon Mint |
|
||||
| forest-calm | #059669 Forest Green | #92400E Warm Brown | #FBBF24 Sunlight |
|
||||
| midnight-purple | #7C3AED Violet | #EC4899 Pink | #06B6D4 Cyan |
|
||||
| sunset-warm | #F97316 Orange | #DC2626 Red | #FACC15 Yellow |
|
||||
|
||||
## Important
|
||||
|
||||
- **Always sync all three files** - Never update just brand-guidelines.md alone
|
||||
- **Verify extraction** - Run inject-brand-context.cjs after update to confirm
|
||||
- **Test image generation** - Optionally generate a test image to verify brand application
|
||||
96
.claude/skills/brand/references/visual-identity.md
Normal file
96
.claude/skills/brand/references/visual-identity.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Visual Identity Basics
|
||||
|
||||
## Core Visual Elements
|
||||
|
||||
### Logo
|
||||
- **Primary:** Full logo (horizontal/stacked)
|
||||
- **Secondary:** Abbreviated version
|
||||
- **Icon/Mark:** Symbol only
|
||||
- **Clear space:** Minimum padding around logo
|
||||
- **Minimum size:** Smallest readable size
|
||||
|
||||
### Color Palette
|
||||
```
|
||||
Primary Colors (1-2)
|
||||
├── Main brand color
|
||||
└── Supporting primary
|
||||
|
||||
Secondary Colors (2-3)
|
||||
├── Accent colors
|
||||
└── Supporting visuals
|
||||
|
||||
Neutrals (3-4)
|
||||
├── Text colors
|
||||
├── Background colors
|
||||
└── UI elements
|
||||
```
|
||||
|
||||
### Typography
|
||||
| Usage | Font | Weight | Size |
|
||||
|-------|------|--------|------|
|
||||
| H1 | [Font] | Bold | 32-48px |
|
||||
| H2 | [Font] | Semibold | 24-32px |
|
||||
| Body | [Font] | Regular | 16-18px |
|
||||
| Caption | [Font] | Regular | 12-14px |
|
||||
|
||||
## Visual Guidelines Template
|
||||
|
||||
```markdown
|
||||
## Logo Usage
|
||||
|
||||
### Correct Usage
|
||||
- [Guidelines for proper logo use]
|
||||
|
||||
### Incorrect Usage
|
||||
- Don't stretch or distort
|
||||
- Don't change colors (unless approved)
|
||||
- Don't add effects
|
||||
- Don't place on busy backgrounds
|
||||
|
||||
## Color Specifications
|
||||
|
||||
### Primary Palette
|
||||
| Color | Hex | RGB | Usage |
|
||||
|-------|-----|-----|-------|
|
||||
| [Name] | #XXXXXX | r,g,b | [Where to use] |
|
||||
|
||||
### Accessibility
|
||||
- Text contrast ratio: 4.5:1 minimum
|
||||
- Button contrast: WCAG AA compliant
|
||||
|
||||
## Imagery Style
|
||||
|
||||
### Photography
|
||||
- [Lighting preferences]
|
||||
- [Subject guidelines]
|
||||
- [Composition rules]
|
||||
- [Editing style]
|
||||
|
||||
### Illustrations
|
||||
- [Style description]
|
||||
- [Color usage]
|
||||
- [Complexity level]
|
||||
|
||||
### Icons
|
||||
- [Style: outlined/filled/duotone]
|
||||
- [Stroke weight]
|
||||
- [Corner radius]
|
||||
```
|
||||
|
||||
## Quick Checks
|
||||
|
||||
### Logo
|
||||
- [ ] Correct version for context
|
||||
- [ ] Sufficient clear space
|
||||
- [ ] Legible at size used
|
||||
- [ ] Correct color for background
|
||||
|
||||
### Colors
|
||||
- [ ] From approved palette
|
||||
- [ ] Accessible contrast
|
||||
- [ ] Consistent across materials
|
||||
|
||||
### Typography
|
||||
- [ ] Correct fonts
|
||||
- [ ] Appropriate hierarchy
|
||||
- [ ] Readable size
|
||||
88
.claude/skills/brand/references/voice-framework.md
Normal file
88
.claude/skills/brand/references/voice-framework.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Brand Voice Framework
|
||||
|
||||
## Voice vs. Tone
|
||||
|
||||
**Voice** = Brand's personality (consistent)
|
||||
**Tone** = How voice adapts to context (variable)
|
||||
|
||||
Example: A friendly brand (voice) might be celebratory in a win announcement but empathetic in a support response (tone).
|
||||
|
||||
## Voice Dimensions
|
||||
|
||||
### Tone Spectrum
|
||||
```
|
||||
Formal ←――――――――――――――→ Casual
|
||||
[Legal docs] [Social media]
|
||||
```
|
||||
|
||||
### Language Spectrum
|
||||
```
|
||||
Simple ←――――――――――――――→ Complex
|
||||
[Consumer] [Technical B2B]
|
||||
```
|
||||
|
||||
### Character Spectrum
|
||||
```
|
||||
Serious ←――――――――――――――→ Playful
|
||||
[Finance] [Entertainment]
|
||||
```
|
||||
|
||||
### Emotion Spectrum
|
||||
```
|
||||
Reserved ←――――――――――――――→ Expressive
|
||||
[Corporate] [Lifestyle brand]
|
||||
```
|
||||
|
||||
## Voice Development Process
|
||||
|
||||
### Step 1: Define Personality Traits
|
||||
Choose 3-5 traits that describe your brand:
|
||||
- Confident, not arrogant
|
||||
- Friendly, not unprofessional
|
||||
- Knowledgeable, not condescending
|
||||
- Innovative, not gimmicky
|
||||
- Authentic, not casual
|
||||
|
||||
### Step 2: Create Voice Chart
|
||||
|
||||
| Trait | Description | Do | Don't |
|
||||
|-------|-------------|-----|-------|
|
||||
| [Trait] | [Meaning] | [Example] | [Example] |
|
||||
|
||||
### Step 3: Context Adaptation
|
||||
|
||||
| Context | Tone Shift | Example |
|
||||
|---------|------------|---------|
|
||||
| Social media | More casual | "Hey there!" |
|
||||
| Support | More empathetic | "We understand..." |
|
||||
| Legal | More formal | "In accordance with..." |
|
||||
| Sales | More confident | "You'll see results..." |
|
||||
|
||||
## Voice Testing
|
||||
|
||||
Ask these questions:
|
||||
1. Does this sound like our brand?
|
||||
2. Would a competitor say this?
|
||||
3. Does it resonate with our audience?
|
||||
4. Is it consistent with our values?
|
||||
|
||||
## Voice Guide Template
|
||||
|
||||
```markdown
|
||||
## [Brand] Voice Guide
|
||||
|
||||
### We Are
|
||||
- [Trait 1]: [Description]
|
||||
- [Trait 2]: [Description]
|
||||
- [Trait 3]: [Description]
|
||||
|
||||
### We Sound Like
|
||||
[Example phrases]
|
||||
|
||||
### We Don't Sound Like
|
||||
[Anti-examples]
|
||||
|
||||
### Sample Rewrites
|
||||
Before: [Generic copy]
|
||||
After: [Branded copy]
|
||||
```
|
||||
341
.claude/skills/brand/scripts/extract-colors.cjs
Executable file
341
.claude/skills/brand/scripts/extract-colors.cjs
Executable file
@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* extract-colors.cjs
|
||||
*
|
||||
* Extract dominant colors from an image and compare against brand palette.
|
||||
* Uses pure Node.js without external image processing dependencies.
|
||||
*
|
||||
* For full color extraction from images, integrate with ai-multimodal skill
|
||||
* or use ImageMagick via shell commands.
|
||||
*
|
||||
* Usage:
|
||||
* node extract-colors.cjs <image-path>
|
||||
* node extract-colors.cjs <image-path> --brand-file <path>
|
||||
* node extract-colors.cjs --palette # Show brand palette from guidelines
|
||||
*
|
||||
* Integration:
|
||||
* For image color analysis, use: ai-multimodal skill or ImageMagick
|
||||
* magick <image> -colors 10 -depth 8 -format "%c" histogram:info:
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Default brand guidelines path
|
||||
const DEFAULT_GUIDELINES_PATH = "docs/brand-guidelines.md";
|
||||
|
||||
/**
|
||||
* Extract hex colors from markdown content
|
||||
*/
|
||||
function extractHexColors(text) {
|
||||
const hexPattern = /#[0-9A-Fa-f]{6}\b/g;
|
||||
return [...new Set(text.match(hexPattern) || [])];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse brand guidelines for color palette
|
||||
*/
|
||||
function parseBrandColors(guidelinesPath) {
|
||||
const resolvedPath = path.isAbsolute(guidelinesPath)
|
||||
? guidelinesPath
|
||||
: path.join(process.cwd(), guidelinesPath);
|
||||
|
||||
if (!fs.existsSync(resolvedPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(resolvedPath, "utf-8");
|
||||
|
||||
const palette = {
|
||||
primary: [],
|
||||
secondary: [],
|
||||
neutral: [],
|
||||
semantic: [],
|
||||
all: [],
|
||||
};
|
||||
|
||||
// Extract colors from different sections
|
||||
const sections = [
|
||||
{ name: "primary", regex: /### Primary[\s\S]*?(?=###|##|$)/i },
|
||||
{ name: "secondary", regex: /### Secondary[\s\S]*?(?=###|##|$)/i },
|
||||
{ name: "neutral", regex: /### Neutral[\s\S]*?(?=###|##|$)/i },
|
||||
{ name: "semantic", regex: /### Semantic[\s\S]*?(?=###|##|$)/i },
|
||||
];
|
||||
|
||||
sections.forEach(({ name, regex }) => {
|
||||
const match = content.match(regex);
|
||||
if (match) {
|
||||
const colors = extractHexColors(match[0]);
|
||||
palette[name] = colors;
|
||||
palette.all.push(...colors);
|
||||
}
|
||||
});
|
||||
|
||||
// Dedupe all
|
||||
palette.all = [...new Set(palette.all)];
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex to RGB
|
||||
*/
|
||||
function hexToRgb(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB to hex
|
||||
*/
|
||||
function rgbToHex(r, g, b) {
|
||||
return (
|
||||
"#" +
|
||||
[r, g, b]
|
||||
.map((x) => {
|
||||
const hex = Math.round(x).toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
})
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate color distance (Euclidean in RGB space)
|
||||
*/
|
||||
function colorDistance(color1, color2) {
|
||||
const rgb1 = typeof color1 === "string" ? hexToRgb(color1) : color1;
|
||||
const rgb2 = typeof color2 === "string" ? hexToRgb(color2) : color2;
|
||||
|
||||
if (!rgb1 || !rgb2) return Infinity;
|
||||
|
||||
return Math.sqrt(
|
||||
Math.pow(rgb1.r - rgb2.r, 2) +
|
||||
Math.pow(rgb1.g - rgb2.g, 2) +
|
||||
Math.pow(rgb1.b - rgb2.b, 2)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find nearest brand color
|
||||
*/
|
||||
function findNearestBrandColor(color, brandColors) {
|
||||
let nearest = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
brandColors.forEach((brandColor) => {
|
||||
const distance = colorDistance(color, brandColor);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearest = brandColor;
|
||||
}
|
||||
});
|
||||
|
||||
return { color: nearest, distance: minDistance };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate brand compliance percentage
|
||||
* Distance threshold: 50 (out of max ~441 for RGB)
|
||||
*/
|
||||
function calculateCompliance(extractedColors, brandColors, threshold = 50) {
|
||||
if (!extractedColors || extractedColors.length === 0) return 100;
|
||||
if (!brandColors || brandColors.length === 0) return 0;
|
||||
|
||||
let matchCount = 0;
|
||||
|
||||
extractedColors.forEach((color) => {
|
||||
const nearest = findNearestBrandColor(color, brandColors);
|
||||
if (nearest.distance <= threshold) {
|
||||
matchCount++;
|
||||
}
|
||||
});
|
||||
|
||||
return Math.round((matchCount / extractedColors.length) * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ImageMagick command for color extraction
|
||||
*/
|
||||
function generateImageMagickCommand(imagePath, numColors = 10) {
|
||||
return `magick "${imagePath}" -colors ${numColors} -depth 8 -format "%c" histogram:info:`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ImageMagick histogram output to extract colors
|
||||
*/
|
||||
function parseImageMagickOutput(output) {
|
||||
const colors = [];
|
||||
const lines = output.trim().split("\n");
|
||||
|
||||
lines.forEach((line) => {
|
||||
// Match pattern like: 12345: (255,128,64) #FF8040 srgb(255,128,64)
|
||||
const hexMatch = line.match(/#([0-9A-Fa-f]{6})/);
|
||||
const countMatch = line.match(/^\s*(\d+):/);
|
||||
|
||||
if (hexMatch) {
|
||||
colors.push({
|
||||
hex: "#" + hexMatch[1].toUpperCase(),
|
||||
count: countMatch ? parseInt(countMatch[1]) : 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by count (most common first)
|
||||
colors.sort((a, b) => b.count - a.count);
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display brand palette
|
||||
*/
|
||||
function displayPalette(palette) {
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log("BRAND COLOR PALETTE");
|
||||
console.log("=".repeat(50));
|
||||
|
||||
if (palette.primary.length > 0) {
|
||||
console.log("\nPrimary Colors:");
|
||||
palette.primary.forEach((c) => console.log(` ${c}`));
|
||||
}
|
||||
|
||||
if (palette.secondary.length > 0) {
|
||||
console.log("\nSecondary Colors:");
|
||||
palette.secondary.forEach((c) => console.log(` ${c}`));
|
||||
}
|
||||
|
||||
if (palette.neutral.length > 0) {
|
||||
console.log("\nNeutral Colors:");
|
||||
palette.neutral.forEach((c) => console.log(` ${c}`));
|
||||
}
|
||||
|
||||
if (palette.semantic.length > 0) {
|
||||
console.log("\nSemantic Colors:");
|
||||
palette.semantic.forEach((c) => console.log(` ${c}`));
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log(`Total: ${palette.all.length} colors in brand palette`);
|
||||
console.log("=".repeat(50) + "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const jsonOutput = args.includes("--json");
|
||||
const showPalette = args.includes("--palette");
|
||||
const brandFileIdx = args.indexOf("--brand-file");
|
||||
const brandFile =
|
||||
brandFileIdx !== -1 ? args[brandFileIdx + 1] : DEFAULT_GUIDELINES_PATH;
|
||||
const brandFileValue = brandFileIdx !== -1 ? args[brandFileIdx + 1] : null;
|
||||
const imagePath = args.find(
|
||||
(a) => !a.startsWith("--") && a !== brandFileValue
|
||||
);
|
||||
|
||||
// Load brand palette
|
||||
const brandPalette = parseBrandColors(brandFile);
|
||||
|
||||
if (!brandPalette) {
|
||||
console.error(`Brand guidelines not found at: ${brandFile}`);
|
||||
console.error(`Create brand guidelines or specify path with --brand-file`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Show palette mode
|
||||
if (showPalette || !imagePath) {
|
||||
if (jsonOutput) {
|
||||
console.log(JSON.stringify(brandPalette, null, 2));
|
||||
} else {
|
||||
displayPalette(brandPalette);
|
||||
|
||||
if (!imagePath) {
|
||||
console.log("To extract colors from an image:");
|
||||
console.log(" node extract-colors.cjs <image-path>");
|
||||
console.log("\nOr use ImageMagick directly:");
|
||||
console.log(' magick image.png -colors 10 -depth 8 -format "%c" histogram:info:');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve image path
|
||||
const resolvedPath = path.isAbsolute(imagePath)
|
||||
? imagePath
|
||||
: path.join(process.cwd(), imagePath);
|
||||
|
||||
if (!fs.existsSync(resolvedPath)) {
|
||||
console.error(`Image not found: ${resolvedPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate extraction instructions
|
||||
const result = {
|
||||
image: resolvedPath,
|
||||
brandPalette: brandPalette,
|
||||
extractionCommand: generateImageMagickCommand(resolvedPath),
|
||||
instructions: [
|
||||
"1. Run the ImageMagick command to extract colors:",
|
||||
` ${generateImageMagickCommand(resolvedPath)}`,
|
||||
"",
|
||||
"2. Or use the ai-multimodal skill:",
|
||||
` python .claude/skills/ai-multimodal/scripts/gemini_batch_process.py \\`,
|
||||
` --files "${resolvedPath}" \\`,
|
||||
` --task analyze \\`,
|
||||
` --prompt "Extract the 10 most dominant colors as hex values"`,
|
||||
"",
|
||||
"3. Then compare extracted colors against brand palette",
|
||||
],
|
||||
complianceCheck: {
|
||||
threshold: 50,
|
||||
description:
|
||||
"Colors within distance 50 (RGB space) are considered brand-compliant",
|
||||
brandColors: brandPalette.all,
|
||||
},
|
||||
};
|
||||
|
||||
if (jsonOutput) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("COLOR EXTRACTION HELPER");
|
||||
console.log("=".repeat(60));
|
||||
console.log(`\nImage: ${result.image}`);
|
||||
console.log(`\nBrand Colors: ${brandPalette.all.length} colors loaded`);
|
||||
console.log("\nTo extract colors from this image:\n");
|
||||
result.instructions.forEach((line) => console.log(line));
|
||||
console.log("\n" + "=".repeat(60));
|
||||
|
||||
// Show brand palette for reference
|
||||
console.log("\nBrand Palette Reference:");
|
||||
console.log(` Primary: ${brandPalette.primary.join(", ") || "none"}`);
|
||||
console.log(` Secondary: ${brandPalette.secondary.join(", ") || "none"}`);
|
||||
console.log(` Neutral: ${brandPalette.neutral.join(", ") || "none"}`);
|
||||
console.log("=".repeat(60) + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions for use as module
|
||||
module.exports = {
|
||||
parseBrandColors,
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
colorDistance,
|
||||
findNearestBrandColor,
|
||||
calculateCompliance,
|
||||
parseImageMagickOutput,
|
||||
};
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
349
.claude/skills/brand/scripts/inject-brand-context.cjs
Executable file
349
.claude/skills/brand/scripts/inject-brand-context.cjs
Executable file
@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* inject-brand-context.cjs
|
||||
*
|
||||
* Extracts brand context from markdown brand guidelines
|
||||
* and outputs a formatted system prompt addition.
|
||||
*
|
||||
* Usage:
|
||||
* node inject-brand-context.cjs [path-to-guidelines]
|
||||
* node inject-brand-context.cjs --json [path-to-guidelines]
|
||||
*
|
||||
* Default path: docs/brand-guidelines.md
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Default brand guidelines path
|
||||
const DEFAULT_GUIDELINES_PATH = "docs/brand-guidelines.md";
|
||||
|
||||
/**
|
||||
* Extract hex colors from text
|
||||
*/
|
||||
function extractHexColors(text) {
|
||||
const hexPattern = /#[0-9A-Fa-f]{6}\b/g;
|
||||
return [...new Set(text.match(hexPattern) || [])];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract color data from markdown table
|
||||
*/
|
||||
function extractColorsFromTable(content) {
|
||||
const colors = {
|
||||
primary: [],
|
||||
secondary: [],
|
||||
neutral: [],
|
||||
semantic: [],
|
||||
};
|
||||
|
||||
// Find color tables
|
||||
const primaryMatch = content.match(
|
||||
/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
||||
);
|
||||
const secondaryMatch = content.match(
|
||||
/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
||||
);
|
||||
const neutralMatch = content.match(
|
||||
/### Neutral[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
||||
);
|
||||
const semanticMatch = content.match(
|
||||
/### Semantic[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
||||
);
|
||||
|
||||
if (primaryMatch) colors.primary = extractHexColors(primaryMatch[0]);
|
||||
if (secondaryMatch) colors.secondary = extractHexColors(secondaryMatch[0]);
|
||||
if (neutralMatch) colors.neutral = extractHexColors(neutralMatch[0]);
|
||||
if (semanticMatch) colors.semantic = extractHexColors(semanticMatch[0]);
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract typography info
|
||||
*/
|
||||
function extractTypography(content) {
|
||||
const typography = {
|
||||
heading: null,
|
||||
body: null,
|
||||
mono: null,
|
||||
};
|
||||
|
||||
// Look for font definitions
|
||||
const headingMatch = content.match(/--font-heading:\s*['"]([^'"]+)['"]/);
|
||||
const bodyMatch = content.match(/--font-body:\s*['"]([^'"]+)['"]/);
|
||||
const monoMatch = content.match(/--font-mono:\s*['"]([^'"]+)['"]/);
|
||||
|
||||
// Fallback: look in tables
|
||||
const fontStackMatch = content.match(/### Font Stack[\s\S]*?(?=###|##|$)/i);
|
||||
if (fontStackMatch) {
|
||||
const stackText = fontStackMatch[0];
|
||||
const headingAlt = stackText.match(/heading[^']*['"]([^'"]+)['"]/i);
|
||||
const bodyAlt = stackText.match(/body[^']*['"]([^'"]+)['"]/i);
|
||||
|
||||
if (headingAlt) typography.heading = headingAlt[1];
|
||||
if (bodyAlt) typography.body = bodyAlt[1];
|
||||
}
|
||||
|
||||
if (headingMatch) typography.heading = headingMatch[1];
|
||||
if (bodyMatch) typography.body = bodyMatch[1];
|
||||
if (monoMatch) typography.mono = monoMatch[1];
|
||||
|
||||
return typography;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract voice/tone information
|
||||
*/
|
||||
function extractVoice(content) {
|
||||
const voice = {
|
||||
traits: [],
|
||||
prohibited: [],
|
||||
personality: "",
|
||||
};
|
||||
|
||||
// Extract personality traits from table
|
||||
const personalityMatch = content.match(
|
||||
/### Brand Personality[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (personalityMatch) {
|
||||
const traits = personalityMatch[0].match(
|
||||
/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/g
|
||||
);
|
||||
if (traits) {
|
||||
voice.traits = traits.map((t) => {
|
||||
const match = t.match(/\*\*([^*]+)\*\*/);
|
||||
return match ? match[1].trim() : "";
|
||||
}).filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract prohibited terms
|
||||
const prohibitedMatch = content.match(
|
||||
/### Prohibited[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (prohibitedMatch) {
|
||||
const terms = prohibitedMatch[0].match(/\|\s*([^|]+)\s*\|/g);
|
||||
if (terms) {
|
||||
voice.prohibited = terms
|
||||
.map((t) => t.replace(/\|/g, "").trim())
|
||||
.filter((t) => t && !t.includes("Avoid") && !t.includes("---"));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: look for Forbidden Phrases
|
||||
const forbiddenMatch = content.match(
|
||||
/### Forbidden Phrases[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (forbiddenMatch && voice.prohibited.length === 0) {
|
||||
const items = forbiddenMatch[0].match(/-\s*["']?([^"'\n(]+)/g);
|
||||
if (items) {
|
||||
voice.prohibited = items
|
||||
.map((item) => item.replace(/^-\s*["']?/, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
voice.personality = voice.traits.join(", ");
|
||||
|
||||
return voice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract core attributes
|
||||
*/
|
||||
function extractCoreAttributes(content) {
|
||||
const attributes = [];
|
||||
|
||||
const attributesMatch = content.match(
|
||||
/### Core Attributes[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (attributesMatch) {
|
||||
const rows = attributesMatch[0].match(
|
||||
/\|\s*\*\*([^*]+)\*\*\s*\|\s*([^|]+)\|/g
|
||||
);
|
||||
if (rows) {
|
||||
rows.forEach((row) => {
|
||||
const match = row.match(/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/);
|
||||
if (match) {
|
||||
attributes.push({
|
||||
name: match[1].trim(),
|
||||
description: match[2].trim(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract AI image generation context
|
||||
*/
|
||||
function extractImageStyle(content) {
|
||||
const imageStyle = {
|
||||
basePrompt: "",
|
||||
keywords: [],
|
||||
mood: [],
|
||||
donts: [],
|
||||
examplePrompts: [],
|
||||
};
|
||||
|
||||
// Extract base prompt template (content between ``` blocks after "Base Prompt Template")
|
||||
const basePromptMatch = content.match(
|
||||
/### Base Prompt Template[\s\S]*?```\n?([\s\S]*?)```/i
|
||||
);
|
||||
if (basePromptMatch) {
|
||||
imageStyle.basePrompt = basePromptMatch[1].trim().replace(/\n/g, " ");
|
||||
}
|
||||
|
||||
// Extract style keywords from table
|
||||
const keywordsMatch = content.match(
|
||||
/### Style Keywords[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (keywordsMatch) {
|
||||
const keywordRows = keywordsMatch[0].match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/g);
|
||||
if (keywordRows) {
|
||||
keywordRows.forEach((row) => {
|
||||
const match = row.match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/);
|
||||
if (match) {
|
||||
const keywords = match[1].split(",").map((k) => k.trim()).filter(Boolean);
|
||||
imageStyle.keywords.push(...keywords);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract visual mood descriptors (bullet points)
|
||||
const moodMatch = content.match(
|
||||
/### Visual Mood Descriptors[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (moodMatch) {
|
||||
const moodItems = moodMatch[0].match(/-\s*([^\n]+)/g);
|
||||
if (moodItems) {
|
||||
imageStyle.mood = moodItems.map((item) => item.replace(/^-\s*/, "").trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract visual don'ts from table
|
||||
const dontsMatch = content.match(
|
||||
/### Visual Don'ts[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
||||
);
|
||||
if (dontsMatch) {
|
||||
const dontRows = dontsMatch[0].match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g);
|
||||
if (dontRows) {
|
||||
dontRows.forEach((row) => {
|
||||
const match = row.match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/);
|
||||
if (match && !match[1].includes("Avoid") && !match[1].includes("---")) {
|
||||
imageStyle.donts.push(match[1].trim());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract example prompts (content between ``` blocks after specific headers)
|
||||
const exampleMatch = content.match(/### Example Prompts[\s\S]*?(?=##|$)/i);
|
||||
if (exampleMatch) {
|
||||
const prompts = exampleMatch[0].match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/g);
|
||||
if (prompts) {
|
||||
prompts.forEach((p) => {
|
||||
const match = p.match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/);
|
||||
if (match) {
|
||||
imageStyle.examplePrompts.push({
|
||||
type: match[1].trim(),
|
||||
prompt: match[2].trim().replace(/\n/g, " "),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return imageStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate system prompt addition
|
||||
*/
|
||||
function generatePromptAddition(brandContext) {
|
||||
const { colors, typography, voice, attributes, imageStyle } = brandContext;
|
||||
|
||||
let prompt = `
|
||||
BRAND CONTEXT:
|
||||
==============
|
||||
|
||||
VISUAL IDENTITY:
|
||||
- Primary Colors: ${colors.primary.join(", ") || "Not specified"}
|
||||
- Secondary Colors: ${colors.secondary.join(", ") || "Not specified"}
|
||||
- Typography: ${typography.heading || typography.body || "System fonts"}
|
||||
|
||||
BRAND VOICE:
|
||||
- Personality: ${voice.personality || "Professional"}
|
||||
- Core Attributes: ${attributes.map((a) => a.name).join(", ") || "Not specified"}
|
||||
|
||||
CONTENT RULES:
|
||||
- Prohibited Terms: ${voice.prohibited.join(", ") || "None specified"}
|
||||
`;
|
||||
|
||||
// Add image style context if available
|
||||
if (imageStyle && imageStyle.basePrompt) {
|
||||
prompt += `
|
||||
IMAGE GENERATION:
|
||||
- Base Prompt: ${imageStyle.basePrompt}
|
||||
- Style Keywords: ${imageStyle.keywords.slice(0, 10).join(", ") || "Not specified"}
|
||||
- Visual Mood: ${imageStyle.mood.slice(0, 5).join("; ") || "Not specified"}
|
||||
- Avoid: ${imageStyle.donts.join(", ") || "None specified"}
|
||||
`;
|
||||
}
|
||||
|
||||
prompt += `
|
||||
Apply these brand guidelines to all generated content.
|
||||
Maintain consistent voice, colors, and messaging.
|
||||
`;
|
||||
|
||||
return prompt.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const jsonOutput = args.includes("--json");
|
||||
const guidelinesPath = args.find((a) => !a.startsWith("--")) || DEFAULT_GUIDELINES_PATH;
|
||||
|
||||
// Resolve path
|
||||
const resolvedPath = path.isAbsolute(guidelinesPath)
|
||||
? guidelinesPath
|
||||
: path.join(process.cwd(), guidelinesPath);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(resolvedPath)) {
|
||||
console.error(`Error: Brand guidelines not found at ${resolvedPath}`);
|
||||
console.error(`Create brand guidelines at ${DEFAULT_GUIDELINES_PATH} or specify a path.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read file
|
||||
const content = fs.readFileSync(resolvedPath, "utf-8");
|
||||
|
||||
// Extract brand context
|
||||
const brandContext = {
|
||||
colors: extractColorsFromTable(content),
|
||||
typography: extractTypography(content),
|
||||
voice: extractVoice(content),
|
||||
attributes: extractCoreAttributes(content),
|
||||
imageStyle: extractImageStyle(content),
|
||||
source: resolvedPath,
|
||||
extractedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Output
|
||||
if (jsonOutput) {
|
||||
console.log(JSON.stringify(brandContext, null, 2));
|
||||
} else {
|
||||
console.log(generatePromptAddition(brandContext));
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
266
.claude/skills/brand/scripts/sync-brand-to-tokens.cjs
Normal file
266
.claude/skills/brand/scripts/sync-brand-to-tokens.cjs
Normal file
@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* sync-brand-to-tokens.cjs
|
||||
*
|
||||
* Syncs brand-guidelines.md colors → design-tokens.json → design-tokens.css
|
||||
*
|
||||
* Usage:
|
||||
* node sync-brand-to-tokens.cjs
|
||||
* node sync-brand-to-tokens.cjs --dry-run
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Paths
|
||||
const BRAND_GUIDELINES = 'docs/brand-guidelines.md';
|
||||
const DESIGN_TOKENS_JSON = 'assets/design-tokens.json';
|
||||
const DESIGN_TOKENS_CSS = 'assets/design-tokens.css';
|
||||
const GENERATE_TOKENS_SCRIPT = '.claude/skills/design-system/scripts/generate-tokens.cjs';
|
||||
|
||||
/**
|
||||
* Extract color info from brand guidelines markdown
|
||||
*/
|
||||
function extractColorsFromMarkdown(content) {
|
||||
const colors = {
|
||||
primary: { name: 'primary', shades: {} },
|
||||
secondary: { name: 'secondary', shades: {} },
|
||||
accent: { name: 'accent', shades: {} }
|
||||
};
|
||||
|
||||
// Extract primary color name and hex from Quick Reference table
|
||||
const quickRefMatch = content.match(/Primary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
|
||||
if (quickRefMatch) {
|
||||
colors.primary.name = quickRefMatch[2].toLowerCase().replace(/\s+/g, '-');
|
||||
colors.primary.base = `#${quickRefMatch[1]}`;
|
||||
}
|
||||
|
||||
const secondaryMatch = content.match(/Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
|
||||
if (secondaryMatch) {
|
||||
colors.secondary.name = secondaryMatch[2].toLowerCase().replace(/\s+/g, '-');
|
||||
colors.secondary.base = `#${secondaryMatch[1]}`;
|
||||
}
|
||||
|
||||
const accentMatch = content.match(/Accent Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
|
||||
if (accentMatch) {
|
||||
colors.accent.name = accentMatch[2].toLowerCase().replace(/\s+/g, '-');
|
||||
colors.accent.base = `#${accentMatch[1]}`;
|
||||
}
|
||||
|
||||
// Extract all shades from Primary Colors table
|
||||
const primarySection = content.match(/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
|
||||
if (primarySection) {
|
||||
const hexMatches = primarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
|
||||
for (const match of hexMatches) {
|
||||
const name = match[1].trim().toLowerCase();
|
||||
const hex = `#${match[2]}`;
|
||||
if (name.includes('dark')) colors.primary.dark = hex;
|
||||
else if (name.includes('light')) colors.primary.light = hex;
|
||||
else colors.primary.base = hex;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract secondary shades
|
||||
const secondarySection = content.match(/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
|
||||
if (secondarySection) {
|
||||
const hexMatches = secondarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
|
||||
for (const match of hexMatches) {
|
||||
const name = match[1].trim().toLowerCase();
|
||||
const hex = `#${match[2]}`;
|
||||
if (name.includes('dark')) colors.secondary.dark = hex;
|
||||
else if (name.includes('light')) colors.secondary.light = hex;
|
||||
else colors.secondary.base = hex;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract accent shades
|
||||
const accentSection = content.match(/### Accent Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
|
||||
if (accentSection) {
|
||||
const hexMatches = accentSection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
|
||||
for (const match of hexMatches) {
|
||||
const name = match[1].trim().toLowerCase();
|
||||
const hex = `#${match[2]}`;
|
||||
if (name.includes('dark')) colors.accent.dark = hex;
|
||||
else if (name.includes('light')) colors.accent.light = hex;
|
||||
else colors.accent.base = hex;
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate color scale from base color (simple approach)
|
||||
*/
|
||||
function generateColorScale(baseHex, darkHex, lightHex) {
|
||||
// Use provided shades or generate approximations
|
||||
return {
|
||||
"50": { "$value": lightHex || adjustBrightness(baseHex, 0.9), "$type": "color" },
|
||||
"100": { "$value": lightHex || adjustBrightness(baseHex, 0.8), "$type": "color" },
|
||||
"200": { "$value": adjustBrightness(baseHex, 0.6), "$type": "color" },
|
||||
"300": { "$value": adjustBrightness(baseHex, 0.4), "$type": "color" },
|
||||
"400": { "$value": adjustBrightness(baseHex, 0.2), "$type": "color" },
|
||||
"500": { "$value": baseHex, "$type": "color" },
|
||||
"600": { "$value": darkHex || adjustBrightness(baseHex, -0.15), "$type": "color" },
|
||||
"700": { "$value": adjustBrightness(baseHex, -0.3), "$type": "color" },
|
||||
"800": { "$value": adjustBrightness(baseHex, -0.45), "$type": "color" },
|
||||
"900": { "$value": adjustBrightness(baseHex, -0.6), "$type": "color" }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust hex color brightness
|
||||
*/
|
||||
function adjustBrightness(hex, percent) {
|
||||
const num = parseInt(hex.replace('#', ''), 16);
|
||||
const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));
|
||||
const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent)));
|
||||
const b = Math.min(255, Math.max(0, (num & 0x0000FF) + Math.round(255 * percent)));
|
||||
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update design tokens JSON
|
||||
*/
|
||||
function updateDesignTokens(tokens, colors) {
|
||||
// Update brand name
|
||||
const brandName = `ClaudeKit Marketing - ${colors.primary.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}`;
|
||||
tokens.brand = brandName;
|
||||
|
||||
// Update primitive colors with new names
|
||||
const primitiveColors = tokens.primitive?.color || {};
|
||||
|
||||
// Remove old color keys, add new ones
|
||||
delete primitiveColors.coral;
|
||||
delete primitiveColors.purple;
|
||||
delete primitiveColors.mint;
|
||||
|
||||
// Add new named colors
|
||||
primitiveColors[colors.primary.name] = generateColorScale(
|
||||
colors.primary.base,
|
||||
colors.primary.dark,
|
||||
colors.primary.light
|
||||
);
|
||||
primitiveColors[colors.secondary.name] = generateColorScale(
|
||||
colors.secondary.base,
|
||||
colors.secondary.dark,
|
||||
colors.secondary.light
|
||||
);
|
||||
primitiveColors[colors.accent.name] = generateColorScale(
|
||||
colors.accent.base,
|
||||
colors.accent.dark,
|
||||
colors.accent.light
|
||||
);
|
||||
|
||||
tokens.primitive.color = primitiveColors;
|
||||
|
||||
// Update ALL semantic color references
|
||||
if (tokens.semantic?.color) {
|
||||
const sem = tokens.semantic.color;
|
||||
const p = colors.primary.name;
|
||||
const s = colors.secondary.name;
|
||||
const a = colors.accent.name;
|
||||
|
||||
// Primary variants
|
||||
sem.primary = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };
|
||||
sem['primary-hover'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };
|
||||
sem['primary-active'] = { "$value": `{primitive.color.${p}.700}`, "$type": "color" };
|
||||
sem['primary-light'] = { "$value": `{primitive.color.${p}.400}`, "$type": "color" };
|
||||
sem['primary-lighter'] = { "$value": `{primitive.color.${p}.100}`, "$type": "color" };
|
||||
sem['primary-dark'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };
|
||||
|
||||
// Secondary variants
|
||||
sem.secondary = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };
|
||||
sem['secondary-hover'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };
|
||||
sem['secondary-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };
|
||||
sem['secondary-dark'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };
|
||||
|
||||
// Accent variants
|
||||
sem.accent = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };
|
||||
sem['accent-hover'] = { "$value": `{primitive.color.${a}.600}`, "$type": "color" };
|
||||
sem['accent-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };
|
||||
|
||||
// Status colors (use accent for success, primary for error/info)
|
||||
sem.success = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };
|
||||
sem['success-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };
|
||||
sem.error = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };
|
||||
sem['error-light'] = { "$value": `{primitive.color.${p}.300}`, "$type": "color" };
|
||||
sem.info = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };
|
||||
sem['info-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };
|
||||
}
|
||||
|
||||
// Update component references (button uses primary color with opacity)
|
||||
if (tokens.component?.button?.secondary) {
|
||||
const primaryBase = colors.primary.base;
|
||||
tokens.component.button.secondary['bg-hover'] = {
|
||||
"$value": `${primaryBase}1A`,
|
||||
"$type": "color"
|
||||
};
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
function main() {
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
|
||||
console.log('🔄 Syncing brand guidelines → design tokens\n');
|
||||
|
||||
// Read brand guidelines
|
||||
const guidelinesPath = path.resolve(process.cwd(), BRAND_GUIDELINES);
|
||||
if (!fs.existsSync(guidelinesPath)) {
|
||||
console.error(`❌ Brand guidelines not found: ${guidelinesPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const guidelinesContent = fs.readFileSync(guidelinesPath, 'utf-8');
|
||||
|
||||
// Extract colors
|
||||
const colors = extractColorsFromMarkdown(guidelinesContent);
|
||||
console.log('📊 Extracted colors:');
|
||||
console.log(` Primary: ${colors.primary.name} (${colors.primary.base})`);
|
||||
console.log(` Secondary: ${colors.secondary.name} (${colors.secondary.base})`);
|
||||
console.log(` Accent: ${colors.accent.name} (${colors.accent.base})\n`);
|
||||
|
||||
// Read existing tokens
|
||||
const tokensPath = path.resolve(process.cwd(), DESIGN_TOKENS_JSON);
|
||||
let tokens = {};
|
||||
if (fs.existsSync(tokensPath)) {
|
||||
tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
|
||||
}
|
||||
|
||||
// Update tokens
|
||||
tokens = updateDesignTokens(tokens, colors);
|
||||
|
||||
if (dryRun) {
|
||||
console.log('📋 Would update design-tokens.json:');
|
||||
console.log(JSON.stringify(tokens.primitive.color, null, 2).slice(0, 500) + '...');
|
||||
console.log('\n⏭️ Dry run - no files changed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Write updated tokens
|
||||
fs.writeFileSync(tokensPath, JSON.stringify(tokens, null, 2));
|
||||
console.log(`✅ Updated: ${DESIGN_TOKENS_JSON}`);
|
||||
|
||||
// Regenerate CSS
|
||||
const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT);
|
||||
if (fs.existsSync(generateScript)) {
|
||||
try {
|
||||
execSync(`node ${generateScript} --config ${DESIGN_TOKENS_JSON} -o ${DESIGN_TOKENS_CSS}`, {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit'
|
||||
});
|
||||
console.log(`✅ Regenerated: ${DESIGN_TOKENS_CSS}`);
|
||||
} catch (e) {
|
||||
console.error('⚠️ Failed to regenerate CSS:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✨ Brand sync complete!');
|
||||
}
|
||||
|
||||
main();
|
||||
387
.claude/skills/brand/scripts/validate-asset.cjs
Executable file
387
.claude/skills/brand/scripts/validate-asset.cjs
Executable file
@ -0,0 +1,387 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* validate-asset.cjs
|
||||
*
|
||||
* Validates marketing assets against brand guidelines.
|
||||
* Checks: file naming, dimensions, file size, metadata.
|
||||
*
|
||||
* Usage:
|
||||
* node validate-asset.cjs <asset-path>
|
||||
* node validate-asset.cjs <asset-path> --json
|
||||
* node validate-asset.cjs <asset-path> --fix
|
||||
*
|
||||
* For color validation of images, use with extract-colors.cjs
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Validation rules
|
||||
const RULES = {
|
||||
naming: {
|
||||
pattern: /^[a-z]+_[a-z0-9-]+_[a-z0-9-]+_\d{8}(_[a-z0-9-]+)?\.[a-z]+$/,
|
||||
description:
|
||||
"{type}_{campaign}_{description}_{timestamp}_{variant}.{ext}",
|
||||
examples: [
|
||||
"banner_claude-launch_hero-image_20251209.png",
|
||||
"logo_brand-refresh_horizontal_20251209_dark.svg",
|
||||
],
|
||||
},
|
||||
dimensions: {
|
||||
banner: { minWidth: 600, minHeight: 300 },
|
||||
logo: { minWidth: 100, minHeight: 100 },
|
||||
design: { minWidth: 800, minHeight: 600 },
|
||||
video: { minWidth: 640, minHeight: 480 },
|
||||
default: { minWidth: 100, minHeight: 100 },
|
||||
},
|
||||
fileSize: {
|
||||
image: { max: 5 * 1024 * 1024, recommended: 1 * 1024 * 1024 },
|
||||
video: { max: 100 * 1024 * 1024, recommended: 50 * 1024 * 1024 },
|
||||
svg: { max: 500 * 1024, recommended: 100 * 1024 },
|
||||
},
|
||||
formats: {
|
||||
image: ["png", "jpg", "jpeg", "webp", "gif"],
|
||||
vector: ["svg"],
|
||||
video: ["mp4", "mov", "webm"],
|
||||
document: ["pdf", "psd", "ai", "fig"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse asset filename
|
||||
*/
|
||||
function parseFilename(filename) {
|
||||
const parts = filename.replace(/\.[^.]+$/, "").split("_");
|
||||
|
||||
if (parts.length < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: parts[0],
|
||||
campaign: parts[1],
|
||||
description: parts[2],
|
||||
timestamp: parts[3],
|
||||
variant: parts.length > 4 ? parts[4] : null,
|
||||
extension: path.extname(filename).slice(1).toLowerCase(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate filename convention
|
||||
*/
|
||||
function validateFilename(filename) {
|
||||
const issues = [];
|
||||
const suggestions = [];
|
||||
|
||||
// Check pattern match
|
||||
if (!RULES.naming.pattern.test(filename)) {
|
||||
issues.push("Filename does not match naming convention");
|
||||
suggestions.push(`Expected format: ${RULES.naming.description}`);
|
||||
suggestions.push(`Examples: ${RULES.naming.examples.join(", ")}`);
|
||||
}
|
||||
|
||||
// Parse and check components
|
||||
const parsed = parseFilename(filename);
|
||||
if (parsed) {
|
||||
// Check timestamp format
|
||||
if (!/^\d{8}$/.test(parsed.timestamp)) {
|
||||
issues.push("Timestamp should be YYYYMMDD format");
|
||||
}
|
||||
|
||||
// Check kebab-case for campaign and description
|
||||
if (parsed.campaign && !/^[a-z0-9-]+$/.test(parsed.campaign)) {
|
||||
issues.push("Campaign name should be kebab-case");
|
||||
}
|
||||
|
||||
if (parsed.description && !/^[a-z0-9-]+$/.test(parsed.description)) {
|
||||
issues.push("Description should be kebab-case");
|
||||
}
|
||||
|
||||
// Check valid type
|
||||
const validTypes = [
|
||||
"banner",
|
||||
"logo",
|
||||
"design",
|
||||
"video",
|
||||
"infographic",
|
||||
"icon",
|
||||
"photo",
|
||||
];
|
||||
if (!validTypes.includes(parsed.type)) {
|
||||
suggestions.push(`Consider using type: ${validTypes.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: issues.length === 0, issues, suggestions, parsed };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file size
|
||||
*/
|
||||
function validateFileSize(filepath, extension) {
|
||||
const issues = [];
|
||||
const warnings = [];
|
||||
|
||||
const stats = fs.statSync(filepath);
|
||||
const size = stats.size;
|
||||
|
||||
let limits;
|
||||
if (RULES.formats.video.includes(extension)) {
|
||||
limits = RULES.fileSize.video;
|
||||
} else if (extension === "svg") {
|
||||
limits = RULES.fileSize.svg;
|
||||
} else {
|
||||
limits = RULES.fileSize.image;
|
||||
}
|
||||
|
||||
if (size > limits.max) {
|
||||
issues.push(
|
||||
`File size (${formatBytes(size)}) exceeds maximum (${formatBytes(
|
||||
limits.max
|
||||
)})`
|
||||
);
|
||||
} else if (size > limits.recommended) {
|
||||
warnings.push(
|
||||
`File size (${formatBytes(size)}) exceeds recommended (${formatBytes(
|
||||
limits.recommended
|
||||
)})`
|
||||
);
|
||||
}
|
||||
|
||||
return { valid: issues.length === 0, issues, warnings, size };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file format
|
||||
*/
|
||||
function validateFormat(extension) {
|
||||
const issues = [];
|
||||
const info = { category: null };
|
||||
|
||||
const allFormats = [
|
||||
...RULES.formats.image,
|
||||
...RULES.formats.vector,
|
||||
...RULES.formats.video,
|
||||
...RULES.formats.document,
|
||||
];
|
||||
|
||||
if (!allFormats.includes(extension)) {
|
||||
issues.push(`Unsupported file format: .${extension}`);
|
||||
return { valid: false, issues, info };
|
||||
}
|
||||
|
||||
// Determine category
|
||||
if (RULES.formats.image.includes(extension)) info.category = "image";
|
||||
else if (RULES.formats.vector.includes(extension)) info.category = "vector";
|
||||
else if (RULES.formats.video.includes(extension)) info.category = "video";
|
||||
else if (RULES.formats.document.includes(extension))
|
||||
info.category = "document";
|
||||
|
||||
return { valid: true, issues, info };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if asset exists in manifest
|
||||
*/
|
||||
function checkManifest(filepath) {
|
||||
const manifestPath = path.join(process.cwd(), ".assets", "manifest.json");
|
||||
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
return { registered: false, message: "Manifest not found" };
|
||||
}
|
||||
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
||||
const relativePath = path.relative(process.cwd(), filepath);
|
||||
const found = manifest.assets?.find(
|
||||
(a) => a.path === relativePath || a.path === filepath
|
||||
);
|
||||
|
||||
return {
|
||||
registered: !!found,
|
||||
message: found ? "Asset registered in manifest" : "Asset not in manifest",
|
||||
asset: found,
|
||||
};
|
||||
} catch {
|
||||
return { registered: false, message: "Error reading manifest" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate suggested filename
|
||||
*/
|
||||
function suggestFilename(original, parsed) {
|
||||
if (!parsed) return null;
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
||||
const type = parsed.type || "asset";
|
||||
const campaign = parsed.campaign || "general";
|
||||
const description = parsed.description || "untitled";
|
||||
const ext = parsed.extension || "png";
|
||||
|
||||
return `${type}_${campaign}_${description}_${today}.${ext}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human readable
|
||||
*/
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Main validation function
|
||||
*/
|
||||
function validateAsset(assetPath) {
|
||||
const results = {
|
||||
path: assetPath,
|
||||
filename: path.basename(assetPath),
|
||||
valid: true,
|
||||
issues: [],
|
||||
warnings: [],
|
||||
suggestions: [],
|
||||
checks: {},
|
||||
};
|
||||
|
||||
// Check file exists
|
||||
if (!fs.existsSync(assetPath)) {
|
||||
results.valid = false;
|
||||
results.issues.push(`File not found: ${assetPath}`);
|
||||
return results;
|
||||
}
|
||||
|
||||
const filename = path.basename(assetPath);
|
||||
const extension = path.extname(filename).slice(1).toLowerCase();
|
||||
|
||||
// 1. Validate filename
|
||||
const filenameResult = validateFilename(filename);
|
||||
results.checks.filename = filenameResult;
|
||||
if (!filenameResult.valid) {
|
||||
results.issues.push(...filenameResult.issues);
|
||||
results.suggestions.push(...filenameResult.suggestions);
|
||||
}
|
||||
|
||||
// 2. Validate format
|
||||
const formatResult = validateFormat(extension);
|
||||
results.checks.format = formatResult;
|
||||
if (!formatResult.valid) {
|
||||
results.issues.push(...formatResult.issues);
|
||||
}
|
||||
|
||||
// 3. Validate file size
|
||||
const sizeResult = validateFileSize(assetPath, extension);
|
||||
results.checks.fileSize = sizeResult;
|
||||
if (!sizeResult.valid) {
|
||||
results.issues.push(...sizeResult.issues);
|
||||
}
|
||||
results.warnings.push(...sizeResult.warnings);
|
||||
|
||||
// 4. Check manifest registration
|
||||
const manifestResult = checkManifest(assetPath);
|
||||
results.checks.manifest = manifestResult;
|
||||
if (!manifestResult.registered) {
|
||||
results.warnings.push("Asset not registered in manifest.json");
|
||||
results.suggestions.push(
|
||||
"Register asset in .assets/manifest.json for tracking"
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Suggest corrected filename if needed
|
||||
if (!filenameResult.valid && filenameResult.parsed) {
|
||||
const suggested = suggestFilename(filename, filenameResult.parsed);
|
||||
if (suggested) {
|
||||
results.suggestions.push(`Suggested filename: ${suggested}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Overall validity
|
||||
results.valid = results.issues.length === 0;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format output for console
|
||||
*/
|
||||
function formatOutput(results) {
|
||||
const lines = [];
|
||||
|
||||
lines.push("\n" + "=".repeat(60));
|
||||
lines.push(`ASSET VALIDATION: ${results.filename}`);
|
||||
lines.push("=".repeat(60));
|
||||
|
||||
lines.push(`\nStatus: ${results.valid ? "PASS" : "FAIL"}`);
|
||||
lines.push(`Path: ${results.path}`);
|
||||
|
||||
if (results.issues.length > 0) {
|
||||
lines.push("\nISSUES:");
|
||||
results.issues.forEach((issue) => lines.push(` - ${issue}`));
|
||||
}
|
||||
|
||||
if (results.warnings.length > 0) {
|
||||
lines.push("\nWARNINGS:");
|
||||
results.warnings.forEach((warning) => lines.push(` - ${warning}`));
|
||||
}
|
||||
|
||||
if (results.suggestions.length > 0) {
|
||||
lines.push("\nSUGGESTIONS:");
|
||||
results.suggestions.forEach((suggestion) =>
|
||||
lines.push(` - ${suggestion}`)
|
||||
);
|
||||
}
|
||||
|
||||
// File size info
|
||||
if (results.checks.fileSize?.size) {
|
||||
lines.push(`\nFile Size: ${formatBytes(results.checks.fileSize.size)}`);
|
||||
}
|
||||
|
||||
lines.push("\n" + "=".repeat(60));
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const jsonOutput = args.includes("--json");
|
||||
const assetPath = args.find((a) => !a.startsWith("--"));
|
||||
|
||||
if (!assetPath) {
|
||||
console.error("Usage: node validate-asset.cjs <asset-path> [--json]");
|
||||
console.error("\nExamples:");
|
||||
console.error(
|
||||
" node validate-asset.cjs assets/banners/social-media/banner_launch_hero_20251209.png"
|
||||
);
|
||||
console.error(
|
||||
" node validate-asset.cjs assets/logos/icon-only/logo-icon.svg --json"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Resolve path
|
||||
const resolvedPath = path.isAbsolute(assetPath)
|
||||
? assetPath
|
||||
: path.join(process.cwd(), assetPath);
|
||||
|
||||
// Validate
|
||||
const results = validateAsset(resolvedPath);
|
||||
|
||||
// Output
|
||||
if (jsonOutput) {
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
} else {
|
||||
console.log(formatOutput(results));
|
||||
}
|
||||
|
||||
// Exit with appropriate code
|
||||
process.exit(results.valid ? 0 : 1);
|
||||
}
|
||||
|
||||
main();
|
||||
275
.claude/skills/brand/templates/brand-guidelines-starter.md
Normal file
275
.claude/skills/brand/templates/brand-guidelines-starter.md
Normal file
@ -0,0 +1,275 @@
|
||||
# Brand Guidelines v1.0
|
||||
|
||||
> Last updated: {DATE}
|
||||
> Status: Draft
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Element | Value |
|
||||
|---------|-------|
|
||||
| Primary Color | #2563EB |
|
||||
| Secondary Color | #8B5CF6 |
|
||||
| Primary Font | Inter |
|
||||
| Voice | Professional, Helpful, Clear |
|
||||
|
||||
---
|
||||
|
||||
## 1. Color Palette
|
||||
|
||||
### Primary Colors
|
||||
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| Primary Blue | #2563EB | rgb(37,99,235) | CTAs, headers, links |
|
||||
| Primary Dark | #1D4ED8 | rgb(29,78,216) | Hover states, emphasis |
|
||||
|
||||
### Secondary Colors
|
||||
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| Secondary Purple | #8B5CF6 | rgb(139,92,246) | Accents, highlights |
|
||||
| Accent Green | #10B981 | rgb(16,185,129) | Success, positive states |
|
||||
|
||||
### Neutral Palette
|
||||
|
||||
| Name | Hex | RGB | Usage |
|
||||
|------|-----|-----|-------|
|
||||
| Background | #FFFFFF | rgb(255,255,255) | Page backgrounds |
|
||||
| Surface | #F9FAFB | rgb(249,250,251) | Cards, sections |
|
||||
| Text Primary | #111827 | rgb(17,24,39) | Headings, body text |
|
||||
| Text Secondary | #6B7280 | rgb(107,114,128) | Captions, muted text |
|
||||
| Border | #E5E7EB | rgb(229,231,235) | Dividers, borders |
|
||||
|
||||
### Semantic Colors
|
||||
|
||||
| State | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| Success | #22C55E | Positive actions, confirmations |
|
||||
| Warning | #F59E0B | Cautions, pending states |
|
||||
| Error | #EF4444 | Errors, destructive actions |
|
||||
| Info | #3B82F6 | Informational messages |
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Text on white background: 7.2:1 contrast ratio (AAA)
|
||||
- Primary on white: 4.6:1 contrast ratio (AA)
|
||||
- All interactive elements meet WCAG 2.1 AA standards
|
||||
|
||||
---
|
||||
|
||||
## 2. Typography
|
||||
|
||||
### Font Stack
|
||||
|
||||
```css
|
||||
--font-heading: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
```
|
||||
|
||||
### Type Scale
|
||||
|
||||
| Element | Size (Desktop) | Size (Mobile) | Weight | Line Height |
|
||||
|---------|----------------|---------------|--------|-------------|
|
||||
| H1 | 48px | 32px | 700 | 1.2 |
|
||||
| H2 | 36px | 28px | 600 | 1.25 |
|
||||
| H3 | 28px | 24px | 600 | 1.3 |
|
||||
| H4 | 24px | 20px | 600 | 1.35 |
|
||||
| Body | 16px | 16px | 400 | 1.5 |
|
||||
| Body Large | 18px | 18px | 400 | 1.6 |
|
||||
| Small | 14px | 14px | 400 | 1.5 |
|
||||
| Caption | 12px | 12px | 400 | 1.4 |
|
||||
|
||||
### Font Loading
|
||||
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Logo Usage
|
||||
|
||||
### Variants
|
||||
|
||||
| Variant | File | Use Case |
|
||||
|---------|------|----------|
|
||||
| Full Horizontal | logo-full-horizontal.svg | Headers, documents |
|
||||
| Stacked | logo-stacked.svg | Square spaces |
|
||||
| Icon Only | logo-icon.svg | Favicons, small spaces |
|
||||
| Monochrome | logo-mono.svg | Limited color contexts |
|
||||
|
||||
### Clear Space
|
||||
|
||||
Minimum clear space = height of the logo icon (mark)
|
||||
|
||||
### Minimum Size
|
||||
|
||||
| Context | Minimum Width |
|
||||
|---------|---------------|
|
||||
| Digital - Full Logo | 120px |
|
||||
| Digital - Icon | 24px |
|
||||
| Print - Full Logo | 35mm |
|
||||
| Print - Icon | 10mm |
|
||||
|
||||
### Don'ts
|
||||
|
||||
- Don't rotate or skew the logo
|
||||
- Don't change colors outside approved palette
|
||||
- Don't add shadows or effects
|
||||
- Don't crop or modify proportions
|
||||
- Don't place on busy backgrounds without sufficient contrast
|
||||
|
||||
---
|
||||
|
||||
## 4. Voice & Tone
|
||||
|
||||
### Brand Personality
|
||||
|
||||
| Trait | Description |
|
||||
|-------|-------------|
|
||||
| **Professional** | Expert knowledge, authoritative yet approachable |
|
||||
| **Helpful** | Solution-focused, actionable guidance |
|
||||
| **Clear** | Direct communication, jargon-free |
|
||||
| **Confident** | Assured without being arrogant |
|
||||
|
||||
### Voice Chart
|
||||
|
||||
| Trait | We Are | We Are Not |
|
||||
|-------|--------|------------|
|
||||
| Professional | Expert, knowledgeable | Stuffy, corporate |
|
||||
| Helpful | Supportive, empowering | Patronizing |
|
||||
| Clear | Direct, concise | Vague, wordy |
|
||||
| Confident | Assured, trustworthy | Arrogant, overselling |
|
||||
|
||||
### Tone by Context
|
||||
|
||||
| Context | Tone | Example |
|
||||
|---------|------|---------|
|
||||
| Marketing | Engaging, benefit-focused | "Create campaigns that convert." |
|
||||
| Documentation | Clear, instructional | "Run the command to start." |
|
||||
| Error messages | Calm, solution-focused | "Try refreshing the page." |
|
||||
| Success | Brief, celebratory | "Campaign published!" |
|
||||
|
||||
### Prohibited Terms
|
||||
|
||||
| Avoid | Reason |
|
||||
|-------|--------|
|
||||
| Revolutionary | Overused |
|
||||
| Best-in-class | Vague claim |
|
||||
| Seamless | Overused |
|
||||
| Synergy | Corporate jargon |
|
||||
| Leverage | Use "use" instead |
|
||||
|
||||
---
|
||||
|
||||
## 5. Imagery Guidelines
|
||||
|
||||
### Photography Style
|
||||
|
||||
- **Lighting:** Natural, soft lighting preferred
|
||||
- **Subjects:** Real people, authentic scenarios
|
||||
- **Color treatment:** Maintain brand colors in post
|
||||
- **Composition:** Clean, focused subjects
|
||||
|
||||
### Illustrations
|
||||
|
||||
- Style: Modern, flat design with subtle gradients
|
||||
- Colors: Brand palette only
|
||||
- Line weight: 2px consistent stroke
|
||||
- Corners: 4px rounded
|
||||
|
||||
### Icons
|
||||
|
||||
- Style: Outlined, 24px base grid
|
||||
- Stroke: 1.5px consistent
|
||||
- Corner radius: 2px
|
||||
- Fill: None (outline only)
|
||||
|
||||
---
|
||||
|
||||
## 6. Design Components
|
||||
|
||||
### Buttons
|
||||
|
||||
| Type | Background | Text | Border Radius |
|
||||
|------|------------|------|---------------|
|
||||
| Primary | #2563EB | #FFFFFF | 8px |
|
||||
| Secondary | Transparent | #2563EB | 8px |
|
||||
| Tertiary | Transparent | #6B7280 | 8px |
|
||||
|
||||
### Spacing Scale
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px | Tight spacing |
|
||||
| sm | 8px | Compact elements |
|
||||
| md | 16px | Standard spacing |
|
||||
| lg | 24px | Section spacing |
|
||||
| xl | 32px | Large gaps |
|
||||
| 2xl | 48px | Section dividers |
|
||||
|
||||
### Border Radius
|
||||
|
||||
| Element | Radius |
|
||||
|---------|--------|
|
||||
| Buttons | 8px |
|
||||
| Cards | 12px |
|
||||
| Inputs | 8px |
|
||||
| Modals | 16px |
|
||||
| Pills/Tags | 9999px |
|
||||
|
||||
---
|
||||
|
||||
## AI Image Generation
|
||||
|
||||
### Base Prompt Template
|
||||
|
||||
Always prepend to image generation prompts:
|
||||
|
||||
```
|
||||
{DESCRIBE YOUR VISUAL STYLE HERE - mood, colors with hex codes, lighting, atmosphere}
|
||||
```
|
||||
|
||||
### Style Keywords
|
||||
|
||||
| Category | Keywords |
|
||||
|----------|----------|
|
||||
| **Lighting** | {e.g., soft lighting, dramatic, natural} |
|
||||
| **Mood** | {e.g., professional, energetic, calm} |
|
||||
| **Composition** | {e.g., centered, rule of thirds, minimal} |
|
||||
| **Treatment** | {e.g., high contrast, muted, vibrant} |
|
||||
| **Aesthetic** | {e.g., modern, vintage, minimalist} |
|
||||
|
||||
### Visual Mood Descriptors
|
||||
|
||||
- {Mood descriptor 1}
|
||||
- {Mood descriptor 2}
|
||||
- {Mood descriptor 3}
|
||||
|
||||
### Visual Don'ts
|
||||
|
||||
| Avoid | Reason |
|
||||
|-------|--------|
|
||||
| {Item to avoid} | {Why to avoid it} |
|
||||
|
||||
### Example Prompts
|
||||
|
||||
**Hero Banner:**
|
||||
```
|
||||
{Example prompt for hero banners}
|
||||
```
|
||||
|
||||
**Social Media Post:**
|
||||
```
|
||||
{Example prompt for social graphics}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0 | {DATE} | Initial guidelines |
|
||||
Loading…
x
Reference in New Issue
Block a user