AgentSkillsCN

color-expert

基于色彩理论原则的狗狗打包袋配色/主题系统专家指南

SKILL.md
--- frontmatter
name: color-expert
description: Expert guide for the Doggy Bag color/theme system with color theory principles
license: MIT
compatibility: opencode
metadata:
  audience: developers
  workflow: design

Color Expert - Theme System Guide

Use this skill when working with colors, themes, or the visual design system in Doggy Bag.

Critical Rules

NEVER Use These Patterns

  1. No hardcoded hex colors - Always use CSS variables
  2. No -dark or -light suffixes - Theme profiles handle light/dark variations
  3. No danger variable - Use error instead (consolidated naming)
  4. No inline rgba() fallbacks - Define in defaults.ts instead

Naming Convention (MUST FOLLOW)

code
{color}         : Primary color (text, icons, solid elements)
{color}-hover   : Hover state for buttons/interactive elements
{color}-bg      : Background fill (~0.1 opacity)
{color}-muted   : Subtle background for hover states (~0.05 opacity)
{color}-border  : Border color (~0.3 opacity)

Examples:

  • --success (primary) → --success-hover (button hover) → --success-bg (background)
  • --error (primary) → --error-hover (button hover) → --error-muted (subtle hover bg)

Anti-patterns (DO NOT USE):

  • --success-dark → Use --success-hover instead
  • --error-dark → Use --error-hover instead
  • --warning-light → Use --warning (theme handles light/dark)
  • --purple-dark → Use --purple-hover instead
  • --danger → Use --error instead

Color Variables Reference

Required Colors (7) - Must Be Defined

VariableDark ValueLight ValuePurpose
--bg-base#0f0f1a#e5e7ebPage background
--bg-surface#1a1a2e#ffffffCards/panels
--bg-elevated#252538#f3f4f6Modals/dropdowns
--text-primary#e4e4e7#1a1a2eMain text
--text-secondary#949494#525252Muted text
--accent#24c8db#0e7490Primary action color
--border-default#3d3d66#6b7280Default borders

Optional Colors (~40) - Have Smart Defaults

Text Variants

VariablePurpose
--text-tertiaryEven more muted text
--text-inverseText on accent backgrounds
--text-disabledDisabled element text

Border Variants

VariablePurpose
--border-hoverBorder on hover
--border-focusBorder on focus (usually accent)
--border-subtleSofter borders (list dividers, etc.)

Accent Variants

VariablePurpose
--accent-hoverAccent on hover (darker/lighter)
--accent-mutedTransparent accent for backgrounds
--accent-borderAccent border color

Semantic: Success

VariablePurpose
--successSuccess state (green)
--success-hoverSuccess on hover
--success-bgSuccess background (10% opacity)
--success-mutedSubtle success background (5%)
--success-borderSuccess border (30% opacity)

Semantic: Error

VariablePurpose
--errorError state (red)
--error-hoverError on hover
--error-bgError background
--error-mutedSubtle error background
--error-borderError border

Semantic: Warning

VariablePurpose
--warningWarning state (amber)
--warning-hoverWarning on hover
--warning-bgWarning background
--warning-mutedSubtle warning background
--warning-borderWarning border

Semantic: Info

VariablePurpose
--infoInfo state (often same as accent)
--info-hoverInfo on hover
--info-bgInfo background
--info-mutedSubtle info background

Special Colors

VariablePurpose
--purplePurple accent (credit cards, etc.)
--purple-hoverPurple on hover
--purple-bgPurple background
--purple-mutedSubtle purple background
--orangeOrange accent
--orange-hoverOrange on hover
--orange-bgOrange background

Financial Semantics

VariablePurpose
--income-colorIncome amounts (green)
--expense-colorExpense amounts (red)
--positive-colorPositive values
--negative-colorNegative values

State Colors

VariablePurpose
--paid-colorPaid status (green)
--unpaid-colorUnpaid status (red)
--partial-colorPartially paid (amber)
--pending-colorPending (yellow)

