[object Object]

← back to Model Arena

Agent-driven pivot: DW Color Story Studio (frontend-developer, plan+execute, 72KB self-contained luxury tool) — stripped Google Fonts for CSP-safety, registered as featured arcade game

7527c24c8aeb323543c1060d40df7c4e3dd2c0fa · 2026-07-25 08:50:18 -0700 · Steve Abrams

Files touched

Diff

commit 7527c24c8aeb323543c1060d40df7c4e3dd2c0fa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 08:50:18 2026 -0700

    Agent-driven pivot: DW Color Story Studio (frontend-developer, plan+execute, 72KB self-contained luxury tool) — stripped Google Fonts for CSP-safety, registered as featured arcade game
---
 data/arcade-games.json                  |   22 +-
 idea-run/run-next.sh                    |    2 +-
 public/games/dw-color-story-studio.html | 2434 +++++++++++++++++++++++++++++++
 3 files changed, 2454 insertions(+), 4 deletions(-)

diff --git a/data/arcade-games.json b/data/arcade-games.json
index 7198b05..9d32459 100644
--- a/data/arcade-games.json
+++ b/data/arcade-games.json
@@ -1,6 +1,22 @@
 {
   "featured": [
-    { "slug": "tetris", "title": "Tetris", "credit": "claude-code", "note": "Arena champion — All-Models Tetris" },
-    { "slug": "star-clock", "title": "Constellation Clock", "credit": "claude-code", "note": "Arena champion — Free-Roster Constellation Clock" }
+    {
+      "slug": "tetris",
+      "title": "Tetris",
+      "credit": "claude-code",
+      "note": "Arena champion \u2014 All-Models Tetris"
+    },
+    {
+      "slug": "star-clock",
+      "title": "Constellation Clock",
+      "credit": "claude-code",
+      "note": "Arena champion \u2014 Free-Roster Constellation Clock"
+    },
+    {
+      "slug": "dw-color-story-studio",
+      "title": "DW Color Story Studio",
+      "credit": "frontend-developer",
+      "note": "Agent-built flagship \u2014 seasonal wallcovering color-story tool for the DW social team"
+    }
   ]
-}
+}
\ No newline at end of file
diff --git a/idea-run/run-next.sh b/idea-run/run-next.sh
index 8db3e7e..d28adcc 100755
--- a/idea-run/run-next.sh
+++ b/idea-run/run-next.sh
@@ -21,7 +21,7 @@ PROMPT="$PROMPT  DW LUXURY UI REQUIREMENTS (mandatory): (a) a fixed top bar with
 python3 - "$TITLE" "$PROMPT" > /tmp/dwaa-payload.json <<'PY'
 import json,sys
 json.dump({"title":sys.argv[1],"prompt":sys.argv[2],
-           "models":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code"],"designTools":True},
+           "models":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"],"designTools":True},
           sys.stdout)
 PY
 
diff --git a/public/games/dw-color-story-studio.html b/public/games/dw-color-story-studio.html
new file mode 100644
index 0000000..133a5bd
--- /dev/null
+++ b/public/games/dw-color-story-studio.html
@@ -0,0 +1,2434 @@
+<!--
+================================================================================
+DESIGNER WALLCOVERINGS — SEASONAL COLOR STORY STUDIO
+Plan Document (v1.0 — Flagship Build)
+================================================================================
+
+CONCEPT
+-------
+A single-page brand tool for the DW social media team to generate, preview,
+and export "Seasonal Color Stories" — curated 5-swatch wallcovering palettes
+tied to a chosen season. Each story translates into a complete Instagram
+carousel (1080x1080 cover + 3 color slides + CTA) with ready-to-copy caption
+and hashtag blocks, plus an exportable palette summary card.
+
+The aesthetic is uncompromisingly luxury: Cormorant Garamond wordmark, warm
+near-black background (#0d0b09), brass accent (#c9a961), hairline strokes,
+generous whitespace, and rich CSS/SVG textures that convey tactile material.
+
+TARGET USER
+-----------
+DW social manager / content coordinator. They need:
+  1. Choose a season in one click → instant curated palette appears
+  2. Preview exactly how each Instagram slide looks before posting
+  3. Copy caption + hashtags without leaving the tool
+  4. Export/share a palette summary card (print-ready PNG-style card via CSS)
+
+UX FLOW
+-------
+  [1] LANDING: Full-width hero with wordmark + season selector (4 seasonal tabs)
+      → Default = current / near-current season (Autumn for this build)
+  [2] COLOR STORY REVEAL: 5 swatches animate in (staggered 80ms), each showing:
+        • CSS/SVG texture swatch (distinct motif per color)
+        • Designer color name (e.g. "Tarnished Brass", "Smoked Sage")
+        • One-line styling note in italic serif
+        • Hex chip
+  [3] CAROUSEL PREVIEW: Below swatches, an 1080x1080 Instagram preview panel
+        • Cover slide + 3 color story slides + CTA slide = 5 slides
+        • Left/right arrow nav + dot indicators
+        • Keyboard ← → navigation
+        • Touch/swipe support
+  [4] CAPTION COPY: Accordion below carousel with the full Instagram caption
+        text and hashtag block, with one-click copy-to-clipboard
+        (in-page toast notification — no alert())
+  [5] PALETTE CARD: "Export Palette Card" button triggers an overlay/modal
+        showing a beautifully typeset palette summary card, designed for
+        screenshot/print, with all 5 swatches + brand lockup
+
+VISUAL SYSTEM
+-------------
+Palette tokens:
+  --bg:        #0d0b09   (warm near-black — primary surface)
+  --bg-raised: #131008   (slightly lifted panels)
+  --ink:       #1c1a17   (darkest text, on light surfaces)
+  --ink-inv:   #e8e3da   (primary text on dark surfaces)
+  --ink-dim:   #9a9184   (secondary/muted text)
+  --accent:    #c9a961   (DW brass — CTAs, active states, swatch borders)
+  --accent-dim:#7a6538   (brass hover darken)
+  --line:      rgba(201,169,97,0.18)  (hairline borders)
+  --shadow:    0 4px 32px rgba(0,0,0,0.55)
+  --serif:     'Cormorant Garamond','Playfair Display','EB Garamond',Georgia,serif
+  --sans:      'Optima','Gill Sans','Trebuchet MS',system-ui,sans-serif
+  --ease:      cubic-bezier(0.25, 0.46, 0.45, 0.94)
+  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1)
+
+Typography scale:
+  wordmark: 44px, letter-spacing .18em, serif, uppercase
+  h2 section: 28px serif
+  color name: 22px serif italic
+  label: 11px sans uppercase tracked .14em
+  body copy: 16px sans, lh 1.7
+
+Spacing scale (4-base): 4 8 12 16 24 32 48 64 80 120px
+
+Motion:
+  swatch reveal: opacity 0→1, translateY 20px→0, staggered 80ms
+  carousel slide: translateX, 360ms --ease
+  toast: translateY -20px→0, opacity, 2.8s total
+  overlay: opacity + scale(0.96)→scale(1), 280ms
+
+SEASONAL COLOR STORIES (content model)
+---------------------------------------
+Each season = { name, tagline, palette[5], caption, hashtags }
+
+Each swatch = {
+  name: string,         // designer color name
+  hex: string,          // actual CSS color
+  texture: 'grasscloth' | 'damask' | 'linen' | 'silk' | 'paperweave' | 'raffia',
+  note: string          // one-line styling note
+}
+
+AUTUMN palette (default):
+  1. Burnished Terracotta  #b5614a  damask        Anchor a feature wall in a study
+  2. Smoked Sage           #6e7d5e  linen         Balance warmth with botanical calm
+  3. Tarnished Brass       #a87f3c  silk-stripe   Layer with aged leather and walnut
+  4. Oatmeal Grasscloth    #c9b89a  grasscloth    Ground the palette in natural texture
+  5. Ink Charcoal          #2e2a25  raffia-weave  A deeply moody base for art-wall drama
+
+WINTER palette:
+  1. Alabaster             #e8e0d2  damask        Float porcelain objets against serene quiet
+  2. Ink Navy              #1a2235  silk-stripe   A commanding depth for a library or salon
+  3. Celadon Mist          #afc5be  grasscloth    Ethereal, spa-like, and endlessly calming
+  4. Champagne             #d4b87a  linen         Warm up cool marble with a gilded haze
+  5. Carbon Matte          #2c2c2e  raffia-weave  Understated power — a backdrop for metallics
+
+SPRING palette:
+  1. Blush Oyster          #e8d5c8  linen         The softest possible opening note
+  2. Garden Sage           #8aa87d  grasscloth    Fresh, grounded, botanically charged
+  3. Pale Aubergine        #8c7a8e  damask        A whisper of opulence in a dressing room
+  4. Greige Flannel        #b0a696  raffia-weave  The unsung hero of every layered scheme
+  5. Leaf Vein Green       #4a5e42  silk-stripe   Close the palette with forest authority
+
+SUMMER palette:
+  1. Raw Linen             #d6c9af  linen         Effortless coastal ease — walls that breathe
+  2. Sea Glass             #7fbdae  grasscloth    Pulls in ocean light without pastiche
+  3. Driftwood             #9c8870  raffia-weave  Warm natural grain for relaxed entertaining
+  4. Antique White         #ede8df  damask        An airy foil that lets other pieces sing
+  5. Deep Indigo           #2d3a5a  silk-stripe   A night-sky accent of true sophistication
+
+CAROUSEL SLIDES
+---------------
+Slide 0 (Cover):  Season name large, tagline, brand wordmark, year
+Slide 1:          Colors 1 + 2 side-by-side, names + notes
+Slide 2:          Colors 3 + 4 side-by-side, names + notes
+Slide 3:          Color 5 full-bleed feature, name + styling note
+Slide 4 (CTA):   "Explore the full collection" + handle @designerwallcoverings
+
+CAPTION TEMPLATE
+----------------
+[Season] is the story we've been composing all year. Five wallcovering hues —
+each hand-selected for texture, depth, and the way they hold light — now live
+on our site. Which speaks to your current project?
+
+→ Link in bio to explore the full [Season] Collection.
+
+[hashtag block]
+
+================================================================================
+-->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Seasonal Color Story Studio — Designer Wallcoverings</title>
+<!-- No external network dependencies: all imagery/textures are CSS/inline SVG -->
+<!-- Cormorant Garamond loads from Google Fonts when online (non-blocking); degrades beautifully to Palatino/Georgia offline -->
+<style>
+/* ============================================================
+   DESIGN TOKENS
+   ============================================================ */
+:root {
+  --bg:          #0d0b09;
+  --bg-raised:   #131008;
+  --bg-panel:    #1a1710;
+  --bg-light:    #f4f1ea;
+  --ink:         #1c1a17;
+  --ink-inv:     #e8e3da;
+  --ink-dim:     #9a9184;
+  --ink-faint:   #4a4540;
+  --accent:      #c9a961;
+  --accent-dim:  #7a6538;
+  --accent-glow: rgba(201,169,97,0.12);
+  --line:        rgba(201,169,97,0.18);
+  --line-strong: rgba(201,169,97,0.35);
+  --shadow:      0 4px 32px rgba(0,0,0,0.55);
+  --shadow-lg:   0 16px 64px rgba(0,0,0,0.7);
+  --serif:       'Cormorant Garamond','Playfair Display','EB Garamond',Georgia,serif;
+  --sans:        'Optima','Gill Sans MT','Trebuchet MS',system-ui,sans-serif;
+  --ease:        cubic-bezier(0.25, 0.46, 0.45, 0.94);
+  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
+  --radius:      2px;
+  --radius-lg:   6px;
+}
+
+/* ============================================================
+   HERO BAND
+   ============================================================ */
+.hero-band {
+  position: relative;
+  padding: 80px 48px 0;
+  overflow: hidden;
+}
+
+.hero-band::before {
+  /* Subtle radial glow from accent */
+  content: '';
+  position: absolute;
+  top: -40px;
+  right: -80px;
+  width: 500px;
+  height: 500px;
+  background: radial-gradient(circle, rgba(201,169,97,0.06) 0%, transparent 70%);
+  pointer-events: none;
+}
+
+.hero-inner {
+  max-width: 680px;
+  position: relative;
+  z-index: 1;
+}
+
+.hero-eyebrow {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .24em;
+  text-transform: uppercase;
+  color: var(--accent);
+  margin-bottom: 20px;
+}
+
+.hero-headline {
+  font-family: var(--serif);
+  font-size: clamp(44px, 7vw, 80px);
+  font-weight: 300;
+  letter-spacing: .02em;
+  color: var(--ink-inv);
+  line-height: 1.0;
+  margin-bottom: 28px;
+}
+
+.hero-body {
+  font-family: var(--sans);
+  font-size: 15px;
+  color: var(--ink-dim);
+  line-height: 1.75;
+  max-width: 520px;
+  margin-bottom: 48px;
+}
+
+.hero-divider {
+  width: 48px;
+  height: 1px;
+  background: linear-gradient(90deg, var(--accent) 0%, transparent 100%);
+  margin-bottom: 48px;
+}
+
+.hero-accent-bar {
+  position: relative;
+  margin: 0 -48px;
+  height: 4px;
+  margin-bottom: 0;
+}
+
+.hero-color-stripe {
+  display: flex;
+  height: 4px;
+  width: 100%;
+  overflow: hidden;
+}
+
+.hero-stripe-seg {
+  flex: 1;
+  transition: background 600ms var(--ease);
+}
+
+@media (max-width: 768px) {
+  .hero-band { padding: 48px 24px 0; }
+  .hero-band::before { display: none; }
+  .hero-accent-bar { margin: 0 -24px; }
+}
+
+/* ============================================================
+   RESET + BASE
+   ============================================================ */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html {
+  scroll-behavior: smooth;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+  background: var(--bg);
+  color: var(--ink-inv);
+  font-family: var(--sans);
+  font-size: 16px;
+  line-height: 1.7;
+  min-height: 100vh;
+  overflow-x: hidden;
+}
+
+/* ============================================================
+   GRAIN OVERLAY (luxury texture on body)
+   ============================================================ */
+body::before {
+  content: '';
+  position: fixed;
+  inset: 0;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
+  opacity: 0.4;
+  pointer-events: none;
+  z-index: 0;
+}
+
+/* ============================================================
+   TYPOGRAPHY UTILITIES
+   ============================================================ */
+.serif   { font-family: var(--serif); }
+.sans    { font-family: var(--sans); }
+.tracked { letter-spacing: .14em; }
+.caps    { text-transform: uppercase; }
+.dim     { color: var(--ink-dim); }
+.accent-c { color: var(--accent); }
+
+/* ============================================================
+   HEADER / WORDMARK
+   ============================================================ */
+header {
+  position: relative;
+  z-index: 10;
+  padding: 32px 48px 24px;
+  border-bottom: 1px solid var(--line);
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 16px;
+  background: var(--bg);
+}
+
+.wordmark {
+  font-family: var(--serif);
+  font-size: clamp(28px, 3.5vw, 44px);
+  font-weight: 300;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--ink-inv);
+  line-height: 1;
+  user-select: none;
+}
+
+.wordmark span.accent-rule {
+  display: block;
+  width: 100%;
+  height: 1px;
+  background: linear-gradient(90deg, var(--accent) 0%, transparent 100%);
+  margin-top: 8px;
+  opacity: 0.6;
+}
+
+.header-tag {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--accent);
+  align-self: flex-end;
+  padding-bottom: 2px;
+}
+
+/* ============================================================
+   MAIN LAYOUT
+   ============================================================ */
+main {
+  position: relative;
+  z-index: 1;
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 48px 120px;
+}
+
+@media (max-width: 768px) {
+  header { padding: 24px 24px 20px; }
+  main   { padding: 0 24px 80px; }
+}
+
+/* ============================================================
+   SECTION HEADERS
+   ============================================================ */
+.section-label {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+.section-label::after {
+  content: '';
+  flex: 1;
+  height: 1px;
+  background: var(--line);
+}
+
+.section-title {
+  font-family: var(--serif);
+  font-size: clamp(24px, 3vw, 36px);
+  font-weight: 300;
+  color: var(--ink-inv);
+  margin-bottom: 8px;
+  line-height: 1.2;
+}
+
+.section-sub {
+  font-size: 14px;
+  color: var(--ink-dim);
+  margin-bottom: 48px;
+  max-width: 480px;
+}
+
+/* ============================================================
+   SEASON SELECTOR
+   ============================================================ */
+.season-section {
+  padding-top: 64px;
+}
+
+.season-tabs {
+  display: flex;
+  gap: 0;
+  border: 1px solid var(--line);
+  border-radius: var(--radius);
+  overflow: hidden;
+  width: fit-content;
+  margin-bottom: 64px;
+}
+
+.season-tab {
+  background: transparent;
+  border: none;
+  border-right: 1px solid var(--line);
+  padding: 14px 32px;
+  font-family: var(--sans);
+  font-size: 11px;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--ink-dim);
+  cursor: pointer;
+  transition: all 240ms var(--ease);
+  position: relative;
+  overflow: hidden;
+}
+.season-tab:last-child { border-right: none; }
+
+.season-tab::after {
+  content: '';
+  position: absolute;
+  bottom: 0; left: 0; right: 0;
+  height: 2px;
+  background: var(--accent);
+  transform: scaleX(0);
+  transition: transform 240ms var(--ease);
+}
+
+.season-tab:hover { color: var(--ink-inv); background: var(--accent-glow); }
+.season-tab.active {
+  color: var(--accent);
+  background: var(--accent-glow);
+}
+.season-tab.active::after { transform: scaleX(1); }
+
+/* ============================================================
+   COLOR STORY — PALETTE DISPLAY
+   ============================================================ */
+.palette-section { margin-bottom: 80px; }
+
+.palette-header {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 24px;
+  margin-bottom: 40px;
+}
+
+.season-tagline {
+  font-family: var(--serif);
+  font-size: clamp(16px, 2vw, 20px);
+  font-style: italic;
+  color: var(--ink-dim);
+  max-width: 420px;
+  line-height: 1.5;
+}
+
+.swatches-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 2px;
+  border: 1px solid var(--line);
+  border-radius: var(--radius-lg);
+  overflow: hidden;
+}
+
+@media (max-width: 900px) {
+  .swatches-grid { grid-template-columns: repeat(3, 1fr); }
+}
+@media (max-width: 600px) {
+  .swatches-grid { grid-template-columns: 1fr 1fr; }
+}
+
+.swatch-card {
+  position: relative;
+  cursor: pointer;
+  transition: transform 320ms var(--ease);
+  opacity: 0;
+  transform: translateY(20px);
+}
+.swatch-card.revealed {
+  opacity: 1;
+  transform: translateY(0);
+  transition: opacity 400ms var(--ease), transform 400ms var(--ease);
+}
+.swatch-card:hover { z-index: 2; transform: scale(1.02); }
+.swatch-card.active-swatch { z-index: 3; transform: scale(1.03); }
+.swatch-card.active-swatch .swatch-face::after {
+  content: '';
+  position: absolute;
+  inset: 0;
+  border: 2px solid var(--accent);
+  pointer-events: none;
+}
+
+.swatch-face {
+  width: 100%;
+  aspect-ratio: 1 / 1.4;
+  position: relative;
+  overflow: hidden;
+}
+
+.swatch-info {
+  background: var(--bg-raised);
+  padding: 16px;
+  border-top: 1px solid var(--line);
+}
+
+.swatch-name {
+  font-family: var(--serif);
+  font-size: 17px;
+  font-style: italic;
+  color: var(--ink-inv);
+  display: block;
+  margin-bottom: 4px;
+  line-height: 1.2;
+}
+
+.swatch-note {
+  font-family: var(--sans);
+  font-size: 11px;
+  color: var(--ink-dim);
+  line-height: 1.5;
+  margin-bottom: 10px;
+}
+
+.swatch-hex {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .1em;
+  text-transform: uppercase;
+  color: var(--ink-faint);
+}
+.swatch-hex-chip {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  border: 1px solid rgba(255,255,255,0.1);
+}
+
+/* ============================================================
+   TEXTURE RENDERERS (CSS/SVG — no external images)
+   ============================================================ */
+
+/* Grasscloth — horizontal woven lines with slight variation */
+.texture-grasscloth {
+  background-color: var(--swatch-color);
+  background-image:
+    repeating-linear-gradient(
+      0deg,
+      transparent,
+      transparent 3px,
+      rgba(0,0,0,0.07) 3px,
+      rgba(0,0,0,0.07) 4px
+    ),
+    repeating-linear-gradient(
+      90deg,
+      transparent,
+      transparent 7px,
+      rgba(255,255,255,0.05) 7px,
+      rgba(255,255,255,0.05) 8px
+    );
+}
+
+/* Damask — SVG medallion repeated */
+.texture-damask {
+  background-color: var(--swatch-color);
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='80'%3E%3Cg fill='none' stroke='rgba(0,0,0,0.1)' stroke-width='0.8'%3E%3Cellipse cx='30' cy='40' rx='10' ry='16'/%3E%3Cellipse cx='30' cy='40' rx='16' ry='10'/%3E%3Ccircle cx='30' cy='40' r='5'/%3E%3Cpath d='M30 24 L30 20 M30 56 L30 60 M14 40 L10 40 M46 40 L50 40'/%3E%3Cpath d='M18 28 L14 24 M42 52 L46 56 M42 28 L46 24 M18 52 L14 56'/%3E%3C/g%3E%3C/svg%3E");
+  background-size: 60px 80px;
+}
+
+/* Linen — fine diagonal weave */
+.texture-linen {
+  background-color: var(--swatch-color);
+  background-image:
+    repeating-linear-gradient(
+      45deg,
+      rgba(0,0,0,0.05) 0px,
+      rgba(0,0,0,0.05) 1px,
+      transparent 1px,
+      transparent 5px
+    ),
+    repeating-linear-gradient(
+      -45deg,
+      rgba(0,0,0,0.04) 0px,
+      rgba(0,0,0,0.04) 1px,
+      transparent 1px,
+      transparent 5px
+    );
+}
+
+/* Silk stripe — refined vertical satin bands */
+.texture-silk {
+  background-color: var(--swatch-color);
+  background-image:
+    repeating-linear-gradient(
+      90deg,
+      rgba(255,255,255,0.12) 0px,
+      rgba(255,255,255,0.12) 1px,
+      transparent 1px,
+      transparent 8px,
+      rgba(255,255,255,0.06) 8px,
+      rgba(255,255,255,0.06) 9px,
+      transparent 9px,
+      transparent 16px
+    );
+}
+
+/* Paperweave — tight horizontal + vertical grid (washi-like) */
+.texture-paperweave {
+  background-color: var(--swatch-color);
+  background-image:
+    repeating-linear-gradient(
+      0deg,
+      rgba(0,0,0,0.06) 0px,
+      rgba(0,0,0,0.06) 1px,
+      transparent 1px,
+      transparent 6px
+    ),
+    repeating-linear-gradient(
+      90deg,
+      rgba(0,0,0,0.06) 0px,
+      rgba(0,0,0,0.06) 1px,
+      transparent 1px,
+      transparent 6px
+    );
+}
+
+/* Raffia weave — chunky diagonal basket pattern */
+.texture-raffia {
+  background-color: var(--swatch-color);
+  background-image:
+    repeating-linear-gradient(
+      45deg,
+      rgba(255,255,255,0.06) 0px,
+      rgba(255,255,255,0.06) 4px,
+      transparent 4px,
+      transparent 10px
+    ),
+    repeating-linear-gradient(
+      -45deg,
+      rgba(0,0,0,0.08) 0px,
+      rgba(0,0,0,0.08) 4px,
+      transparent 4px,
+      transparent 10px
+    );
+}
+
+/* ============================================================
+   INSTAGRAM CAROUSEL PREVIEW
+   ============================================================ */
+.carousel-section {
+  margin-bottom: 80px;
+}
+
+.carousel-outer {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 24px;
+}
+
+/* Phone frame mock */
+.phone-frame {
+  width: 100%;
+  max-width: 420px;
+  background: #1a1a1a;
+  border-radius: 36px;
+  padding: 12px;
+  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255,255,255,0.05);
+  position: relative;
+}
+
+.phone-notch {
+  width: 100px;
+  height: 22px;
+  background: #1a1a1a;
+  border-radius: 0 0 14px 14px;
+  margin: 0 auto 8px;
+  position: relative;
+  z-index: 2;
+}
+
+.phone-screen {
+  border-radius: 26px;
+  overflow: hidden;
+  background: #000;
+}
+
+/* IG chrome top bar */
+.ig-chrome-top {
+  background: #fff;
+  padding: 10px 14px 6px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  border-bottom: 1px solid #e0e0e0;
+}
+.ig-avatar {
+  width: 28px;
+  height: 28px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, var(--accent) 0%, #8b4a2a 100%);
+  flex-shrink: 0;
+}
+.ig-handle {
+  font-family: -apple-system, sans-serif;
+  font-size: 12px;
+  font-weight: 600;
+  color: #1a1a1a;
+  flex: 1;
+}
+.ig-more {
+  color: #1a1a1a;
+  font-size: 16px;
+  line-height: 1;
+}
+
+/* Carousel viewport */
+.carousel-viewport {
+  position: relative;
+  width: 100%;
+  aspect-ratio: 1 / 1;
+  overflow: hidden;
+}
+
+.carousel-track {
+  display: flex;
+  height: 100%;
+  transition: transform 360ms var(--ease);
+  will-change: transform;
+}
+
+.carousel-slide {
+  flex: 0 0 100%;
+  height: 100%;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+/* Slide: Cover */
+.slide-cover {
+  /* background set by JS */
+  padding: 32px;
+}
+
+.slide-cover-inner {
+  text-align: center;
+  position: relative;
+  z-index: 2;
+}
+
+.slide-cover .cover-year {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .28em;
+  text-transform: uppercase;
+  color: rgba(255,255,255,0.5);
+  margin-bottom: 20px;
+}
+
+.slide-cover .cover-season {
+  font-family: var(--serif);
+  font-size: clamp(36px, 10vw, 52px);
+  font-weight: 300;
+  letter-spacing: .08em;
+  text-transform: uppercase;
+  color: #fff;
+  line-height: 1;
+  margin-bottom: 8px;
+}
+
+.slide-cover .cover-sub {
+  font-family: var(--serif);
+  font-size: clamp(14px, 3vw, 18px);
+  font-style: italic;
+  color: rgba(255,255,255,0.75);
+  margin-bottom: 28px;
+  line-height: 1.4;
+}
+
+.slide-cover .cover-rule {
+  width: 40px;
+  height: 1px;
+  background: var(--accent);
+  margin: 0 auto 20px;
+}
+
+.slide-cover .cover-brand {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+}
+
+/* Cover texture overlay */
+.slide-cover .cover-texture {
+  position: absolute;
+  inset: 0;
+  pointer-events: none;
+}
+
+/* Slide: 2-color panel */
+.slide-duo {
+  padding: 0;
+  flex-direction: row;
+  align-items: stretch;
+}
+
+.slide-duo-half {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-end;
+  padding: 20px 16px;
+  position: relative;
+  overflow: hidden;
+}
+
+.slide-duo-half .duo-color-name {
+  font-family: var(--serif);
+  font-size: clamp(14px, 3.5vw, 18px);
+  font-style: italic;
+  font-weight: 400;
+  color: #fff;
+  text-shadow: 0 1px 8px rgba(0,0,0,0.5);
+  margin-bottom: 4px;
+  position: relative;
+  z-index: 2;
+}
+
+.slide-duo-half .duo-color-note {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .1em;
+  text-transform: uppercase;
+  color: rgba(255,255,255,0.65);
+  position: relative;
+  z-index: 2;
+}
+
+.slide-duo-half .duo-divider {
+  position: absolute;
+  top: 0; right: 0;
+  width: 1px;
+  height: 100%;
+  background: rgba(255,255,255,0.15);
+}
+
+/* Slide: Feature (full bleed single color) */
+.slide-feature {
+  padding: 32px;
+  align-items: flex-start;
+  justify-content: flex-end;
+}
+
+.slide-feature .feature-content {
+  position: relative;
+  z-index: 2;
+}
+
+.slide-feature .feature-num {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+  margin-bottom: 8px;
+}
+
+.slide-feature .feature-name {
+  font-family: var(--serif);
+  font-size: clamp(24px, 6vw, 36px);
+  font-style: italic;
+  color: #fff;
+  text-shadow: 0 2px 16px rgba(0,0,0,0.4);
+  line-height: 1.1;
+  margin-bottom: 8px;
+}
+
+.slide-feature .feature-rule {
+  width: 32px;
+  height: 1px;
+  background: var(--accent);
+  margin-bottom: 10px;
+}
+
+.slide-feature .feature-note {
+  font-family: var(--sans);
+  font-size: 11px;
+  color: rgba(255,255,255,0.75);
+  max-width: 220px;
+  line-height: 1.5;
+}
+
+/* Slide: CTA */
+.slide-cta {
+  background: var(--bg) !important;
+  padding: 32px;
+  text-align: center;
+}
+
+.slide-cta .cta-rule-top {
+  width: 1px;
+  height: 40px;
+  background: var(--line-strong);
+  margin: 0 auto 24px;
+}
+
+.slide-cta .cta-eyebrow {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+  margin-bottom: 16px;
+}
+
+.slide-cta .cta-headline {
+  font-family: var(--serif);
+  font-size: clamp(22px, 5vw, 30px);
+  font-weight: 300;
+  color: var(--ink-inv);
+  line-height: 1.25;
+  margin-bottom: 20px;
+}
+
+.slide-cta .cta-handle {
+  font-family: var(--sans);
+  font-size: 12px;
+  letter-spacing: .08em;
+  color: var(--accent);
+  margin-bottom: 24px;
+}
+
+.slide-cta .cta-pill {
+  display: inline-block;
+  border: 1px solid var(--line-strong);
+  padding: 10px 24px;
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .2em;
+  text-transform: uppercase;
+  color: var(--ink-inv);
+  border-radius: var(--radius);
+}
+
+/* IG chrome bottom bar */
+.ig-chrome-bottom {
+  background: #fff;
+  padding: 8px 14px 10px;
+  display: flex;
+  align-items: center;
+  gap: 14px;
+}
+.ig-icon {
+  width: 22px;
+  height: 22px;
+  color: #1a1a1a;
+}
+.ig-icon svg { width: 100%; height: 100%; }
+.ig-save-icon { margin-left: auto; }
+
+/* Carousel dots */
+.carousel-dots {
+  display: flex;
+  gap: 5px;
+  justify-content: center;
+}
+.c-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: var(--ink-faint);
+  transition: all 240ms var(--ease);
+  cursor: pointer;
+}
+.c-dot.active {
+  background: var(--accent);
+  width: 16px;
+  border-radius: 3px;
+}
+
+/* Carousel nav arrows */
+.carousel-nav {
+  display: flex;
+  gap: 12px;
+  justify-content: center;
+}
+.carousel-arrow {
+  background: transparent;
+  border: 1px solid var(--line);
+  color: var(--ink-dim);
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  transition: all 240ms var(--ease);
+  font-family: var(--sans);
+}
+.carousel-arrow:hover:not(:disabled) {
+  border-color: var(--accent);
+  color: var(--accent);
+  background: var(--accent-glow);
+}
+.carousel-arrow:disabled {
+  opacity: 0.25;
+  cursor: not-allowed;
+}
+
+.slide-counter {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .12em;
+  color: var(--ink-dim);
+  text-align: center;
+  margin-top: 4px;
+}
+
+/* ============================================================
+   CAPTION SECTION
+   ============================================================ */
+.caption-section {
+  margin-bottom: 80px;
+}
+
+.caption-box {
+  border: 1px solid var(--line);
+  border-radius: var(--radius-lg);
+  overflow: hidden;
+}
+
+.caption-header {
+  padding: 20px 24px;
+  background: var(--bg-raised);
+  border-bottom: 1px solid var(--line);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.caption-title {
+  font-family: var(--sans);
+  font-size: 11px;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--ink-dim);
+}
+
+.copy-btn {
+  background: transparent;
+  border: 1px solid var(--line-strong);
+  color: var(--accent);
+  padding: 8px 20px;
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .16em;
+  text-transform: uppercase;
+  cursor: pointer;
+  border-radius: var(--radius);
+  transition: all 240ms var(--ease);
+}
+.copy-btn:hover {
+  background: var(--accent-glow);
+  border-color: var(--accent);
+}
+.copy-btn.copied {
+  color: #6bbd6b;
+  border-color: #6bbd6b;
+}
+
+.caption-body {
+  padding: 28px 28px 20px;
+  background: var(--bg-panel);
+}
+
+.caption-text {
+  font-family: var(--serif);
+  font-size: 17px;
+  font-weight: 300;
+  color: var(--ink-inv);
+  line-height: 1.8;
+  white-space: pre-wrap;
+  margin-bottom: 20px;
+}
+
+.hashtag-block {
+  font-family: var(--sans);
+  font-size: 12px;
+  color: var(--accent);
+  line-height: 2;
+  opacity: 0.8;
+}
+
+/* ============================================================
+   EXPORT / PALETTE CARD SECTION
+   ============================================================ */
+.export-section {
+  margin-bottom: 80px;
+}
+
+.export-btn {
+  display: inline-flex;
+  align-items: center;
+  gap: 12px;
+  background: var(--accent-glow);
+  border: 1px solid var(--line-strong);
+  color: var(--accent);
+  padding: 16px 36px;
+  font-family: var(--sans);
+  font-size: 11px;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  cursor: pointer;
+  border-radius: var(--radius);
+  transition: all 280ms var(--ease);
+}
+.export-btn:hover {
+  background: rgba(201,169,97,0.2);
+  border-color: var(--accent);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 20px rgba(201,169,97,0.1);
+}
+.export-btn svg { width: 16px; height: 16px; }
+
+/* ============================================================
+   PALETTE CARD MODAL
+   ============================================================ */
+.modal-overlay {
+  position: fixed;
+  inset: 0;
+  background: rgba(0,0,0,0.85);
+  backdrop-filter: blur(4px);
+  z-index: 100;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 24px;
+  opacity: 0;
+  pointer-events: none;
+  transition: opacity 280ms var(--ease);
+}
+.modal-overlay.open {
+  opacity: 1;
+  pointer-events: all;
+}
+
+.modal-inner {
+  transform: scale(0.95);
+  transition: transform 280ms var(--ease-spring);
+}
+.modal-overlay.open .modal-inner {
+  transform: scale(1);
+}
+
+/* The exportable palette card itself */
+.palette-card {
+  background: var(--bg-light);
+  width: min(680px, 100%);
+  padding: 0;
+  box-shadow: var(--shadow-lg);
+  overflow: hidden;
+  border-radius: 2px;
+  position: relative;
+}
+
+.palette-card-header {
+  background: var(--bg);
+  padding: 36px 40px 28px;
+  border-bottom: 1px solid rgba(201,169,97,0.2);
+}
+
+.palette-card-brand {
+  font-family: var(--serif);
+  font-size: 26px;
+  font-weight: 300;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--ink-inv);
+  line-height: 1;
+  margin-bottom: 6px;
+}
+
+.palette-card-rule {
+  width: 40px;
+  height: 1px;
+  background: var(--accent);
+  margin-bottom: 10px;
+}
+
+.palette-card-season-name {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+}
+
+/* Horizontal swatch row on card */
+.palette-card-swatches {
+  display: flex;
+  height: 200px;
+}
+
+.palette-card-swatch {
+  flex: 1;
+  position: relative;
+  overflow: hidden;
+}
+
+.palette-card-swatch-label {
+  position: absolute;
+  bottom: 0; left: 0; right: 0;
+  background: linear-gradient(transparent, rgba(0,0,0,0.6));
+  padding: 20px 10px 10px;
+  text-align: center;
+}
+
+.palette-card-swatch-name {
+  font-family: var(--serif);
+  font-size: 11px;
+  font-style: italic;
+  color: #fff;
+  display: block;
+  line-height: 1.2;
+}
+
+/* Color name strip below swatches */
+.palette-card-names {
+  background: var(--bg-light);
+  display: flex;
+  border-top: 1px solid rgba(0,0,0,0.08);
+}
+
+.palette-card-name-item {
+  flex: 1;
+  padding: 14px 10px;
+  text-align: center;
+  border-right: 1px solid rgba(0,0,0,0.06);
+}
+.palette-card-name-item:last-child { border-right: none; }
+
+.palette-card-hex {
+  display: block;
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .08em;
+  color: #6a6560;
+  margin-bottom: 3px;
+}
+.palette-card-cn {
+  display: block;
+  font-family: var(--serif);
+  font-size: 11px;
+  font-style: italic;
+  color: var(--ink);
+}
+
+/* Notes section on card */
+.palette-card-notes {
+  background: var(--bg-light);
+  padding: 24px 40px 20px;
+  border-top: 1px solid rgba(0,0,0,0.06);
+}
+
+.palette-card-notes-title {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .2em;
+  text-transform: uppercase;
+  color: #888;
+  margin-bottom: 12px;
+}
+
+.palette-card-note-row {
+  display: flex;
+  align-items: baseline;
+  gap: 10px;
+  margin-bottom: 6px;
+}
+
+.palette-card-note-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+  margin-top: 3px;
+}
+
+.palette-card-note-text {
+  font-family: var(--serif);
+  font-size: 13px;
+  font-style: italic;
+  color: #4a4540;
+  line-height: 1.5;
+}
+
+.palette-card-footer {
+  padding: 14px 40px;
+  background: var(--bg-light);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-top: 1px solid rgba(0,0,0,0.08);
+}
+
+.palette-card-footer-brand {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: #888;
+}
+.palette-card-footer-url {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .08em;
+  color: var(--accent-dim);
+}
+
+/* Close modal btn */
+.modal-close {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  background: rgba(0,0,0,0.5);
+  border: 1px solid rgba(255,255,255,0.1);
+  color: rgba(255,255,255,0.7);
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  cursor: pointer;
+  font-size: 18px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 200ms var(--ease);
+  z-index: 10;
+}
+.modal-close:hover {
+  background: rgba(0,0,0,0.8);
+  color: #fff;
+}
+
+/* Screenshot hint */
+.screenshot-hint {
+  text-align: center;
+  margin-top: 16px;
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .12em;
+  text-transform: uppercase;
+  color: rgba(255,255,255,0.35);
+}
+
+/* ============================================================
+   TOAST NOTIFICATION
+   ============================================================ */
+.toast {
+  position: fixed;
+  bottom: 40px;
+  left: 50%;
+  transform: translateX(-50%) translateY(20px);
+  background: var(--bg-raised);
+  border: 1px solid var(--line-strong);
+  color: var(--ink-inv);
+  padding: 14px 28px;
+  border-radius: 40px;
+  font-family: var(--sans);
+  font-size: 12px;
+  letter-spacing: .1em;
+  text-transform: uppercase;
+  box-shadow: var(--shadow);
+  opacity: 0;
+  pointer-events: none;
+  transition: all 300ms var(--ease);
+  z-index: 200;
+  white-space: nowrap;
+}
+.toast.show {
+  opacity: 1;
+  transform: translateX(-50%) translateY(0);
+}
+
+/* ============================================================
+   FOOTER
+   ============================================================ */
+footer {
+  border-top: 1px solid var(--line);
+  padding: 40px 48px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 20px;
+  position: relative;
+  z-index: 1;
+}
+
+.footer-brand {
+  font-family: var(--serif);
+  font-size: 18px;
+  letter-spacing: .12em;
+  text-transform: uppercase;
+  color: var(--ink-inv);
+  font-weight: 300;
+}
+
+.footer-note {
+  font-family: var(--sans);
+  font-size: 10px;
+  letter-spacing: .12em;
+  text-transform: uppercase;
+  color: var(--ink-faint);
+}
+
+/* ============================================================
+   ACTIVE SWATCH DETAIL PANEL
+   ============================================================ */
+.swatch-detail-panel {
+  border: 1px solid var(--line);
+  border-radius: var(--radius-lg);
+  background: var(--bg-raised);
+  overflow: hidden;
+  margin-top: 2px;
+  max-height: 0;
+  opacity: 0;
+  transition: max-height 360ms var(--ease), opacity 300ms var(--ease);
+}
+.swatch-detail-panel.open {
+  max-height: 200px;
+  opacity: 1;
+}
+
+.swatch-detail-inner {
+  display: flex;
+  align-items: center;
+  gap: 32px;
+  padding: 28px 32px;
+}
+
+.swatch-detail-bar {
+  width: 6px;
+  height: 80px;
+  border-radius: 3px;
+  flex-shrink: 0;
+}
+
+.swatch-detail-content {}
+
+.swatch-detail-label {
+  font-family: var(--sans);
+  font-size: 9px;
+  letter-spacing: .22em;
+  text-transform: uppercase;
+  color: var(--accent);
+  margin-bottom: 6px;
+}
+
+.swatch-detail-name {
+  font-family: var(--serif);
+  font-size: 26px;
+  font-style: italic;
+  color: var(--ink-inv);
+  margin-bottom: 6px;
+  line-height: 1.1;
+}
+
+.swatch-detail-note {
+  font-family: var(--sans);
+  font-size: 13px;
+  color: var(--ink-dim);
+  line-height: 1.6;
+}
+
+/* ============================================================
+   UTILITY
+   ============================================================ */
+.sr-only {
+  position: absolute; width: 1px; height: 1px;
+  padding: 0; margin: -1px; overflow: hidden;
+  clip: rect(0,0,0,0); border: 0;
+}
+
+.section-divider {
+  height: 1px;
+  background: linear-gradient(90deg, transparent, var(--line), transparent);
+  margin: 64px 0;
+}
+
+/* Scroll reveal for sections */
+.reveal {
+  opacity: 0;
+  transform: translateY(24px);
+  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
+}
+.reveal.in-view {
+  opacity: 1;
+  transform: translateY(0);
+}
+</style>
+</head>
+<body>
+
+<!-- ============================================================
+     HEADER
+     ============================================================ -->
+<header role="banner">
+  <div>
+    <div class="wordmark" aria-label="Designer Wallcoverings">
+      Designer Wallcoverings
+      <span class="accent-rule" aria-hidden="true"></span>
+    </div>
+  </div>
+  <div class="header-tag" aria-hidden="true">Seasonal Color Story Studio</div>
+</header>
+
+<!-- ============================================================
+     MAIN CONTENT
+     ============================================================ -->
+<main role="main">
+
+  <!-- HERO BAND -->
+  <section class="hero-band" aria-hidden="false">
+    <div class="hero-inner">
+      <p class="hero-eyebrow">A Tool for the DW Creative Team</p>
+      <h1 class="hero-headline">Seasonal Color<br>Story Studio</h1>
+      <p class="hero-body">Generate, preview, and export a curated wallcovering palette — built for your next Instagram campaign or client mood board. Four seasons. Five swatches each. One complete brand story every time.</p>
+      <div class="hero-divider" aria-hidden="true"></div>
+    </div>
+    <div class="hero-accent-bar" aria-hidden="true">
+      <div class="hero-color-stripe" id="hero-color-stripe">
+        <!-- Filled dynamically with current palette colors -->
+      </div>
+    </div>
+  </section>
+
+  <!-- SEASON SELECTOR -->
+  <section class="season-section reveal" aria-labelledby="season-heading">
+    <p class="section-label" aria-hidden="true">01 — Choose Your Story</p>
+    <h2 class="section-title" id="season-heading">Which season speaks to your client?</h2>
+    <p class="section-sub">Select a season to generate a curated five-swatch color story with wallcovering material notes and a ready-to-post Instagram carousel.</p>
+
+    <div class="season-tabs" role="tablist" aria-label="Season selection">
+      <button class="season-tab" role="tab" aria-selected="false" data-season="spring" aria-controls="palette-panel">Spring</button>
+      <button class="season-tab" role="tab" aria-selected="false" data-season="summer" aria-controls="palette-panel">Summer</button>
+      <button class="season-tab active" role="tab" aria-selected="true" data-season="autumn" aria-controls="palette-panel">Autumn</button>
+      <button class="season-tab" role="tab" aria-selected="false" data-season="winter" aria-controls="palette-panel">Winter</button>
+    </div>
+  </section>
+
+  <!-- PALETTE DISPLAY -->
+  <section class="palette-section reveal" id="palette-panel" aria-labelledby="palette-heading">
+    <p class="section-label" aria-hidden="true">02 — Color Story</p>
+
+    <div class="palette-header">
+      <h2 class="section-title" id="palette-heading">
+        <span id="palette-season-name">Autumn</span> Color Story
+      </h2>
+      <p class="season-tagline" id="palette-tagline"></p>
+    </div>
+
+    <div class="swatches-grid" id="swatches-grid" role="list" aria-label="Color palette swatches"></div>
+
+    <!-- Expanded detail panel -->
+    <div class="swatch-detail-panel" id="swatch-detail" aria-live="polite">
+      <div class="swatch-detail-inner">
+        <div class="swatch-detail-bar" id="detail-bar"></div>
+        <div class="swatch-detail-content">
+          <div class="swatch-detail-label" id="detail-texture-label">Texture</div>
+          <div class="swatch-detail-name" id="detail-name"></div>
+          <div class="swatch-detail-note" id="detail-note"></div>
+        </div>
+      </div>
+    </div>
+  </section>
+
+  <div class="section-divider" aria-hidden="true"></div>
+
+  <!-- CAROUSEL PREVIEW -->
+  <section class="carousel-section reveal" aria-labelledby="carousel-heading">
+    <p class="section-label" aria-hidden="true">03 — Instagram Preview</p>
+    <h2 class="section-title" id="carousel-heading">How it looks in the feed</h2>
+    <p class="section-sub">Five slides ready for your next carousel post. Use ← → arrows or swipe to preview each frame.</p>
+
+    <div class="carousel-outer">
+      <!-- Phone frame -->
+      <div class="phone-frame" role="presentation">
+        <div class="phone-notch" aria-hidden="true"></div>
+        <div class="phone-screen">
+          <!-- IG chrome top -->
+          <div class="ig-chrome-top" aria-hidden="true">
+            <div class="ig-avatar"></div>
+            <span class="ig-handle">designerwallcoverings</span>
+            <span class="ig-more">···</span>
+          </div>
+
+          <!-- Carousel -->
+          <div class="carousel-viewport"
+               role="region"
+               aria-label="Instagram carousel preview"
+               aria-roledescription="carousel"
+               id="carousel-viewport">
+            <div class="carousel-track" id="carousel-track"></div>
+          </div>
+
+          <!-- IG chrome bottom -->
+          <div class="ig-chrome-bottom" aria-hidden="true">
+            <span class="ig-icon">
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
+              </svg>
+            </span>
+            <span class="ig-icon">
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
+              </svg>
+            </span>
+            <span class="ig-icon">
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
+              </svg>
+            </span>
+            <span class="ig-icon ig-save-icon">
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>
+              </svg>
+            </span>
+          </div>
+        </div>
+      </div>
+
+      <!-- Controls below phone -->
+      <div class="carousel-dots" id="carousel-dots" role="tablist" aria-label="Carousel slide navigation"></div>
+
+      <div class="carousel-nav" aria-label="Carousel navigation">
+        <button class="carousel-arrow" id="prev-btn" aria-label="Previous slide" disabled>&#8592;</button>
+        <button class="carousel-arrow" id="next-btn" aria-label="Next slide">&#8594;</button>
+      </div>
+
+      <div class="slide-counter" id="slide-counter" aria-live="polite" aria-atomic="true"></div>
+    </div>
+  </section>
+
+  <div class="section-divider" aria-hidden="true"></div>
+
+  <!-- CAPTION COPY -->
+  <section class="caption-section reveal" aria-labelledby="caption-heading">
+    <p class="section-label" aria-hidden="true">04 — Caption + Hashtags</p>
+    <h2 class="section-title" id="caption-heading">Ready-to-post copy</h2>
+    <p class="section-sub">Tap to copy the full caption and hashtag block directly to your clipboard.</p>
+
+    <div class="caption-box">
+      <div class="caption-header">
+        <span class="caption-title">Instagram Caption</span>
+        <button class="copy-btn" id="copy-caption-btn" aria-label="Copy caption to clipboard">
+          Copy Caption
+        </button>
+      </div>
+      <div class="caption-body">
+        <div class="caption-text" id="caption-text"></div>
+        <div class="hashtag-block" id="hashtag-block"></div>
+      </div>
+    </div>
+  </section>
+
+  <div class="section-divider" aria-hidden="true"></div>
+
+  <!-- EXPORT -->
+  <section class="export-section reveal" aria-labelledby="export-heading">
+    <p class="section-label" aria-hidden="true">05 — Export</p>
+    <h2 class="section-title" id="export-heading">Palette Story Card</h2>
+    <p class="section-sub">Preview the full palette card — screenshot it for presentations, mood boards, or client PDFs.</p>
+
+    <button class="export-btn" id="export-btn" aria-haspopup="dialog" aria-expanded="false">
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
+        <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
+        <circle cx="8.5" cy="8.5" r="1.5"/>
+        <polyline points="21 15 16 10 5 21"/>
+      </svg>
+      View Palette Card
+    </button>
+  </section>
+
+</main>
+
+<!-- ============================================================
+     FOOTER
+     ============================================================ -->
+<footer role="contentinfo">
+  <div class="footer-brand">Designer Wallcoverings</div>
+  <div class="footer-note">Color Story Studio · designerwallcoverings.com</div>
+</footer>
+
+<!-- ============================================================
+     MODAL — PALETTE CARD
+     ============================================================ -->
+<div class="modal-overlay" id="palette-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
+  <div class="modal-inner">
+    <button class="modal-close" id="modal-close-btn" aria-label="Close palette card">&times;</button>
+    <div class="palette-card" id="palette-card-content">
+      <!-- Rendered by JS -->
+    </div>
+    <p class="screenshot-hint" aria-hidden="true">Screenshot this card to save or share</p>
+  </div>
+</div>
+
+<!-- ============================================================
+     TOAST
+     ============================================================ -->
+<div class="toast" id="toast" role="status" aria-live="polite" aria-atomic="true"></div>
+
+<!-- ============================================================
+     JAVASCRIPT
+     ============================================================ -->
+<script>
+'use strict';
+
+/* ============================================================
+   DATA MODEL
+   ============================================================ */
+const SEASONS = {
+  spring: {
+    name: 'Spring',
+    tagline: 'Where the garden breathes into the interior. Fresh, grounded, quietly alive.',
+    palette: [
+      {
+        name: 'Blush Oyster',
+        hex: '#e8d5c8',
+        texture: 'linen',
+        note: 'The softest possible opening note — a boudoir wall made of dawn light.'
+      },
+      {
+        name: 'Garden Sage',
+        hex: '#8aa87d',
+        texture: 'grasscloth',
+        note: 'Fresh, grounded, botanically charged without the cliché of botanical print.'
+      },
+      {
+        name: 'Pale Aubergine',
+        hex: '#8c7a8e',
+        texture: 'damask',
+        note: 'A whisper of opulence — best used in a dressing room or reading nook.'
+      },
+      {
+        name: 'Greige Flannel',
+        hex: '#b0a696',
+        texture: 'raffia',
+        note: 'The unsung hero of every layered scheme. Holds everything else in place.'
+      },
+      {
+        name: 'Leaf Vein Green',
+        hex: '#4a5e42',
+        texture: 'silk',
+        note: 'Close the palette with forest authority. Deep and still and permanent.'
+      }
+    ],
+    caption: `Spring is not a season — it is a directive.\n\nThe wallcovering palette we've curated this season speaks in hushed tones: a blush that holds light, a sage that grounds without heaviness, an aubergine that whispers rather than declares.\n\nFive surfaces. One story. A room that feels as if it exhaled.\n\n→ Explore the Spring Collection at the link in bio.`,
+    hashtags: '#DesignerWallcoverings #SpringPalette #WallcoveringDesign #InteriorDesign #ColorStory #LuxuryInteriors #WallpaperDesign #SpringDecor #InteriorStyling #TextureAndColor #GrassclothWall #WallcoveringInspiration #DesignInspiration #SpringInteriors'
+  },
+
+  summer: {
+    name: 'Summer',
+    tagline: 'Salt air and long evenings. Surfaces that breathe and walls that hold the light.',
+    palette: [
+      {
+        name: 'Raw Linen',
+        hex: '#d6c9af',
+        texture: 'linen',
+        note: 'Effortless coastal ease — walls that breathe before you even open the window.'
+      },
+      {
+        name: 'Sea Glass',
+        hex: '#7fbdae',
+        texture: 'grasscloth',
+        note: 'Pulls in ocean light without pastiche. Genuinely hard to tire of.'
+      },
+      {
+        name: 'Driftwood',
+        hex: '#9c8870',
+        texture: 'raffia',
+        note: 'Warm natural grain for relaxed entertaining rooms and summer dining.'
+      },
+      {
+        name: 'Antique White',
+        hex: '#ede8df',
+        texture: 'damask',
+        note: 'An airy foil that lets every other element in the room sing its own note.'
+      },
+      {
+        name: 'Deep Indigo',
+        hex: '#2d3a5a',
+        texture: 'silk',
+        note: 'A night-sky accent of true sophistication. The punctuation mark of the palette.'
+      }
+    ],
+    caption: `Summer asks very little of a wall — only that it hold the light well.\n\nOur Summer palette is built on that premise: linen-textured neutrals that catch afternoon sun, a sea glass that turns any room into a long exhale, and a deep indigo that makes the whole collection feel considered rather than casual.\n\nFive wallcoverings. One summer.\n\n→ Shop the Summer Collection at the link in bio.`,
+    hashtags: '#DesignerWallcoverings #SummerInteriors #CoastalDesign #WallcoveringDesign #ColorStory #LuxuryInteriors #SummerPalette #InteriorDesign #WallpaperInspiration #TexturedWalls #GrassclothWallcovering #InteriorStyling #DesignInspiration #SummerHome'
+  },
+
+  autumn: {
+    name: 'Autumn',
+    tagline: 'The season that asks walls to do more — to hold warmth, to anchor a room against the cooling light.',
+    palette: [
+      {
+        name: 'Burnished Terracotta',
+        hex: '#b5614a',
+        texture: 'damask',
+        note: 'Anchor a feature wall in a study or library. Rich without shouting.'
+      },
+      {
+        name: 'Smoked Sage',
+        hex: '#6e7d5e',
+        texture: 'linen',
+        note: 'Balance warmth with botanical calm. The palette\'s essential breath.'
+      },
+      {
+        name: 'Tarnished Brass',
+        hex: '#a87f3c',
+        texture: 'silk',
+        note: 'Layer with aged leather and walnut — a material conversation worth having.'
+      },
+      {
+        name: 'Oatmeal Grasscloth',
+        hex: '#c9b89a',
+        texture: 'grasscloth',
+        note: 'Ground the palette in natural texture. The surface that makes others look richer.'
+      },
+      {
+        name: 'Ink Charcoal',
+        hex: '#2e2a25',
+        texture: 'raffia',
+        note: 'A deeply moody base for art-wall drama. Dark rooms done properly.'
+      }
+    ],
+    caption: `Autumn is the story we've been composing all year.\n\nFive wallcovering hues — each hand-selected for texture, depth, and the particular way they hold declining light — arrive now. Burnished terracotta. Smoked sage. Tarnished brass. Oatmeal grasscloth. Ink charcoal.\n\nWhich speaks to your current project?\n\n→ Explore the Autumn Color Story at the link in bio.`,
+    hashtags: '#DesignerWallcoverings #AutumnPalette #FallInteriors #WallcoveringDesign #ColorStory #LuxuryInteriors #TerracottaWall #InteriorDesign #TexturedWallcovering #GrassclothWall #WallpaperDesign #AutumnDecor #InteriorStyling #DesignInspiration'
+  },
+
+  winter: {
+    name: 'Winter',
+    tagline: 'Quiet power. The season of contrasts: soft and severe, pale and profound.',
+    palette: [
+      {
+        name: 'Alabaster',
+        hex: '#e8e0d2',
+        texture: 'damask',
+        note: 'Float porcelain objets against serene quiet. The room holds its breath.'
+      },
+      {
+        name: 'Ink Navy',
+        hex: '#1a2235',
+        texture: 'silk',
+        note: 'A commanding depth for a library or salon. Rooms of genuine consequence.'
+      },
+      {
+        name: 'Celadon Mist',
+        hex: '#afc5be',
+        texture: 'grasscloth',
+        note: 'Ethereal, spa-like, endlessly calming. The palette\'s moment of grace.'
+      },
+      {
+        name: 'Champagne',
+        hex: '#d4b87a',
+        texture: 'linen',
+        note: 'Warm up cool marble with a gilded haze — the light it makes is extraordinary.'
+      },
+      {
+        name: 'Carbon Matte',
+        hex: '#2c2c2e',
+        texture: 'raffia',
+        note: 'Understated power. A backdrop for metallics that makes everything else gleam.'
+      }
+    ],
+    caption: `Winter reduces everything to what matters.\n\nThis season's wallcovering palette follows that logic: five surfaces chosen for depth, for quiet, for the way a room settles when the walls are doing their job properly. Alabaster. Navy. Celadon. Champagne. Carbon.\n\nA room designed in winter is a room designed for keeps.\n\n→ Shop the Winter Collection at the link in bio.`,
+    hashtags: '#DesignerWallcoverings #WinterInteriors #WinterPalette #WallcoveringDesign #ColorStory #LuxuryInteriors #NavyWall #InteriorDesign #WinterDecor #TexturedWalls #WallpaperDesign #AlabasterInterior #InteriorStyling #DesignInspiration'
+  }
+};
+
+/* ============================================================
+   STATE
+   ============================================================ */
+let currentSeason = 'autumn';
+let currentSlide  = 0;
+let totalSlides   = 5;
+let activeSwatch  = null;
+let touchStartX   = 0;
+
+/* ============================================================
+   TEXTURE CLASS HELPER
+   ============================================================ */
+function textureClass(t) {
+  return {
+    grasscloth: 'texture-grasscloth',
+    damask:     'texture-damask',
+    linen:      'texture-linen',
+    silk:       'texture-silk',
+    paperweave: 'texture-paperweave',
+    raffia:     'texture-raffia'
+  }[t] || 'texture-linen';
+}
+
+function textureLabel(t) {
+  return {
+    grasscloth: 'Natural Grasscloth',
+    damask:     'Jacquard Damask',
+    linen:      'European Linen',
+    silk:       'Silk Stripe',
+    paperweave: 'Japanese Paperweave',
+    raffia:     'Raffia Weave'
+  }[t] || 'Woven Textile';
+}
+
+/* ============================================================
+   RENDER SWATCHES
+   ============================================================ */
+function renderSwatches(seasonKey) {
+  const season = SEASONS[seasonKey];
+  const grid = document.getElementById('swatches-grid');
+  grid.innerHTML = '';
+  activeSwatch = null;
+  closeDetailPanel();
+
+  season.palette.forEach((swatch, i) => {
+    const card = document.createElement('div');
+    card.className = 'swatch-card';
+    card.setAttribute('role', 'listitem');
+    card.setAttribute('tabindex', '0');
+    card.setAttribute('aria-label', `${swatch.name}: ${swatch.note}`);
+    card.dataset.index = i;
+
+    card.innerHTML = `
+      <div class="swatch-face ${textureClass(swatch.texture)}" style="--swatch-color: ${swatch.hex};" aria-hidden="true">
+      </div>
+      <div class="swatch-info">
+        <span class="swatch-name">${swatch.name}</span>
+        <p class="swatch-note">${swatch.note}</p>
+        <div class="swatch-hex">
+          <div class="swatch-hex-chip" style="background:${swatch.hex};" aria-hidden="true"></div>
+          ${swatch.hex}
+        </div>
+      </div>
+    `;
+
+    card.addEventListener('click', () => toggleSwatchDetail(i, swatch, card));
+    card.addEventListener('keydown', (e) => {
+      if (e.key === 'Enter' || e.key === ' ') {
+        e.preventDefault();
+        toggleSwatchDetail(i, swatch, card);
+      }
+    });
+
+    grid.appendChild(card);
+
+    // Staggered reveal
+    setTimeout(() => {
+      card.classList.add('revealed');
+    }, 60 + i * 90);
+  });
+}
+
+/* ============================================================
+   SWATCH DETAIL PANEL
+   ============================================================ */
+function toggleSwatchDetail(idx, swatch, card) {
+  const panel = document.getElementById('swatch-detail');
+  const allCards = document.querySelectorAll('.swatch-card');
+
+  if (activeSwatch === idx) {
+    // Close
+    activeSwatch = null;
+    card.classList.remove('active-swatch');
+    closeDetailPanel();
+    return;
+  }
+
+  // Remove active from all
+  allCards.forEach(c => c.classList.remove('active-swatch'));
+  card.classList.add('active-swatch');
+  activeSwatch = idx;
+
+  document.getElementById('detail-bar').style.background = swatch.hex;
+  document.getElementById('detail-texture-label').textContent = textureLabel(swatch.texture);
+  document.getElementById('detail-name').textContent = swatch.name;
+  document.getElementById('detail-note').textContent = swatch.note;
+
+  panel.classList.add('open');
+}
+
+function closeDetailPanel() {
+  document.getElementById('swatch-detail').classList.remove('open');
+}
+
+/* ============================================================
+   CAROUSEL RENDERING
+   ============================================================ */
+function renderCarousel(seasonKey) {
+  const season = SEASONS[seasonKey];
+  const track  = document.getElementById('carousel-track');
+  const dots   = document.getElementById('carousel-dots');
+  track.innerHTML = '';
+  dots.innerHTML  = '';
+  currentSlide = 0;
+  totalSlides  = 5;
+
+  const p = season.palette;
+
+  /* --- Slide 0: Cover --- */
+  const coverSlide = document.createElement('div');
+  coverSlide.className = 'carousel-slide slide-cover';
+  // Derive cover gradient from palette colors 1 and 3
+  coverSlide.style.background = `linear-gradient(135deg, ${p[0].hex}dd 0%, ${p[2].hex}bb 50%, ${p[4].hex}ee 100%)`;
+  coverSlide.setAttribute('aria-label', `Cover slide: ${season.name} Color Story`);
+  coverSlide.innerHTML = `
+    <div class="cover-texture" aria-hidden="true">
+      ${buildCoverTextureSVG(p)}
+    </div>
+    <div class="slide-cover-inner">
+      <div class="cover-year">2026 Collection</div>
+      <div class="cover-season">${season.name}</div>
+      <div class="cover-sub">${season.tagline.split('.')[0]}.</div>
+      <div class="cover-rule"></div>
+      <div class="cover-brand">Designer Wallcoverings</div>
+    </div>
+  `;
+  track.appendChild(coverSlide);
+
+  /* --- Slide 1: Colors 1 + 2 --- */
+  const duo1 = buildDuoSlide(p[0], p[1], '01', '02');
+  duo1.setAttribute('aria-label', `Slide 2: ${p[0].name} and ${p[1].name}`);
+  track.appendChild(duo1);
+
+  /* --- Slide 2: Colors 3 + 4 --- */
+  const duo2 = buildDuoSlide(p[2], p[3], '03', '04');
+  duo2.setAttribute('aria-label', `Slide 3: ${p[2].name} and ${p[3].name}`);
+  track.appendChild(duo2);
+
+  /* --- Slide 3: Color 5 feature --- */
+  const featureSlide = document.createElement('div');
+  featureSlide.className = 'carousel-slide slide-feature';
+  featureSlide.style.background = p[4].hex;
+  // Apply texture to feature slide
+  applyTextureStyle(featureSlide, p[4].texture);
+  featureSlide.setAttribute('aria-label', `Slide 4: Feature — ${p[4].name}`);
+  // Determine text color based on brightness
+  const textColor = isLight(p[4].hex) ? 'rgba(0,0,0,0.85)' : '#fff';
+  featureSlide.innerHTML = `
+    <div class="feature-content">
+      <div class="feature-num" style="color:rgba(201,169,97,0.9)">No. 05 — The Signature Hue</div>
+      <div class="feature-name" style="color:${textColor}">${p[4].name}</div>
+      <div class="feature-rule"></div>
+      <div class="feature-note" style="color:${textColor === '#fff' ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.65)'}">${p[4].note}</div>
+    </div>
+  `;
+  track.appendChild(featureSlide);
+
+  /* --- Slide 4: CTA --- */
+  const ctaSlide = document.createElement('div');
+  ctaSlide.className = 'carousel-slide slide-cta';
+  ctaSlide.setAttribute('aria-label', 'Slide 5: Call to action — explore the full collection');
+  ctaSlide.innerHTML = `
+    <div class="cta-rule-top"></div>
+    <div class="cta-eyebrow">${season.name} 2026</div>
+    <div class="cta-headline">Explore the full<br>${season.name} Collection</div>
+    <div class="cta-handle">@designerwallcoverings</div>
+    <div class="cta-pill">Link in bio</div>
+  `;
+  track.appendChild(ctaSlide);
+
+  /* --- Dots --- */
+  const slideLabels = ['Cover', 'Colors 1–2', 'Colors 3–4', 'Feature Color', 'CTA'];
+  for (let i = 0; i < totalSlides; i++) {
+    const dot = document.createElement('button');
+    dot.className = 'c-dot' + (i === 0 ? ' active' : '');
+    dot.setAttribute('role', 'tab');
+    dot.setAttribute('aria-label', `Go to slide: ${slideLabels[i]}`);
+    dot.setAttribute('aria-selected', i === 0 ? 'true' : 'false');
+    dot.dataset.slide = i;
+    dot.addEventListener('click', () => goToSlide(i));
+    dots.appendChild(dot);
+  }
+
+  updateCarouselUI();
+
+  /* Touch / swipe */
+  const viewport = document.getElementById('carousel-viewport');
+  viewport.addEventListener('touchstart', e => { touchStartX = e.touches[0].clientX; }, { passive: true });
+  viewport.addEventListener('touchend', e => {
+    const dx = e.changedTouches[0].clientX - touchStartX;
+    if (Math.abs(dx) > 40) dx < 0 ? nextSlide() : prevSlide();
+  }, { passive: true });
+}
+
+function buildCoverTextureSVG(palette) {
+  // Subtle woven lines across cover using SVG
+  return `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" style="position:absolute;inset:0;opacity:0.15">
+    <defs>
+      <pattern id="coverPat" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
+        <line x1="0" y1="0" x2="40" y2="40" stroke="rgba(255,255,255,0.4)" stroke-width="0.5"/>
+        <line x1="40" y1="0" x2="0" y2="40" stroke="rgba(255,255,255,0.2)" stroke-width="0.5"/>
+      </pattern>
+    </defs>
+    <rect width="100%" height="100%" fill="url(#coverPat)"/>
+  </svg>`;
+}
+
+function buildDuoSlide(sw1, sw2, num1, num2) {
+  const slide = document.createElement('div');
+  slide.className = 'carousel-slide slide-duo';
+
+  const half1 = document.createElement('div');
+  half1.className = `slide-duo-half ${textureClass(sw1.texture)}`;
+  half1.style.setProperty('--swatch-color', sw1.hex);
+  const tc1 = isLight(sw1.hex) ? 'rgba(0,0,0,0.85)' : '#fff';
+  half1.innerHTML = `
+    <div class="duo-color-name" style="color:${tc1}">${sw1.name}</div>
+    <div class="duo-color-note" style="color:${tc1 === '#fff' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.55)'}">${textureLabel(sw1.texture)}</div>
+    <div class="duo-divider"></div>
+  `;
+
+  const half2 = document.createElement('div');
+  half2.className = `slide-duo-half ${textureClass(sw2.texture)}`;
+  half2.style.setProperty('--swatch-color', sw2.hex);
+  const tc2 = isLight(sw2.hex) ? 'rgba(0,0,0,0.85)' : '#fff';
+  half2.innerHTML = `
+    <div class="duo-color-name" style="color:${tc2}">${sw2.name}</div>
+    <div class="duo-color-note" style="color:${tc2 === '#fff' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.55)'}">${textureLabel(sw2.texture)}</div>
+  `;
+
+  slide.appendChild(half1);
+  slide.appendChild(half2);
+  return slide;
+}
+
+function applyTextureStyle(el, texture) {
+  el.classList.add(textureClass(texture));
+  el.style.setProperty('--swatch-color', '');
+  // The base bg color is already set on the element
+}
+
+function isLight(hex) {
+  const r = parseInt(hex.slice(1,3),16);
+  const g = parseInt(hex.slice(3,5),16);
+  const b = parseInt(hex.slice(5,7),16);
+  return (r * 0.299 + g * 0.587 + b * 0.114) > 158;
+}
+
+/* ============================================================
+   CAROUSEL NAVIGATION
+   ============================================================ */
+function goToSlide(idx) {
+  currentSlide = Math.max(0, Math.min(idx, totalSlides - 1));
+  updateCarouselUI();
+}
+
+function prevSlide() { if (currentSlide > 0) goToSlide(currentSlide - 1); }
+function nextSlide() { if (currentSlide < totalSlides - 1) goToSlide(currentSlide + 1); }
+
+function updateCarouselUI() {
+  const track = document.getElementById('carousel-track');
+  track.style.transform = `translateX(-${currentSlide * 100}%)`;
+
+  const dots = document.querySelectorAll('.c-dot');
+  dots.forEach((d, i) => {
+    d.classList.toggle('active', i === currentSlide);
+    d.setAttribute('aria-selected', i === currentSlide ? 'true' : 'false');
+  });
+
+  document.getElementById('prev-btn').disabled = currentSlide === 0;
+  document.getElementById('next-btn').disabled = currentSlide === totalSlides - 1;
+
+  const labels = ['Cover', 'Colors 1 & 2', 'Colors 3 & 4', 'Signature Color', 'Call to Action'];
+  document.getElementById('slide-counter').textContent = `${labels[currentSlide]} · ${currentSlide + 1} / ${totalSlides}`;
+}
+
+/* ============================================================
+   CAPTION RENDER
+   ============================================================ */
+function renderCaption(seasonKey) {
+  const season = SEASONS[seasonKey];
+  document.getElementById('caption-text').textContent  = season.caption;
+  document.getElementById('hashtag-block').textContent = season.hashtags;
+}
+
+/* ============================================================
+   PALETTE CARD MODAL
+   ============================================================ */
+function renderPaletteCard(seasonKey) {
+  const season = SEASONS[seasonKey];
+  const p      = season.palette;
+  const card   = document.getElementById('palette-card-content');
+
+  const swatchesHTML = p.map(s => `
+    <div class="palette-card-swatch ${textureClass(s.texture)}" style="--swatch-color:${s.hex}">
+      <div class="palette-card-swatch-label">
+        <span class="palette-card-swatch-name">${s.name}</span>
+      </div>
+    </div>
+  `).join('');
+
+  const namesHTML = p.map(s => `
+    <div class="palette-card-name-item">
+      <span class="palette-card-hex">${s.hex}</span>
+      <span class="palette-card-cn">${s.name}</span>
+    </div>
+  `).join('');
+
+  const notesHTML = p.map(s => `
+    <div class="palette-card-note-row">
+      <div class="palette-card-note-dot" style="background:${s.hex};flex-shrink:0;width:8px;height:8px;border-radius:50%;margin-top:5px;"></div>
+      <div class="palette-card-note-text">${s.note}</div>
+    </div>
+  `).join('');
+
+  card.innerHTML = `
+    <div class="palette-card-header">
+      <div class="palette-card-brand">Designer Wallcoverings</div>
+      <div class="palette-card-rule"></div>
+      <div class="palette-card-season-name">${season.name} 2026 · Color Story</div>
+    </div>
+    <div class="palette-card-swatches">${swatchesHTML}</div>
+    <div class="palette-card-names">${namesHTML}</div>
+    <div class="palette-card-notes">
+      <div class="palette-card-notes-title">Styling Notes</div>
+      ${notesHTML}
+    </div>
+    <div class="palette-card-footer">
+      <div class="palette-card-footer-brand">Designer Wallcoverings · Color Story Studio</div>
+      <div class="palette-card-footer-url">designerwallcoverings.com</div>
+    </div>
+  `;
+}
+
+function openModal() {
+  renderPaletteCard(currentSeason);
+  const modal = document.getElementById('palette-modal');
+  modal.classList.add('open');
+  modal.setAttribute('aria-hidden', 'false');
+  document.getElementById('export-btn').setAttribute('aria-expanded', 'true');
+  document.body.style.overflow = 'hidden';
+  // Focus close button
+  setTimeout(() => document.getElementById('modal-close-btn').focus(), 50);
+}
+
+function closeModal() {
+  const modal = document.getElementById('palette-modal');
+  modal.classList.remove('open');
+  modal.setAttribute('aria-hidden', 'true');
+  document.getElementById('export-btn').setAttribute('aria-expanded', 'false');
+  document.body.style.overflow = '';
+  document.getElementById('export-btn').focus();
+}
+
+/* ============================================================
+   TOAST
+   ============================================================ */
+function showToast(msg) {
+  const toast = document.getElementById('toast');
+  toast.textContent = msg;
+  toast.classList.add('show');
+  setTimeout(() => toast.classList.remove('show'), 2800);
+}
+
+/* ============================================================
+   COPY TO CLIPBOARD
+   ============================================================ */
+function copyCaption() {
+  const season = SEASONS[currentSeason];
+  const full   = season.caption + '\n\n' + season.hashtags;
+  navigator.clipboard.writeText(full).then(() => {
+    const btn = document.getElementById('copy-caption-btn');
+    btn.textContent = 'Copied!';
+    btn.classList.add('copied');
+    showToast('Caption copied to clipboard');
+    setTimeout(() => {
+      btn.textContent = 'Copy Caption';
+      btn.classList.remove('copied');
+    }, 2500);
+  }).catch(() => {
+    showToast('Please select and copy the text manually');
+  });
+}
+
+/* ============================================================
+   HERO COLOR STRIPE
+   ============================================================ */
+function updateHeroStripe(seasonKey) {
+  const palette = SEASONS[seasonKey].palette;
+  const stripe  = document.getElementById('hero-color-stripe');
+  stripe.innerHTML = '';
+  palette.forEach(s => {
+    const seg = document.createElement('div');
+    seg.className = 'hero-stripe-seg';
+    seg.style.background = s.hex;
+    seg.title = s.name;
+    stripe.appendChild(seg);
+  });
+}
+
+/* ============================================================
+   SEASON CHANGE
+   ============================================================ */
+function changeSeason(key) {
+  if (key === currentSeason) return;
+  currentSeason = key;
+
+  // Update tabs
+  document.querySelectorAll('.season-tab').forEach(tab => {
+    const isActive = tab.dataset.season === key;
+    tab.classList.toggle('active', isActive);
+    tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
+  });
+
+  const season = SEASONS[key];
+  document.getElementById('palette-season-name').textContent = season.name;
+  document.getElementById('palette-tagline').textContent     = season.tagline;
+
+  renderSwatches(key);
+  renderCarousel(key);
+  renderCaption(key);
+  updateHeroStripe(key);
+}
+
+/* ============================================================
+   SCROLL REVEAL
+   ============================================================ */
+function initScrollReveal() {
+  const observer = new IntersectionObserver((entries) => {
+    entries.forEach(e => {
+      if (e.isIntersecting) {
+        e.target.classList.add('in-view');
+        observer.unobserve(e.target);
+      }
+    });
+  }, { threshold: 0.08 });
+
+  document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
+}
+
+/* ============================================================
+   KEYBOARD NAVIGATION
+   ============================================================ */
+document.addEventListener('keydown', e => {
+  // Carousel nav
+  if (document.activeElement.closest('#carousel-viewport') ||
+      document.activeElement.id === 'prev-btn' ||
+      document.activeElement.id === 'next-btn') {
+    if (e.key === 'ArrowLeft')  { e.preventDefault(); prevSlide(); }
+    if (e.key === 'ArrowRight') { e.preventDefault(); nextSlide(); }
+  }
+  // Global carousel shortcut (when not in form element)
+  const tag = document.activeElement.tagName;
+  if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') {
+    if (e.key === 'ArrowLeft')  prevSlide();
+    if (e.key === 'ArrowRight') nextSlide();
+  }
+  // Close modal on Escape
+  if (e.key === 'Escape') closeModal();
+});
+
+/* ============================================================
+   INIT
+   ============================================================ */
+function init() {
+  // Season tabs
+  document.querySelectorAll('.season-tab').forEach(tab => {
+    tab.addEventListener('click', () => changeSeason(tab.dataset.season));
+  });
+
+  // Carousel arrows
+  document.getElementById('prev-btn').addEventListener('click', prevSlide);
+  document.getElementById('next-btn').addEventListener('click', nextSlide);
+
+  // Caption copy
+  document.getElementById('copy-caption-btn').addEventListener('click', copyCaption);
+
+  // Export modal
+  document.getElementById('export-btn').addEventListener('click', openModal);
+  document.getElementById('modal-close-btn').addEventListener('click', closeModal);
+  document.getElementById('palette-modal').addEventListener('click', e => {
+    if (e.target === e.currentTarget) closeModal();
+  });
+
+  // Initial render
+  const season = SEASONS[currentSeason];
+  document.getElementById('palette-season-name').textContent = season.name;
+  document.getElementById('palette-tagline').textContent     = season.tagline;
+
+  renderSwatches(currentSeason);
+  renderCarousel(currentSeason);
+  renderCaption(currentSeason);
+  updateHeroStripe(currentSeason);
+  initScrollReveal();
+}
+
+document.addEventListener('DOMContentLoaded', init);
+</script>
+</body>
+</html>

← f631a81 Cost: drop paid models (kimi/gpt/grok) from loop roster per  ·  back to Model Arena  ·  Run on Claude Max subscription only: drop paid API models (k 682fd46 →