# Creating a theme Theme Lab uses `src/themes.ts` as its single source of truth. A theme definition supplies display metadata, a complete semantic palette, and optional hero metadata. The Settings UI, validator, Host routes, and cleanup lifecycle all derive from that registry. ## 1. Choose an id and artwork Use a lowercase, hyphenated id such as `aurora-ink`. Add it to `THEME_IDS`, then place a 16:9 WebP at `assets/aurora-ink.webp`. Recommended artwork: - 1600×900 or larger, optimized below 500 KB; - generous negative space around the chat composer; - no readable text, logos, copyrighted characters, or third-party trademarks; - a focal point that still works on narrow screens. ## 2. Add the registry entry ```ts 'aurora-ink': Object.freeze({ id: 'aurora-ink', label: 'Aurora Ink', description: 'A short, concrete palette description.', swatch: 'linear-gradient(135deg,#05070d,#6ee7ff)', tokens: Object.freeze({ // Supply every token listed below with light and dark values. }), hero: hero('aurora-ink', 'center center', '70% center', '.56', '.44'), }), ``` The six hero arguments are id, desktop position, mobile position, desktop opacity, and mobile opacity. The helper derives both the packaged asset and exact `/plugins/dsh-theme-lab/.webp` route. ## 3. Supply all semantic tokens Every custom theme must define these 16 tokens: 1. `--dsw-alias-bg-base` 2. `--dsw-alias-bg-layer-1` 3. `--dsw-alias-bg-layer-2` 4. `--dsw-alias-bg-overlay` 5. `--dsw-alias-border-l1` 6. `--dsw-alias-border-l2` 7. `--dsw-alias-brand-primary` 8. `--dsw-alias-label-primary` 9. `--dsw-alias-label-secondary` 10. `--dsw-alias-state-business-primary` 11. `--dsw-alias-state-error-primary` 12. `--dsw-alias-state-success-primary` 13. `--dsw-alias-state-warn-primary` 14. `--dsw-alias-button-info-fill` 15. `--dsw-alias-button-info-hover` 16. `--dsw-specific-sidebar-fill` Use six-digit hexadecimal colors. The validator requires at least 4.5:1 contrast for labels and states against the base background, and for information buttons against white, in both modes. ## 4. Validate the lifecycle ```sh pnpm bundle pnpm test npm pack --dry-run --json ``` Then test in Harness: - select the new card and reload to prove native Settings persistence; - switch to DeepSeek original and verify that all overrides and hero variables disappear; - start a conversation and verify the hero disappears outside `[data-phase='hero']`; - check desktop and mobile framing; - verify the exact WebP route returns `200 image/webp`; - unload the plugin and confirm its slot, styles, subscription, and token override are disposed. Do not add theme-specific branches to the controller. If a design needs new behavior, open a proposal first so the registry contract stays predictable.