Theme JSON Format

Schema

json
{
  "id": "string", // Unique identifier (e.g., "dark", "ocean")
  "name": "string", // Display name (e.g., "Dark", "Ocean Blue")
  "version": "1.0", // Schema version
  "isDark": true, // true = dark theme, false = light theme
  "colors": {
    // 7 required colors (must be defined)
    "bg-base": "#hex",
    "bg-surface": "#hex",
    "bg-elevated": "#hex",
    "text-primary": "#hex",
    "text-secondary": "#hex",
    "accent": "#hex",
    "border-default": "#hex",
    // Optional colors (omit to use defaults)
    "success": "#hex"
    // ... etc
  },
  "meta": {
    // Optional metadata
    "description": "string",
    "author": "string"
  }
}

Minimal Theme Example (7 colors)

json
{
  "id": "ocean",
  "name": "Ocean",
  "version": "1.0",
  "isDark": true,
  "colors": {
    "bg-base": "#0a1628",
    "bg-surface": "#0f2744",
    "bg-elevated": "#153357",
    "text-primary": "#e0f2fe",
    "text-secondary": "#7dd3fc",
    "accent": "#38bdf8",
    "border-default": "#1e4976"
  },
  "meta": {
    "description": "Deep ocean blue theme",
    "author": "Your Name"
  }
}

All ~40 optional colors will be derived from defaults based on isDark.


Palette Visualization

Terminal Command

bash
make show-palette              # Shows dark theme (default)
make show-palette theme=light  # Shows light theme
make show-palette theme=compare # Side-by-side comparison

This displays actual colored swatches in your terminal using ANSI escape codes.

Note: Run in your terminal directly, not through OpenCode's output panel.

What You'll See

code
══════════════════════════════════════════════════════════════════════
  DARK THEME PALETTE
══════════════════════════════════════════════════════════════════════

  BACKGROUNDS
  ████  #0f0f1a  --bg-base         Page background
  ████  #1a1a2e  --bg-surface      Cards/panels
  ████  #252538  --bg-elevated     Modals/dropdowns

  TEXT
  ████  #e4e4e7  --text-primary    Main text
  ████  #949494  --text-secondary  Muted text
  ████  #7a7a7a  --text-tertiary   Even more muted
  ...

Creating New Color Profiles

Step 1: Choose Base Type

Decide if your theme is dark (isDark: true) or light (isDark: false). This affects how defaults are derived.

Step 2: Create Minimal JSON

Start with just the 7 required colors in data/themes/mytheme.json:

json
{
  "id": "mytheme",
  "name": "My Theme",
  "version": "1.0",
  "isDark": true,
  "colors": {
    "bg-base": "#...",
    "bg-surface": "#...",
    "bg-elevated": "#...",
    "text-primary": "#...",
    "text-secondary": "#...",
    "accent": "#...",
    "border-default": "#..."
  }
}

Step 3: Override Optional Colors (As Needed)

Add optional colors only if you want different values from defaults:

json
{
  "colors": {
    // ... required colors ...
    "success": "#00ff88", // Custom success color
    "income-color": "#00ff88" // Match success
  }
}

Step 4: Validate

typescript
import { validateTheme } from './src/lib/theme/validator';
import { loadTheme } from './src/lib/theme/loader';

const result = validateTheme(myThemeJson);
if (!result.valid) {
  console.error(result.errors);
}

const fullTheme = loadTheme(myThemeJson); // Merges with defaults

Step 5: Test

typescript
import { applyTheme } from './src/lib/theme/applier';
applyTheme(fullTheme); // Applies to document.documentElement

Color Theory Fundamentals

Color Harmony Types

HarmonyDescriptionBest For
ComplementaryOpposite on color wheelHigh contrast accents
AnalogousAdjacent colorsCohesive, subtle palettes
TriadicThree evenly spacedBalanced variety
Split-complementaryBase + two adjacent to complementVibrant but balanced

Choosing an Accent Color

  1. Brand alignment - Match your brand identity
  2. Accessibility - Must have sufficient contrast on backgrounds
  3. Emotional resonance - Cyan = modern/tech, Green = growth, Purple = premium

The 60-30-10 Rule

ProportionRoleExample
60%Background--bg-base, --bg-surface
30%Secondary--text-secondary, --border-default
10%Accent--accent, semantic colors

Color Derivation Patterns

Hover States

code
Dark theme:  base color → lighten 10-15%
Light theme: base color → darken 10-15%

Example:

  • --accent: #24c8db--accent-hover: #1ab0c9 (darker)

Muted/Background Variants

code
accent-muted = rgba(accent, 0.1)
success-bg   = rgba(success, 0.1)
error-border = rgba(error, 0.3)

Semantic Color Families

Each semantic color (success, error, warning, info) has 5 variants:

code
success family:
  --success        = #4ade80  (primary, for icons/text)
  --success-hover  = #22c55e  (hover state for buttons)
  --success-bg     = rgba(..., 0.1)  (subtle background)
  --success-muted  = rgba(..., 0.05) (very subtle for hover)
  --success-border = rgba(..., 0.3)  (visible but soft)

Naming convention:

  • {color} - Primary color (text, icons, solid elements)
  • {color}-hover - Hover state for buttons/interactive elements
  • {color}-bg - Background fill (~0.1 opacity)
  • {color}-muted - Subtle background for hover states (~0.05 opacity)
  • {color}-border - Border color (~0.3 opacity)

Accessibility & Contrast

WCAG Contrast Requirements

LevelNormal Text (< 18pt)Large Text (>= 18pt)
AA (minimum)4.5:13:1
AAA (enhanced)7:14.5:1

Current Dark Theme Contrast Ratios

These ratios have been verified and meet WCAG AA:

PairRatioStatus
text-primary (#e4e4e7) on bg-base (#0f0f1a)~13.5:1Excellent
text-primary on bg-surface (#1a1a2e)~11.5:1Excellent
text-primary on bg-elevated (#252538)~9.5:1Excellent
text-secondary (#949494) on bg-surface~5.5:1Good (AA)
text-tertiary (#7a7a7a) on bg-surface~4.5:1Passes AA
accent (#24c8db) on bg-surface~6.5:1Good
text-inverse (#000) on accent~8.5:1Excellent

Key Pairs to Verify When Changing Colors

ForegroundBackgroundRequirement
--text-primary--bg-baseMust pass AA (4.5:1)
--text-primary--bg-surfaceMust pass AA
--text-secondary--bg-surfaceMust pass AA (~5:1 target)
--text-tertiary--bg-surfaceMust pass AA (4.5:1 min)
--text-inverse--accentMust pass AA (button text)

Minimum Contrast Guidelines

When choosing new colors, follow these minimum contrast ratios:

Variable TypeMinimum RatioTarget RatioNotes
Primary text4.5:110:1+Higher is better for readability
Secondary text4.5:15.5:1Must be clearly readable
Tertiary text4.5:14.5:1Bare minimum for AA
Disabled text3:13:1Intentionally low (indicates disabled)
Borders1.5:12:1+Subtle but visible
Accent on bg4.5:16:1+For text/icons in accent color

Background Hierarchy

Backgrounds must have sufficient distinction to create visual depth:

LevelVariableDark ValuePurposeMin Contrast to Surface
Base--bg-base#0f0f1aPage background1.3:1
Surface--bg-surface#1a1a2eCards, panels— (reference)
Elevated--bg-elevated#252538Modals, dropdowns1.3:1

Tip: If elevated elements look too similar to surface, increase lightness or use shadows/borders for distinction.

Contrast Calculation

Relative luminance formula:

code
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
Contrast = (L1 + 0.05) / (L2 + 0.05)

Tools:


Common Pitfalls & Lessons Learned

Pitfall 1: Using -dark or -light Suffixes

Wrong:

css
color: var(--success-dark);
color: var(--warning-light);

Right:

css
color: var(--success-hover); /* For hover states */
color: var(--warning); /* Theme profiles handle light/dark */

Why: The theme system has separate dark and light profiles in defaults.ts. Each profile defines appropriate colors for its context. Using -dark/-light suffixes creates ambiguity and breaks when switching themes.

Pitfall 2: Inline rgba() Fallbacks

Wrong:

css
background: var(--success-muted, rgba(74, 222, 128, 0.05));

Right:

css
background: var(--success-muted);

Why: If a variable needs a fallback, define it in defaults.ts. Inline fallbacks create maintenance burden and can become out of sync.

Pitfall 3: Insufficient Text Contrast

Common mistake: Using gray values that are too dark for secondary/tertiary text.

VariableBad ValueGood ValueIssue
text-secondary#888888#949494Too low contrast (~4.8:1 → ~5.5:1)
text-tertiary#666666#7a7a7aFails WCAG AA (~3.2:1 → ~4.5:1)

Rule of thumb: On dark backgrounds, secondary text should be at least #909090 or lighter.

Pitfall 4: Glaring Warning Colors

Common mistake: Using highly saturated yellows that cause eye strain.

BadGoodIssue
#fbbf24#e5a91fToo bright/saturated, causes glare
#f59e0b#d4970fSame issue for hover state

Rule of thumb: Desaturate warning/yellow colors slightly for dark themes.

Pitfall 5: Indistinguishable Background Levels

Common mistake: Background hierarchy colors too similar.

LevelBad GapGood Gap
base → surface1.15:11.3:1+
surface → elevated1.1:11.25:1+

Solution: If backgrounds look too similar:

  1. Increase lightness difference (preferred)
  2. Add subtle borders between levels
  3. Use shadows for elevated elements

Pitfall 6: Hardcoded Colors in Components

Wrong:

svelte
<style>
  .card {
    background: #1a1a2e;
  }
</style>

Right:

svelte
<style>
  .card {
    background: var(--bg-surface);
  }
</style>

Detection: Run this command to find hardcoded colors:

bash
rg '#[0-9a-fA-F]{3,8}' -g '*.svelte' src/ --no-heading

Adding New Color Variables

Step 1: Define in types.ts

Add to OPTIONAL_COLOR_KEYS array:

typescript
export const OPTIONAL_COLOR_KEYS = [
  // ... existing keys ...
  'my-new-color',
  'my-new-color-hover',
  'my-new-color-bg',
] as const;

Step 2: Add defaults in defaults.ts

Add values for BOTH dark and light themes:

typescript
// In DARK_THEME_COLORS:
'my-new-color': '#...',
'my-new-color-hover': '#...',
'my-new-color-bg': 'rgba(..., 0.1)',

// In LIGHT_THEME_COLORS:
'my-new-color': '#...',
'my-new-color-hover': '#...',
'my-new-color-bg': 'rgba(..., 0.1)',

Step 3: Verify Contrast

Check that your new color meets WCAG AA on all background levels:

  • On --bg-base
  • On --bg-surface
  • On --bg-elevated

Step 4: Update Documentation

Add to:

  • docs/colour-themes.md - User-facing docs
  • This skill file - Developer reference

Financial Colors

VariableColorPsychology
--income-colorGreenGrowth, positive, "go" signal
--expense-colorRedCaution, negative, attention
--positive-colorGreenUniversal positive association
--negative-colorRedUniversal negative association

Cultural note: Green/red associations are Western-centric. Some cultures reverse these meanings.

State Colors

StateColorReason
PaidGreenCompletion, success
UnpaidRedAttention needed, urgent
PartialAmber/OrangeIn-progress, caution
PendingYellowAwaiting action, neutral alert

Component Usage Rules

Hard Rule (from AGENTS.md)

NO HARDCODED COLORS: Never use hardcoded hex colors in CSS. Always use CSS variables from the theme system.

Correct Usage

css
/* Good - Always use variables */
.card {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-default);
}

.button-primary {
  background: var(--accent);
  color: var(--text-inverse);
}

.button-primary:hover {
  background: var(--accent-hover);
}

.income-amount {
  color: var(--income-color);
}

.expense-amount {
  color: var(--expense-color);
}

Incorrect Usage

css
/* Bad - Never hardcode */
.card {
  background: #1a1a2e; /* DON'T DO THIS */
  color: #e4e4e7; /* DON'T DO THIS */
}

Quick Reference

Files to Modify

TaskFile(s)
Change default colorssrc/lib/theme/defaults.ts
Add new color variablesrc/lib/theme/types.ts (add to arrays) + defaults.ts
Create built-in themesrc/lib/theme/themes/*.json
Create user themedata/themes/*.json

Theme Store API

typescript
import { themeMode, currentTheme, isDarkTheme } from '../stores/theme';

$themeMode; // 'dark' | 'light' | 'system'
$isDarkTheme; // boolean
$currentTheme; // Full Theme object

themeMode.set('light'); // Change theme

Programmatic Theme Creation

typescript
import { createTheme, loadTheme, applyTheme } from './src/lib/theme';

// Create from scratch
const theme = createTheme({
  id: 'custom',
  name: 'Custom',
  isDark: true,
  colors: {
    /* required colors */
  },
});

// Load from JSON
const theme = loadTheme(jsonObject);

// Apply to DOM
applyTheme(theme);

Old → New Color Mapping

For migrating hardcoded colors:

Old HardcodedNew Variable
#0f0f1avar(--bg-base)
#1a1a2evar(--bg-surface)
#252538var(--bg-elevated)
#e4e4e7var(--text-primary)
#949494var(--text-secondary)
#7a7a7avar(--text-tertiary)
#24c8dbvar(--accent)
#1ab0c9var(--accent-hover)
rgba(36, 200, 219, 0.1)var(--accent-muted)
#3d3d66var(--border-default)
#4ade80, #22c55evar(--success)
#f87171, #ff6b6b, #ef4444var(--error)
#e5a91fvar(--warning)
#000, #000000var(--text-inverse)

Quick Checklist

Before Committing Color Changes

  • No hardcoded hex colors in .svelte files
  • All new variables follow naming convention (-hover, -bg, -muted, -border)
  • Values defined in BOTH DARK_THEME_COLORS and LIGHT_THEME_COLORS
  • Text colors meet WCAG AA contrast (4.5:1 minimum)
  • Secondary text is at least #909090 on dark backgrounds
  • Tertiary text is at least #7a7a7a on dark backgrounds
  • No -dark or -light suffixes used
  • No inline rgba() fallbacks
  • Documentation updated if new variables added

Contrast Quick Check

Run in browser DevTools console to check a color pair:

javascript
// Calculate contrast ratio
function contrast(hex1, hex2) {
  const lum = (hex) => {
    const rgb = hex.match(/\w\w/g).map((x) => parseInt(x, 16) / 255);
    const [r, g, b] = rgb.map((c) =>
      c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
    );
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  };
  const L1 = lum(hex1),
    L2 = lum(hex2);
  return ((Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05)).toFixed(2);
}

// Example: text-secondary on bg-surface
contrast('949494', '1a1a2e'); // Should be >= 4.5

Find Hardcoded Colors

bash
# Find all hardcoded hex colors in Svelte files
rg '#[0-9a-fA-F]{3,8}' -g '*.svelte' src/ --no-heading

# Find all CSS variables currently in use
rg 'var\(--[a-z-]+\)' -g '*.svelte' src/ -o | sed 's/.*var(--//' | sed 's/)$//' | sort -u