[object Object]

← back to Model Arena

Agent-driven social tool: DW Reel Storyboard Builder (frontend-developer) — 9:16 preview + storyboard editor. Registered.

dac7e64b3f624fefc23500a2e763c98c960b6225 · 2026-07-25 11:37:54 -0700 · Steve Abrams

Files touched

Diff

commit dac7e64b3f624fefc23500a2e763c98c960b6225
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 11:37:54 2026 -0700

    Agent-driven social tool: DW Reel Storyboard Builder (frontend-developer) — 9:16 preview + storyboard editor. Registered.
---
 data/arcade-games.json               |   6 +
 public/games/dw-design-iq.html       | 791 +++++++++++++++++++++++++++++
 public/games/dw-reel-storyboard.html | 948 +++++++++++++++++++++++++++++++++++
 3 files changed, 1745 insertions(+)

diff --git a/data/arcade-games.json b/data/arcade-games.json
index 992b495..bd5866c 100644
--- a/data/arcade-games.json
+++ b/data/arcade-games.json
@@ -47,6 +47,12 @@
       "title": "DW Shop by Color",
       "credit": "frontend-developer",
       "note": "Agent-built \u2014 interactive color-wheel discovery, hue\u2192swatch families, warm/cool + value controls"
+    },
+    {
+      "slug": "dw-reel-storyboard",
+      "title": "DW Reel Storyboard",
+      "credit": "frontend-developer",
+      "note": "Agent-built social tool \u2014 9:16 Reel preview + drag-reorder storyboard editor, scene presets, caption/hashtag export"
     }
   ]
 }
\ No newline at end of file
diff --git a/public/games/dw-design-iq.html b/public/games/dw-design-iq.html
new file mode 100644
index 0000000..67adf2c
--- /dev/null
+++ b/public/games/dw-design-iq.html
@@ -0,0 +1,791 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Design IQ — Designer Wallcoverings</title>
+<style>
+:root{
+  --bg:#f4f1ea;--bg2:#ede9df;--ink:#1c1a17;--ink2:#4a4540;
+  --accent:#c9a961;--accent2:#a8873e;--correct:#3a7d5c;--wrong:#8b3a3a;
+  --surface:#ffffff;--rim:rgba(201,169,97,.25);
+  --serif:'Cormorant Garamond','Playfair Display','EB Garamond',Georgia,serif;
+  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,sans-serif;
+  --ease:cubic-bezier(.4,0,.2,1);--r:12px;
+}
+*{box-sizing:border-box;margin:0;padding:0}
+html,body{height:100%;background:var(--bg);color:var(--ink);font-family:var(--sans);overflow-x:hidden}
+
+/* HEADER */
+header{
+  display:flex;align-items:center;gap:16px;
+  padding:18px 28px 14px;border-bottom:1px solid var(--rim);
+  background:var(--bg);position:sticky;top:0;z-index:50;
+}
+.wordmark{
+  font-family:var(--serif);font-size:clamp(20px,3.5vw,36px);
+  letter-spacing:.18em;text-transform:uppercase;color:var(--ink);
+  font-weight:400;line-height:1;
+}
+.wordmark span{color:var(--accent);font-size:.7em;letter-spacing:.35em;display:block;margin-top:2px}
+header .right{margin-left:auto;display:flex;align-items:center;gap:12px}
+#muteBtn{
+  background:none;border:1px solid var(--rim);border-radius:50%;
+  width:36px;height:36px;cursor:pointer;font-size:16px;
+  display:flex;align-items:center;justify-content:center;transition:background .2s;
+}
+#muteBtn:hover{background:var(--bg2)}
+#scoreBadge{
+  font-family:var(--serif);font-size:22px;color:var(--accent);font-weight:600;
+  min-width:60px;text-align:right;display:none;
+}
+
+/* SCREENS */
+.screen{
+  display:none;min-height:calc(100vh - 70px);
+  align-items:center;justify-content:center;flex-direction:column;
+  padding:32px 20px;
+}
+.screen.active{display:flex}
+
+/* START SCREEN */
+#startScreen{gap:24px;text-align:center}
+.hero-title{
+  font-family:var(--serif);font-size:clamp(42px,7vw,80px);
+  color:var(--ink);letter-spacing:.06em;line-height:1.05;font-weight:400;
+}
+.hero-sub{color:var(--ink2);font-size:clamp(14px,2vw,17px);max-width:520px;line-height:1.7;margin-top:8px}
+.btn{
+  font-family:var(--serif);font-size:18px;letter-spacing:.1em;
+  background:var(--accent);color:var(--ink);border:none;
+  padding:14px 44px;border-radius:40px;cursor:pointer;
+  transition:transform .2s var(--ease),background .2s;font-weight:600;
+  text-transform:uppercase;
+}
+.btn:hover{background:var(--accent2);transform:translateY(-2px)}
+.btn:active{transform:translateY(0)}
+.best-badge{
+  font-size:13px;color:var(--ink2);
+  background:var(--bg2);border:1px solid var(--rim);
+  padding:6px 18px;border-radius:20px;display:none;
+}
+.ornament{font-size:28px;color:var(--accent);opacity:.6;letter-spacing:.3em}
+
+/* GAME SCREEN */
+#gameScreen{gap:0;justify-content:flex-start;padding:0}
+.game-wrap{width:100%;max-width:760px;margin:0 auto;padding:24px 20px 48px}
+
+/* HUD */
+.hud{display:flex;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap}
+.hud-q{font-size:13px;color:var(--ink2);font-weight:600;letter-spacing:.08em;text-transform:uppercase}
+.hud-streak{
+  margin-left:auto;font-family:var(--serif);font-size:20px;color:var(--accent);
+  font-weight:700;min-width:80px;text-align:right;transition:all .3s;
+}
+.streak-flare{animation:flare .4s var(--ease)}
+@keyframes flare{0%{transform:scale(1)}50%{transform:scale(1.5);color:#ffdd88}100%{transform:scale(1)}}
+
+/* TIMER BAR */
+.timer-track{height:5px;background:var(--bg2);border-radius:3px;margin-bottom:24px;overflow:hidden}
+.timer-fill{height:100%;background:var(--accent);border-radius:3px;transition:width .1s linear;transform-origin:left}
+
+/* QUESTION CARD */
+.q-card{
+  background:var(--surface);border:1px solid var(--rim);border-radius:var(--r);
+  padding:32px;margin-bottom:20px;box-shadow:0 2px 20px rgba(0,0,0,.06);
+}
+.q-label{
+  font-size:11px;letter-spacing:.18em;text-transform:uppercase;
+  color:var(--accent2);font-weight:600;margin-bottom:14px;
+}
+.q-text{
+  font-family:var(--serif);font-size:clamp(20px,3vw,28px);
+  color:var(--ink);line-height:1.4;font-weight:400;
+}
+.q-svg{
+  margin:20px auto 0;display:block;border-radius:8px;
+  border:1px solid var(--rim);max-width:100%;
+}
+
+/* ANSWERS */
+.answers{display:grid;grid-template-columns:1fr 1fr;gap:12px}
+@media(max-width:520px){.answers{grid-template-columns:1fr}}
+.ans-btn{
+  background:var(--surface);border:1.5px solid var(--rim);
+  border-radius:var(--r);padding:16px 20px;cursor:pointer;
+  font-family:var(--serif);font-size:18px;color:var(--ink);
+  text-align:left;line-height:1.3;
+  transition:transform .18s var(--ease),border-color .18s,background .18s,box-shadow .18s;
+  position:relative;overflow:hidden;
+}
+.ans-btn:hover:not(:disabled){
+  transform:translateY(-3px);border-color:var(--accent);
+  box-shadow:0 6px 20px rgba(201,169,97,.18);
+}
+.ans-btn:disabled{cursor:not-allowed}
+.ans-btn.correct{
+  background:#e8f5ee;border-color:var(--correct);color:var(--correct);
+  animation:popIn .3s var(--ease);
+}
+.ans-btn.wrong{background:#f5e8e8;border-color:var(--wrong);color:var(--wrong)}
+.ans-btn.reveal{background:#e8f5ee;border-color:var(--correct);color:var(--correct)}
+@keyframes popIn{0%{transform:scale(.96)}60%{transform:scale(1.04)}100%{transform:scale(1)}}
+
+/* FEEDBACK */
+.feedback{
+  text-align:center;padding:16px;border-radius:var(--r);
+  font-family:var(--serif);font-size:20px;min-height:68px;
+  transition:all .3s var(--ease);opacity:0;
+}
+.feedback.show{opacity:1}
+.feedback.correct-fb{color:var(--correct)}
+.feedback.wrong-fb{color:var(--wrong)}
+.feedback .speed-bonus{font-size:14px;color:var(--accent2);display:block;margin-top:2px;font-family:var(--sans)}
+
+/* NEXT BTN */
+#nextBtn{
+  display:none;margin:12px auto 0;font-size:16px;padding:12px 36px;letter-spacing:.12em;
+}
+
+/* END SCREEN */
+#endScreen{gap:20px;text-align:center;max-width:600px;margin:0 auto}
+.grade-title{
+  font-family:var(--serif);font-size:clamp(36px,6vw,64px);
+  color:var(--accent);font-weight:400;letter-spacing:.06em;
+}
+.final-score{
+  font-family:var(--serif);font-size:clamp(52px,9vw,96px);
+  color:var(--ink);font-weight:600;line-height:1;
+}
+.final-label{font-size:14px;color:var(--ink2);letter-spacing:.1em;text-transform:uppercase}
+.stats-row{display:flex;gap:32px;justify-content:center;flex-wrap:wrap;margin:8px 0}
+.stat{text-align:center}
+.stat-n{font-family:var(--serif);font-size:28px;color:var(--ink);font-weight:600}
+.stat-l{font-size:11px;color:var(--ink2);letter-spacing:.1em;text-transform:uppercase;margin-top:2px}
+.new-best{
+  background:linear-gradient(135deg,var(--accent),var(--accent2));
+  color:var(--ink);font-size:13px;font-weight:700;letter-spacing:.12em;
+  text-transform:uppercase;padding:6px 20px;border-radius:20px;
+  display:inline-block;margin-bottom:4px;animation:pulse 1s ease infinite alternate;
+}
+@keyframes pulse{from{opacity:.8}to{opacity:1}}
+
+/* PROGRESS DOTS */
+.progress-dots{display:flex;gap:6px;justify-content:center;margin-bottom:8px}
+.dot{
+  width:8px;height:8px;border-radius:50%;background:var(--bg2);
+  transition:background .3s;
+}
+.dot.done-correct{background:var(--correct)}
+.dot.done-wrong{background:var(--wrong)}
+.dot.current{background:var(--accent)}
+
+/* OVERLAYS */
+.overlay{
+  position:fixed;inset:0;background:rgba(28,26,23,.7);
+  display:none;align-items:center;justify-content:center;z-index:100;
+  backdrop-filter:blur(4px);
+}
+.overlay.visible{display:flex}
+.overlay-box{
+  background:var(--surface);border-radius:var(--r);padding:40px 48px;
+  max-width:420px;width:90%;text-align:center;
+  box-shadow:0 24px 80px rgba(0,0,0,.25);
+}
+.overlay-box h2{font-family:var(--serif);font-size:36px;color:var(--ink);margin-bottom:12px}
+.overlay-box p{color:var(--ink2);line-height:1.6;margin-bottom:24px}
+</style>
+</head>
+<body>
+
+<header>
+  <div class="wordmark">Designer Wallcoverings<span>Design IQ</span></div>
+  <div class="right">
+    <span id="scoreBadge">0</span>
+    <button id="muteBtn" aria-label="Toggle sound">🔔</button>
+  </div>
+</header>
+
+<!-- START -->
+<div id="startScreen" class="screen active" role="main">
+  <div class="ornament">✦ ✦ ✦</div>
+  <h1 class="hero-title">Design IQ</h1>
+  <p class="hero-sub">Test your knowledge of interior design eras, wallcovering materials, and classic pattern motifs. 12 questions. Beat the clock. Prove your taste.</p>
+  <div class="best-badge" id="bestBadge">Best Score: <strong id="bestNum">0</strong></div>
+  <button class="btn" id="startBtn">Begin the Quiz</button>
+  <div class="ornament">✦</div>
+</div>
+
+<!-- GAME -->
+<div id="gameScreen" class="screen" role="main" aria-live="polite">
+  <div class="game-wrap">
+    <div class="hud">
+      <span class="hud-q" id="qCounter">Question 1 of 12</span>
+      <span class="hud-streak" id="streakDisplay"></span>
+    </div>
+    <div class="progress-dots" id="progressDots"></div>
+    <div class="timer-track"><div class="timer-fill" id="timerFill"></div></div>
+    <div class="q-card" id="qCard">
+      <div class="q-label" id="qLabel"></div>
+      <div class="q-text" id="qText"></div>
+      <div id="qSvg"></div>
+    </div>
+    <div class="answers" id="answers"></div>
+    <div class="feedback" id="feedback"></div>
+    <button class="btn" id="nextBtn">Next →</button>
+  </div>
+</div>
+
+<!-- END -->
+<div id="endScreen" class="screen" role="main">
+  <div class="ornament">✦ ✦ ✦</div>
+  <div id="newBestBadge" class="new-best" style="display:none">New Best!</div>
+  <div class="grade-title" id="gradeTitle">Connoisseur</div>
+  <div class="final-score" id="finalScore">0</div>
+  <div class="final-label">Design IQ Points</div>
+  <div class="stats-row">
+    <div class="stat"><div class="stat-n" id="statCorrect">0</div><div class="stat-l">Correct</div></div>
+    <div class="stat"><div class="stat-n" id="statStreak">0</div><div class="stat-l">Best Streak</div></div>
+    <div class="stat"><div class="stat-n" id="statTime">0s</div><div class="stat-l">Avg Speed</div></div>
+  </div>
+  <p id="gradeBlurb" style="color:var(--ink2);line-height:1.7;max-width:400px"></p>
+  <button class="btn" id="playAgainBtn">Play Again</button>
+</div>
+
+<div class="overlay" id="overlay" role="dialog" aria-modal="true">
+  <div class="overlay-box">
+    <h2 id="overlayTitle"></h2>
+    <p id="overlayMsg"></p>
+    <button class="btn" id="overlayBtn">OK</button>
+  </div>
+</div>
+
+<script>
+// ─── WEB AUDIO ───────────────────────────────────────────────────────────────
+let audioCtx=null,muted=false;
+function getCtx(){if(!audioCtx)audioCtx=new(window.AudioContext||window.webkitAudioContext)();return audioCtx}
+function playTone(freq,dur,type='sine',vol=.18,ramp=true){
+  if(muted)return;
+  try{
+    const c=getCtx(),g=c.createGain(),o=c.createOscillator();
+    o.type=type;o.frequency.value=freq;
+    g.gain.value=vol;
+    if(ramp)g.gain.exponentialRampToValueAtTime(.001,c.currentTime+dur);
+    o.connect(g);g.connect(c.destination);
+    o.start();o.stop(c.currentTime+dur);
+  }catch(e){}
+}
+function sndCorrect(){playTone(523,.12,'sine',.15);setTimeout(()=>playTone(659,.18,'sine',.12),100);setTimeout(()=>playTone(784,.25,'sine',.1),200)}
+function sndWrong(){playTone(220,.08,'sawtooth',.12);setTimeout(()=>playTone(196,.2,'sawtooth',.08),90)}
+function sndStreak(){playTone(880,.08,'sine',.12);setTimeout(()=>playTone(1046,.08,'sine',.1),80);setTimeout(()=>playTone(1318,.22,'sine',.1),160)}
+function sndWin(){[523,659,784,1046].forEach((f,i)=>setTimeout(()=>playTone(f,.3,'sine',.12),i*120))}
+
+document.getElementById('muteBtn').addEventListener('click',()=>{
+  muted=!muted;
+  document.getElementById('muteBtn').textContent=muted?'🔕':'🔔';
+});
+
+// ─── SVGs ────────────────────────────────────────────────────────────────────
+const svgs={
+  damask:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#f4f1ea"/>
+    <g fill="#c9a961" opacity=".85">
+      <ellipse cx="90" cy="90" rx="18" ry="32"/>
+      <ellipse cx="90" cy="90" rx="32" ry="18"/>
+      <ellipse cx="90" cy="90" rx="10" ry="20" fill="#f4f1ea"/>
+      <ellipse cx="90" cy="90" rx="20" ry="10" fill="#f4f1ea"/>
+      <circle cx="90" cy="90" r="6"/>
+      <path d="M90 58 C80 68 70 78 60 90 C70 102 80 112 90 122 C100 112 110 102 120 90 C110 78 100 68 90 58Z" fill="none" stroke="#c9a961" stroke-width="2"/>
+      <path d="M58 90 C68 80 78 70 90 60 C102 70 112 80 122 90 C112 100 102 110 90 120 C78 110 68 100 58 90Z" fill="none" stroke="#c9a961" stroke-width="1.5"/>
+      <circle cx="60" cy="60" r="4"/><circle cx="120" cy="60" r="4"/>
+      <circle cx="60" cy="120" r="4"/><circle cx="120" cy="120" r="4"/>
+      <ellipse cx="90" cy="48" rx="6" ry="10"/><ellipse cx="90" cy="132" rx="6" ry="10"/>
+      <ellipse cx="48" cy="90" rx="10" ry="6"/><ellipse cx="132" cy="90" rx="10" ry="6"/>
+    </g>
+  </svg>`,
+  greekkey:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#1c1a17"/>
+    <g fill="none" stroke="#c9a961" stroke-width="4" stroke-linecap="square">
+      <polyline points="10,10 10,40 40,40 40,20 20,20 20,60 60,60 60,10 10,10"/>
+      <polyline points="90,10 90,40 120,40 120,20 100,20 100,60 140,60 140,10 90,10"/>
+      <polyline points="10,90 10,120 40,120 40,100 20,100 20,140 60,140 60,90 10,90"/>
+      <polyline points="90,90 90,120 120,120 120,100 100,100 100,140 140,140 140,90 90,90"/>
+      <line x1="0" y1="75" x2="180" y2="75" stroke-width="2" opacity=".3"/>
+      <line x1="75" y1="0" x2="75" y2="180" stroke-width="2" opacity=".3"/>
+    </g>
+  </svg>`,
+  ikat:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#2a1f3a"/>
+    <defs>
+      <filter id="blur"><feGaussianBlur stdDeviation="1.4"/></filter>
+    </defs>
+    <g filter="url(#blur)">
+      <ellipse cx="45" cy="45" rx="14" ry="28" fill="#c9a961" transform="rotate(15 45 45)"/>
+      <ellipse cx="135" cy="45" rx="14" ry="28" fill="#e8d5a0" transform="rotate(-15 135 45)"/>
+      <ellipse cx="45" cy="135" rx="14" ry="28" fill="#e8d5a0" transform="rotate(-15 45 135)"/>
+      <ellipse cx="135" cy="135" rx="14" ry="28" fill="#c9a961" transform="rotate(15 135 135)"/>
+      <ellipse cx="90" cy="90" rx="20" ry="36" fill="#b87333" transform="rotate(5 90 90)"/>
+      <ellipse cx="90" cy="90" rx="10" ry="18" fill="#c9a961"/>
+    </g>
+    <g fill="none" stroke="#c9a961" stroke-width="1" opacity=".3">
+      <line x1="0" y1="0" x2="180" y2="180"/>
+      <line x1="0" y1="90" x2="180" y2="90"/>
+      <line x1="90" y1="0" x2="90" y2="180"/>
+    </g>
+  </svg>`,
+  toile:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#f0ede6"/>
+    <g fill="none" stroke="#4a6b8a" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">
+      <path d="M60 80 Q55 60 70 45 Q85 30 95 50 Q100 65 90 75 Q80 85 70 80Z"/>
+      <path d="M70 45 Q75 35 85 30 Q95 28 92 40"/>
+      <circle cx="80" cy="58" r="3" fill="#4a6b8a"/>
+      <path d="M60 80 Q50 95 55 110 Q60 125 70 120 Q80 115 75 100 Q72 90 65 85"/>
+      <path d="M75 100 L85 105 L90 95 L80 90"/>
+      <path d="M90 75 Q100 85 105 100 Q108 115 100 118 Q92 120 88 108 Q85 95 90 75"/>
+      <path d="M55 110 Q45 120 50 135 Q55 145 65 140 Q60 130 58 120"/>
+      <path d="M100 118 Q110 128 108 142 Q105 152 95 148 Q100 138 98 126"/>
+      <line x1="40" y1="155" x2="140" y2="155" stroke-width=".8"/>
+      <path d="M30 30 Q35 25 40 28 Q38 35 32 36Z" fill="#4a6b8a" opacity=".4"/>
+      <path d="M140 120 Q148 115 152 120 Q150 128 143 126Z" fill="#4a6b8a" opacity=".4"/>
+      <path d="M120 40 Q130 30 140 38 Q135 48 125 46Z" fill="#4a6b8a" opacity=".3"/>
+    </g>
+  </svg>`,
+  trellis:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#f4f1ea"/>
+    <g stroke="#c9a961" stroke-width="1.8" fill="none" opacity=".8">
+      <line x1="0" y1="0" x2="180" y2="180"/><line x1="90" y1="0" x2="180" y2="90"/>
+      <line x1="0" y1="90" x2="90" y2="180"/><line x1="0" y1="180" x2="180" y2="0"/>
+      <line x1="90" y1="180" x2="180" y2="90"/><line x1="0" y1="90" x2="90" y2="0"/>
+    </g>
+    <g stroke="#4a4540" stroke-width=".8" fill="none">
+      <rect x="22" y="22" width="46" height="46" rx="6"/>
+      <rect x="112" y="22" width="46" height="46" rx="6"/>
+      <rect x="22" y="112" width="46" height="46" rx="6"/>
+      <rect x="112" y="112" width="46" height="46" rx="6"/>
+      <rect x="67" y="67" width="46" height="46" rx="6"/>
+    </g>
+    <g fill="#c9a961">
+      <circle cx="45" cy="45" r="4"/><circle cx="135" cy="45" r="4"/>
+      <circle cx="45" cy="135" r="4"/><circle cx="135" cy="135" r="4"/>
+      <circle cx="90" cy="90" r="5"/><circle cx="90" cy="0" r="3"/>
+      <circle cx="90" cy="180" r="3"/><circle cx="0" cy="90" r="3"/>
+      <circle cx="180" cy="90" r="3"/>
+    </g>
+  </svg>`,
+  chinoiserie:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#1a3a2a"/>
+    <g fill="none" stroke="#c9d9c0" stroke-width="1" stroke-linecap="round">
+      <path d="M90 20 Q105 40 100 60 Q115 55 120 70 Q105 75 100 90 Q115 95 110 115 Q95 108 90 125"/>
+      <path d="M90 20 Q75 40 80 60 Q65 55 60 70 Q75 75 80 90 Q65 95 70 115 Q85 108 90 125"/>
+      <ellipse cx="90" cy="50" rx="8" ry="12" fill="#c9a961" stroke="#c9a961"/>
+      <ellipse cx="115" cy="72" rx="7" ry="10" fill="#d4b896" stroke="#d4b896"/>
+      <ellipse cx="65" cy="72" rx="7" ry="10" fill="#d4b896" stroke="#d4b896"/>
+      <ellipse cx="105" cy="105" rx="6" ry="9" fill="#8fbc8f" stroke="#8fbc8f"/>
+      <ellipse cx="75" cy="105" rx="6" ry="9" fill="#8fbc8f" stroke="#8fbc8f"/>
+      <path d="M70 140 Q90 130 110 140 Q115 155 90 160 Q65 155 70 140Z" fill="#c9a961" stroke="none"/>
+      <circle cx="90" cy="148" r="4" fill="#1a3a2a"/>
+      <path d="M50 30 Q55 20 62 26 Q58 36 50 30Z" fill="#8fbc8f"/>
+      <path d="M130 30 Q125 20 118 26 Q122 36 130 30Z" fill="#8fbc8f"/>
+      <path d="M30 90 Q20 82 26 74 Q36 78 30 90Z" fill="#c9d9c0"/>
+      <path d="M150 90 Q160 82 154 74 Q144 78 150 90Z" fill="#c9d9c0"/>
+    </g>
+  </svg>`,
+  artdeco:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#0d0b09"/>
+    <g fill="#c9a961">
+      <polygon points="90,10 100,50 140,50 108,74 120,114 90,90 60,114 72,74 40,50 80,50"/>
+      <circle cx="90" cy="90" r="28" fill="none" stroke="#c9a961" stroke-width="2"/>
+      <circle cx="90" cy="90" r="20" fill="none" stroke="#c9a961" stroke-width="1" opacity=".5"/>
+    </g>
+    <g stroke="#c9a961" stroke-width="1.5" fill="none" opacity=".6">
+      <line x1="90" y1="10" x2="90" y2="40"/>
+      <line x1="90" y1="140" x2="90" y2="170"/>
+      <line x1="10" y1="90" x2="40" y2="90"/>
+      <line x1="140" y1="90" x2="170" y2="90"/>
+    </g>
+    <g stroke="#c9a961" stroke-width=".8" fill="none" opacity=".35">
+      <rect x="10" y="10" width="160" height="160"/>
+      <rect x="20" y="20" width="140" height="140"/>
+    </g>
+  </svg>`,
+  grasscloth:`<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
+    <rect width="180" height="180" fill="#c4b89a"/>
+    <g stroke="#8a7a5a" stroke-width=".9" opacity=".6">
+      ${Array.from({length:36},(_,i)=>`<line x1="${i*5}" y1="0" x2="${i*5+2}" y2="180"/>`).join('')}
+    </g>
+    <g stroke="#6b5d3f" stroke-width=".5" opacity=".4">
+      ${Array.from({length:18},(_,i)=>`<line x1="0" y1="${i*10}" x2="180" y2="${i*10+3}"/>`).join('')}
+    </g>
+    <g stroke="#a08060" stroke-width="1.2" opacity=".3">
+      <line x1="30" y1="0" x2="30" y2="180"/>
+      <line x1="60" y1="0" x2="60" y2="180"/>
+      <line x1="90" y1="0" x2="90" y2="180"/>
+      <line x1="120" y1="0" x2="120" y2="180"/>
+      <line x1="150" y1="0" x2="150" y2="180"/>
+    </g>
+    <rect width="180" height="180" fill="none" stroke="#6b5d3f" stroke-width="1"/>
+  </svg>`,
+};
+
+// ─── QUESTIONS ───────────────────────────────────────────────────────────────
+const ALL_QUESTIONS=[
+  {
+    label:'Pattern Recognition',
+    text:'What pattern motif is shown below?',
+    svg:'damask',
+    answers:['Damask','Toile de Jouy','Chinoiserie','Ogee'],
+    correct:0,
+    explain:'Damask is characterized by mirrored, reversible woven patterns — typically a central medallion with ornate floral or foliate symmetry, originating in Damascus.'
+  },
+  {
+    label:'Pattern Recognition',
+    text:'This repeating geometric border motif traces back to ancient Greece. Name it.',
+    svg:'greekkey',
+    answers:['Chevron','Greek Key / Meander','Houndstooth','Labyrinth Fret'],
+    correct:1,
+    explain:'The Greek Key (or Meander) is a continuous interlocking border motif derived from ancient Greek pottery and architectural friezes — a perennial in interior design.'
+  },
+  {
+    label:'Technique & Motif',
+    text:'The slightly blurred, feathered edges on this woven pattern are the hallmark of which ancient dyeing technique?',
+    svg:'ikat',
+    answers:['Batik','Ikat','Resist-printing','Shibori'],
+    correct:1,
+    explain:'Ikat is a resist-dyeing method where yarns are bound and dyed before weaving, creating the characteristic soft "halo" bleed at pattern edges.'
+  },
+  {
+    label:'Pattern Recognition',
+    text:'This pastoral scene with fine engraved-style line work depicts which classic wallcovering style?',
+    svg:'toile',
+    answers:['Toile de Jouy','Chinoiserie','Scenic Mural','Grisaille'],
+    correct:0,
+    explain:'Toile de Jouy originated in Jouy-en-Josas, France (c. 1760). Monochromatic pastoral or narrative scenes printed on plain ground cloth.'
+  },
+  {
+    label:'Pattern Recognition',
+    text:'Identify the geometric lattice pattern shown — a perennial in wallcovering design.',
+    svg:'trellis',
+    answers:['Ogee','Trellis / Lattice','Diamond Fret','Quatrefoil'],
+    correct:1,
+    explain:'Trellis patterns simulate garden lattice — diagonal or orthogonal grids with intersecting lines, sometimes with botanical motifs at the nodes.'
+  },
+  {
+    label:'Pattern Recognition',
+    text:'Which design tradition does this stylized botanical motif represent?',
+    svg:'chinoiserie',
+    answers:['Japonisme','Indian Paisley','Chinoiserie','Indienne'],
+    correct:2,
+    explain:'Chinoiserie (French: "Chinese-esque") flourished in 18th-century Europe as a romanticized interpretation of East Asian art — pagodas, exotic birds, and stylized botanicals.'
+  },
+  {
+    label:'Design Era',
+    text:'The starburst medallion and gilded geometric symmetry shown here typify which early 20th-century movement?',
+    svg:'artdeco',
+    answers:['Arts & Crafts','Bauhaus','Art Deco','Mid-Century Modern'],
+    correct:2,
+    explain:'Art Deco (1920s–30s) prized geometric symmetry, metallic glamour, and bold angular forms — influenced by Cubism and the 1925 Paris Exposition Internationale.'
+  },
+  {
+    label:'Materials',
+    text:'The tactile, woven texture you see here is made from natural plant fiber twisted into weft yarns on a cotton warp. What is this wallcovering material?',
+    svg:'grasscloth',
+    answers:['Sisal','Grasscloth','Jute Hessian','Linen Weave'],
+    correct:1,
+    explain:'Grasscloth uses natural plant fibers (seagrass, jute, bamboo) woven into panels. Each roll is unique; vertical seams are visible by design.'
+  },
+  {
+    label:'Design Vocabulary',
+    text:'A wallcovering described as "Type II" refers to what specification?',
+    svg:null,
+    answers:['A double roll','A commercial-grade vinyl ≥20 oz/yd²','An antimicrobial coating','An unpasted backing type'],
+    correct:1,
+    explain:'Type II is a commercial vinyl wallcovering standard (ASTM F793) — minimum 20 oz/sq yd total weight. Required in hospitality, healthcare, and high-traffic spaces.'
+  },
+  {
+    label:'Design Era',
+    text:'Which design aesthetic combines grandmotherly antiques, chintz, needlepoint, and layered pattern-on-pattern to deliberately nostalgic effect?',
+    svg:null,
+    answers:['Cottagecore','Grandmillennial','Dark Academia','Maximalism'],
+    correct:1,
+    explain:'Grandmillennial style (coined c. 2019) is a Gen-X/Millennial reclamation of "granny chic" — fringe, floral chintz, ornate wallpaper, and heirloom objects.'
+  },
+  {
+    label:'Color History',
+    text:'Which pigment gave William Morris\'s iconic Victorian wallpapers their distinctive, and later controversial, emerald green?',
+    svg:null,
+    answers:['Viridian','Malachite','Scheele\'s Green (arsenic)','Chrome Green'],
+    correct:2,
+    explain:'Scheele\'s Green (copper arsenite) produced brilliant emerald tones used in Victorian wallpapers. It could release arsenic gas in damp conditions — linked to illness in arsenic-saturated rooms.'
+  },
+  {
+    label:'Materials',
+    text:'A "Mylar" wallcovering gets its distinctive reflective quality from what material?',
+    svg:null,
+    answers:['Brushed aluminum foil','Polyester film (biaxially oriented PET)','Lacquered cotton','Metallic-ink printed vinyl'],
+    correct:1,
+    explain:'Mylar is a brand name for biaxially oriented polyester (BoPET) film. In wallcovering, a thin PET layer laminated to a substrate creates the mirror-like reflective surface.'
+  },
+];
+
+// ─── GAME STATE ───────────────────────────────────────────────────────────────
+let questions=[],qIdx=0,score=0,streak=0,bestStreak=0;
+let timerInterval=null,timeLeft=0,qStartTime=0;
+let correctCount=0,totalTime=0;
+const TIMER_MAX=18;
+const SPEED_BONUS_MAX=200;
+const BASE_POINTS=300;
+
+function shuffle(arr){
+  const a=[...arr];for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;
+}
+
+function startGame(){
+  questions=shuffle(ALL_QUESTIONS);
+  qIdx=0;score=0;streak=0;bestStreak=0;correctCount=0;totalTime=0;
+  showScreen('gameScreen');
+  buildDots();
+  loadQuestion();
+  document.getElementById('scoreBadge').style.display='block';
+  updateScoreBadge();
+}
+
+function buildDots(){
+  const d=document.getElementById('progressDots');
+  d.innerHTML='';
+  questions.forEach((_,i)=>{
+    const dot=document.createElement('div');
+    dot.className='dot'+(i===0?' current':'');
+    dot.id=`dot${i}`;
+    d.appendChild(dot);
+  });
+}
+
+function updateDot(idx,result){
+  const d=document.getElementById(`dot${idx}`);
+  if(d)d.className='dot done-'+result;
+  const next=document.getElementById(`dot${idx+1}`);
+  if(next)next.className='dot current';
+}
+
+function loadQuestion(){
+  const q=questions[qIdx];
+  document.getElementById('qCounter').textContent=`Question ${qIdx+1} of ${questions.length}`;
+  document.getElementById('qLabel').textContent=q.label||'';
+  document.getElementById('qText').textContent=q.text;
+
+  const svgDiv=document.getElementById('qSvg');
+  if(q.svg&&svgs[q.svg]){
+    svgDiv.innerHTML=`<div class="q-svg">${svgs[q.svg]}</div>`;
+  } else {
+    svgDiv.innerHTML='';
+  }
+
+  // shuffle answer order
+  const indices=[0,1,2,3];
+  const shuffled=shuffle(indices);
+  const correctShuffled=shuffled.indexOf(q.correct);
+
+  const aContainer=document.getElementById('answers');
+  aContainer.innerHTML='';
+  shuffled.forEach((origIdx,i)=>{
+    const btn=document.createElement('button');
+    btn.className='ans-btn';
+    btn.textContent=q.answers[origIdx];
+    btn.dataset.orig=origIdx;
+    btn.dataset.isCorrect=origIdx===q.correct?'1':'0';
+    btn.addEventListener('click',()=>handleAnswer(btn,q));
+    btn.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();handleAnswer(btn,q);}});
+    aContainer.appendChild(btn);
+  });
+
+  const fb=document.getElementById('feedback');
+  fb.className='feedback';fb.innerHTML='';
+
+  document.getElementById('nextBtn').style.display='none';
+
+  startTimer();
+}
+
+function startTimer(){
+  clearInterval(timerInterval);
+  timeLeft=TIMER_MAX;
+  qStartTime=Date.now();
+  const fill=document.getElementById('timerFill');
+  fill.style.width='100%';
+  fill.style.background='var(--accent)';
+
+  timerInterval=setInterval(()=>{
+    timeLeft-=.1;
+    const pct=Math.max(0,timeLeft/TIMER_MAX*100);
+    fill.style.width=pct+'%';
+    if(pct<30)fill.style.background='#c96161';
+    else if(pct<60)fill.style.background='#c9a330';
+    else fill.style.background='var(--accent)';
+
+    if(timeLeft<=0){
+      clearInterval(timerInterval);
+      handleTimeout();
+    }
+  },100);
+}
+
+function handleTimeout(){
+  disableAnswers();
+  streak=0;updateStreakDisplay();
+  const q=questions[qIdx];
+  revealCorrect(q);
+  const fb=document.getElementById('feedback');
+  fb.className='feedback wrong-fb show';
+  fb.innerHTML=`Time's up! <span class="speed-bonus">${q.explain}</span>`;
+  sndWrong();
+  updateDot(qIdx,'wrong');
+  showNext();
+}
+
+function handleAnswer(btn,q){
+  if(btn.disabled)return;
+  clearInterval(timerInterval);
+  const elapsed=(Date.now()-qStartTime)/1000;
+  totalTime+=elapsed;
+  disableAnswers();
+
+  const isCorrect=btn.dataset.isCorrect==='1';
+  const fb=document.getElementById('feedback');
+
+  if(isCorrect){
+    const speedPct=Math.max(0,1-(elapsed/TIMER_MAX));
+    const speedBonus=Math.round(speedPct*SPEED_BONUS_MAX);
+    const multiplier=streak>=4?3:streak>=2?2:1;
+    const pts=(BASE_POINTS+speedBonus)*multiplier;
+    score+=pts;
+    streak++;
+    correctCount++;
+    if(streak>bestStreak)bestStreak=streak;
+    btn.classList.add('correct');
+    if(streak>=3){sndStreak();}else{sndCorrect();}
+    updateStreakDisplay();
+    fb.className='feedback correct-fb show';
+    let bonusText='';
+    if(speedBonus>150)bonusText+=` +${speedBonus} speed bonus`;
+    if(multiplier>1)bonusText+=` ×${multiplier} streak!`;
+    fb.innerHTML=`Correct! +${pts} pts${bonusText?'<span class="speed-bonus">'+bonusText+'</span>':''}`;
+    updateDot(qIdx,'correct');
+  } else {
+    btn.classList.add('wrong');
+    streak=0;updateStreakDisplay();
+    revealCorrect(q);
+    sndWrong();
+    fb.className='feedback wrong-fb show';
+    fb.innerHTML=`Not quite. <span class="speed-bonus">${q.explain}</span>`;
+    updateDot(qIdx,'wrong');
+  }
+
+  updateScoreBadge();
+  showNext();
+}
+
+function revealCorrect(q){
+  document.querySelectorAll('.ans-btn').forEach(b=>{
+    if(b.dataset.isCorrect==='1')b.classList.add('reveal');
+  });
+}
+
+function disableAnswers(){
+  document.querySelectorAll('.ans-btn').forEach(b=>b.disabled=true);
+}
+
+function updateStreakDisplay(){
+  const el=document.getElementById('streakDisplay');
+  if(streak>=2){
+    el.textContent=`🔥 ${streak}× Streak`;
+    el.classList.add('streak-flare');
+    setTimeout(()=>el.classList.remove('streak-flare'),400);
+  } else {
+    el.textContent='';
+  }
+}
+
+function updateScoreBadge(){
+  document.getElementById('scoreBadge').textContent=score.toLocaleString();
+}
+
+function showNext(){
+  const nb=document.getElementById('nextBtn');
+  nb.style.display='block';
+  nb.textContent=qIdx<questions.length-1?'Next →':'See Results';
+}
+
+document.getElementById('nextBtn').addEventListener('click',()=>{
+  qIdx++;
+  if(qIdx<questions.length){
+    loadQuestion();
+  } else {
+    showEndScreen();
+  }
+});
+
+function showEndScreen(){
+  clearInterval(timerInterval);
+  const best=parseInt(localStorage.getItem('dw-iq-best')||'0');
+  const isNewBest=score>best;
+  if(isNewBest)localStorage.setItem('dw-iq-best',score);
+
+  const pct=correctCount/questions.length;
+  let grade,blurb;
+  if(pct>=.92){grade='Master Curator';blurb='Extraordinary. Your eye for design history, pattern, and material is at the level of a true tastemaker. The trade knows your name.'}
+  else if(pct>=.75){grade='Connoisseur';blurb='Impressive depth of knowledge. You read rooms and references with confidence — wallcoverings hold no mystery for you.'}
+  else if(pct>=.5){grade='Design Devotee';blurb='A solid foundation. You know your damask from your toile and your Art Deco from your Bauhaus — keep studying the archive.'}
+  else{grade='Inspired Novice';blurb='Every expert begins here. The world of pattern, motif, and material is rich — and now you\'ve had your first proper introduction.'}
+
+  document.getElementById('gradeTitle').textContent=grade;
+  document.getElementById('finalScore').textContent=score.toLocaleString();
+  document.getElementById('statCorrect').textContent=`${correctCount}/${questions.length}`;
+  document.getElementById('statStreak').textContent=bestStreak;
+  const avgTime=correctCount>0?(totalTime/questions.length).toFixed(1)+'s':'—';
+  document.getElementById('statTime').textContent=avgTime;
+  document.getElementById('gradeBlurb').textContent=blurb;
+  document.getElementById('newBestBadge').style.display=isNewBest?'inline-block':'none';
+
+  sndWin();
+  showScreen('endScreen');
+  document.getElementById('scoreBadge').style.display='none';
+}
+
+// ─── SCREEN NAV ──────────────────────────────────────────────────────────────
+function showScreen(id){
+  document.querySelectorAll('.screen').forEach(s=>{s.classList.remove('active')});
+  document.getElementById(id).classList.add('active');
+}
+
+// ─── START / AGAIN ────────────────────────────────────────────────────────────
+document.getElementById('startBtn').addEventListener('click',startGame);
+document.getElementById('playAgainBtn').addEventListener('click',startGame);
+
+// load best score on start screen
+(function(){
+  const best=localStorage.getItem('dw-iq-best');
+  if(best&&parseInt(best)>0){
+    document.getElementById('bestNum').textContent=parseInt(best).toLocaleString();
+    document.getElementById('bestBadge').style.display='block';
+  }
+})();
+
+// ─── KEYBOARD ────────────────────────────────────────────────────────────────
+document.addEventListener('keydown',e=>{
+  if(['1','2','3','4'].includes(e.key)){
+    const idx=parseInt(e.key)-1;
+    const btns=document.querySelectorAll('.ans-btn');
+    if(btns[idx]&&!btns[idx].disabled)btns[idx].click();
+  }
+  if((e.key==='Enter'||e.key===' ')&&document.getElementById('nextBtn').style.display!=='none'){
+    document.getElementById('nextBtn').click();
+  }
+});
+
+// overlay dismiss
+document.getElementById('overlayBtn').addEventListener('click',()=>{
+  document.getElementById('overlay').classList.remove('visible');
+});
+</script>
+</body>
+</html>
diff --git a/public/games/dw-reel-storyboard.html b/public/games/dw-reel-storyboard.html
new file mode 100644
index 0000000..bd4f9b1
--- /dev/null
+++ b/public/games/dw-reel-storyboard.html
@@ -0,0 +1,948 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Reel Storyboard Builder — Designer Wallcoverings</title>
+<style>
+/* ── TOKENS ─────────────────────────────────────────────────────── */
+:root{
+  --bg:#0d0b09; --bg2:#1a1713; --bg3:#231f1b; --bg4:#2e2924;
+  --ink:#f4f1ea; --ink2:#c8bfa8; --ink3:#7a6e5f;
+  --accent:#c9a961; --accent2:#a8843d; --accent3:#e8d4a0;
+  --serif:'Cormorant Garamond','Playfair Display','EB Garamond',Georgia,serif;
+  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
+  --ease:cubic-bezier(.4,0,.2,1);
+  --ease-spring:cubic-bezier(.34,1.56,.64,1);
+  --r:8px; --r2:14px;
+  --frame-w:270px; --frame-h:480px;
+  --strip-h:80px;
+}
+*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+html{scroll-behavior:smooth}
+body{
+  background:var(--bg);color:var(--ink);font-family:var(--sans);
+  min-height:100vh;overflow-x:hidden;
+  display:grid;grid-template-rows:auto 1fr;
+}
+
+/* ── HEADER ─────────────────────────────────────────────────────── */
+header{
+  padding:18px 28px 14px;
+  border-bottom:1px solid rgba(201,169,97,.18);
+  display:flex;align-items:center;gap:20px;
+  background:var(--bg);
+  position:sticky;top:0;z-index:99;
+}
+.wordmark{
+  font-family:var(--serif);font-size:clamp(26px,3vw,42px);
+  font-weight:600;letter-spacing:.18em;text-transform:uppercase;
+  color:var(--ink);line-height:1;white-space:nowrap;
+}
+.wordmark span{color:var(--accent)}
+.header-sub{
+  font-size:11px;letter-spacing:.12em;text-transform:uppercase;
+  color:var(--ink3);padding-top:4px;
+}
+.header-badge{
+  margin-left:auto;
+  background:linear-gradient(135deg,var(--accent2),var(--accent));
+  color:#0d0b09;font-size:10px;font-weight:700;letter-spacing:.12em;
+  text-transform:uppercase;padding:5px 12px;border-radius:20px;
+}
+
+/* ── MAIN LAYOUT ─────────────────────────────────────────────────── */
+main{
+  display:grid;
+  grid-template-columns:var(--frame-w) 1fr;
+  grid-template-rows:auto auto;
+  gap:24px;padding:24px 28px 40px;
+  align-items:start;
+}
+@media(max-width:820px){
+  main{grid-template-columns:1fr;justify-items:center}
+  .right-panel{width:100%;max-width:600px}
+}
+
+/* ── PHONE FRAME ─────────────────────────────────────────────────── */
+.phone-wrap{
+  grid-row:1/3;
+  display:flex;flex-direction:column;align-items:center;gap:16px;
+  position:sticky;top:90px;
+}
+.phone{
+  width:var(--frame-w);height:var(--frame-h);
+  border-radius:32px;
+  border:3px solid rgba(201,169,97,.35);
+  box-shadow:0 0 0 1px rgba(0,0,0,.8),0 24px 80px rgba(0,0,0,.7),
+             inset 0 0 0 1px rgba(255,255,255,.04);
+  background:#000;
+  position:relative;overflow:hidden;
+}
+.phone-notch{
+  position:absolute;top:0;left:50%;transform:translateX(-50%);
+  width:90px;height:26px;background:#000;border-radius:0 0 16px 16px;z-index:20;
+}
+/* IG UI chrome */
+.phone-chrome{
+  position:absolute;top:30px;left:0;right:0;z-index:15;
+  padding:8px 14px;
+  display:flex;align-items:center;justify-content:space-between;
+}
+.chrome-avatar{
+  width:28px;height:28px;border-radius:50%;
+  border:1.5px solid var(--accent);
+  background:linear-gradient(135deg,#c9a961,#7a4f1a);
+  display:flex;align-items:center;justify-content:center;
+  font-size:10px;font-weight:700;color:#fff;
+}
+.chrome-user{font-size:11px;font-weight:700;color:#fff;margin-left:7px;flex:1;text-shadow:0 1px 4px rgba(0,0,0,.8)}
+.chrome-follow{
+  font-size:11px;font-weight:700;color:#fff;
+  background:rgba(255,255,255,.18);border-radius:4px;padding:3px 10px;
+}
+/* Progress bars */
+.progress-bars{
+  position:absolute;top:8px;left:10px;right:10px;z-index:16;
+  display:flex;gap:3px;
+}
+.progress-seg{
+  flex:1;height:2px;border-radius:1px;
+  background:rgba(255,255,255,.25);overflow:hidden;
+}
+.progress-fill{
+  height:100%;background:#fff;width:0%;
+  transition:width 50ms linear;
+}
+.progress-seg.done .progress-fill{width:100%}
+
+/* ── SCENES (canvas) ─────────────────────────────────────────────── */
+.scenes-canvas{
+  position:absolute;inset:0;
+}
+.scene{
+  position:absolute;inset:0;
+  opacity:0;
+  transition:opacity .45s var(--ease);
+  display:flex;align-items:flex-end;
+}
+.scene.active{opacity:1}
+.scene.exiting{opacity:0}
+
+/* Scene SVG bg */
+.scene-bg{
+  position:absolute;inset:0;width:100%;height:100%;
+}
+
+/* Caption overlay */
+.caption-overlay{
+  position:absolute;
+  bottom:0;left:0;right:0;
+  padding:50px 16px 64px;
+  background:linear-gradient(to top,rgba(0,0,0,.82) 0%,rgba(0,0,0,.3) 70%,transparent 100%);
+  z-index:10;
+}
+.caption-label{
+  font-size:9px;letter-spacing:.18em;text-transform:uppercase;
+  color:var(--accent);margin-bottom:6px;font-family:var(--sans);
+}
+.caption-text{
+  font-family:var(--serif);font-size:15px;line-height:1.45;
+  color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6);
+  animation:fadeSlide .5s var(--ease) both;
+}
+@keyframes fadeSlide{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+.scene-type-tag{
+  display:inline-block;margin-bottom:8px;
+  font-size:9px;letter-spacing:.14em;text-transform:uppercase;
+  background:var(--accent);color:#0d0b09;
+  padding:2px 8px;border-radius:3px;font-weight:700;
+}
+/* bottom IG actions */
+.phone-actions{
+  position:absolute;bottom:16px;right:12px;z-index:16;
+  display:flex;flex-direction:column;align-items:center;gap:14px;
+}
+.action-icon{font-size:22px;text-shadow:0 1px 4px rgba(0,0,0,.7);cursor:default;}
+.action-count{font-size:10px;color:#fff;text-align:center;text-shadow:0 1px 3px rgba(0,0,0,.7)}
+
+/* ── PLAYBACK CONTROLS ───────────────────────────────────────────── */
+.playback-row{
+  display:flex;align-items:center;gap:10px;
+  width:var(--frame-w);
+}
+.btn-play{
+  width:44px;height:44px;border-radius:50%;
+  background:linear-gradient(135deg,var(--accent2),var(--accent));
+  border:none;cursor:pointer;
+  display:flex;align-items:center;justify-content:center;
+  font-size:17px;color:#0d0b09;
+  transition:transform .15s var(--ease-spring),box-shadow .15s;
+  flex-shrink:0;
+}
+.btn-play:hover{transform:scale(1.08);box-shadow:0 4px 20px rgba(201,169,97,.45)}
+.mood-select{
+  flex:1;background:var(--bg3);border:1px solid rgba(201,169,97,.22);
+  color:var(--ink2);font-family:var(--sans);font-size:12px;
+  padding:6px 10px;border-radius:var(--r);cursor:pointer;
+  appearance:none;
+  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23c9a961'/%3E%3C/svg%3E");
+  background-repeat:no-repeat;background-position:right 10px center;
+  padding-right:28px;
+}
+.mood-select:focus{outline:1px solid var(--accent)}
+
+/* ── STRIP / TIMELINE ────────────────────────────────────────────── */
+.strip-wrap{
+  width:var(--frame-w);
+}
+.strip-label{
+  font-size:10px;letter-spacing:.14em;text-transform:uppercase;
+  color:var(--ink3);margin-bottom:8px;
+}
+.strip{
+  display:flex;gap:6px;overflow-x:auto;padding-bottom:4px;
+}
+.strip::-webkit-scrollbar{height:3px}
+.strip::-webkit-scrollbar-track{background:var(--bg2)}
+.strip::-webkit-scrollbar-thumb{background:var(--accent2);border-radius:2px}
+.thumb{
+  flex-shrink:0;
+  width:52px;height:52px;
+  border-radius:8px;overflow:hidden;
+  cursor:pointer;position:relative;
+  border:2px solid transparent;
+  transition:border-color .2s,transform .15s var(--ease-spring);
+}
+.thumb.active{border-color:var(--accent);transform:scale(1.08)}
+.thumb svg{width:100%;height:100%;display:block}
+.thumb-num{
+  position:absolute;top:2px;left:4px;
+  font-size:9px;font-weight:700;color:#fff;
+  text-shadow:0 1px 3px rgba(0,0,0,.9);
+}
+
+/* ── RIGHT PANEL ─────────────────────────────────────────────────── */
+.right-panel{
+  display:flex;flex-direction:column;gap:20px;
+}
+.panel-title{
+  font-family:var(--serif);font-size:22px;letter-spacing:.06em;
+  color:var(--ink);margin-bottom:2px;
+}
+.panel-sub{font-size:12px;color:var(--ink3);letter-spacing:.04em}
+
+/* Scene editor cards */
+.scene-cards{display:flex;flex-direction:column;gap:10px}
+.scene-card{
+  background:var(--bg2);border:1px solid rgba(255,255,255,.05);
+  border-radius:var(--r2);padding:14px 16px;
+  cursor:pointer;
+  transition:border-color .2s,box-shadow .2s,background .2s;
+  position:relative;
+}
+.scene-card:hover{border-color:rgba(201,169,97,.3);background:var(--bg3)}
+.scene-card.selected{
+  border-color:var(--accent);
+  box-shadow:0 0 0 1px var(--accent2);
+  background:var(--bg3);
+}
+.card-header{
+  display:flex;align-items:center;gap:10px;margin-bottom:10px;
+}
+.card-num{
+  width:22px;height:22px;border-radius:50%;
+  background:var(--bg4);
+  font-size:11px;font-weight:700;color:var(--accent);
+  display:flex;align-items:center;justify-content:center;flex-shrink:0;
+}
+.scene-card.selected .card-num{background:var(--accent);color:#0d0b09}
+.card-type-badge{
+  font-size:10px;letter-spacing:.12em;text-transform:uppercase;
+  color:var(--accent3);background:rgba(201,169,97,.1);
+  padding:2px 8px;border-radius:20px;
+}
+.card-controls{margin-left:auto;display:flex;gap:6px}
+.card-btn{
+  background:var(--bg4);border:1px solid rgba(255,255,255,.08);
+  color:var(--ink3);font-size:11px;
+  width:24px;height:24px;border-radius:4px;cursor:pointer;
+  display:flex;align-items:center;justify-content:center;
+  transition:color .15s,border-color .15s;
+}
+.card-btn:hover{color:var(--accent);border-color:var(--accent2)}
+.card-body{display:flex;gap:12px;align-items:flex-start}
+.card-thumb-mini{
+  width:40px;height:40px;flex-shrink:0;
+  border-radius:6px;overflow:hidden;border:1px solid rgba(255,255,255,.08);
+}
+.card-thumb-mini svg{width:100%;height:100%;display:block}
+.card-fields{flex:1;display:flex;flex-direction:column;gap:8px}
+.field-row{display:flex;gap:8px;align-items:center}
+.field-label{
+  font-size:10px;letter-spacing:.1em;text-transform:uppercase;
+  color:var(--ink3);width:56px;flex-shrink:0;
+}
+.field-input{
+  flex:1;background:var(--bg);border:1px solid rgba(255,255,255,.08);
+  color:var(--ink);font-family:var(--sans);font-size:12px;
+  padding:5px 8px;border-radius:5px;resize:none;
+  transition:border-color .15s;
+}
+.field-input:focus{outline:none;border-color:var(--accent)}
+.field-select{
+  flex:1;background:var(--bg);border:1px solid rgba(255,255,255,.08);
+  color:var(--ink2);font-family:var(--sans);font-size:12px;
+  padding:5px 8px;border-radius:5px;cursor:pointer;appearance:none;
+  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23c9a961'/%3E%3C/svg%3E");
+  background-repeat:no-repeat;background-position:right 8px center;padding-right:22px;
+}
+.field-select:focus{outline:1px solid var(--accent)}
+.dur-input{
+  width:52px;background:var(--bg);border:1px solid rgba(255,255,255,.08);
+  color:var(--ink);font-size:12px;padding:5px 8px;border-radius:5px;text-align:center;
+}
+.dur-input:focus{outline:1px solid var(--accent)}
+
+/* Copy button row */
+.copy-row{
+  display:flex;gap:10px;align-items:center;flex-wrap:wrap;
+  background:var(--bg2);border-radius:var(--r2);padding:14px 16px;
+  border:1px solid rgba(255,255,255,.05);
+}
+.btn-copy{
+  background:linear-gradient(135deg,var(--accent2),var(--accent));
+  color:#0d0b09;font-size:12px;font-weight:700;letter-spacing:.08em;
+  border:none;padding:9px 18px;border-radius:var(--r);cursor:pointer;
+  transition:transform .15s var(--ease-spring),opacity .15s;
+}
+.btn-copy:hover{transform:scale(1.04);opacity:.92}
+.copy-hint{font-size:11px;color:var(--ink3)}
+
+/* ── TOAST ───────────────────────────────────────────────────────── */
+#toast{
+  position:fixed;bottom:32px;left:50%;transform:translateX(-50%) translateY(20px);
+  background:var(--accent);color:#0d0b09;
+  font-size:13px;font-weight:700;letter-spacing:.06em;
+  padding:10px 24px;border-radius:24px;
+  opacity:0;pointer-events:none;z-index:999;
+  transition:opacity .25s,transform .25s var(--ease-spring);
+}
+#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
+
+/* ── DRAG GHOST ──────────────────────────────────────────────────── */
+.dragging{opacity:.35;border-style:dashed!important}
+.drag-over{border-color:var(--accent)!important;background:var(--bg4)!important}
+</style>
+</head>
+<body>
+
+<header>
+  <div>
+    <div class="wordmark">Designer <span>Wallcoverings</span></div>
+    <div class="header-sub">Reel Storyboard Builder</div>
+  </div>
+  <div class="header-badge">Social Tool</div>
+</header>
+
+<main>
+  <!-- LEFT: phone + strip -->
+  <div class="phone-wrap">
+    <div class="phone" id="phone">
+      <div class="phone-notch"></div>
+      <div class="progress-bars" id="progressBars"></div>
+      <div class="phone-chrome">
+        <div class="chrome-avatar">DW</div>
+        <span class="chrome-user">designerwallcoverings</span>
+        <span class="chrome-follow">Follow</span>
+      </div>
+      <div class="scenes-canvas" id="scenesCanvas"></div>
+      <div class="phone-actions">
+        <div><div class="action-icon">♡</div><div class="action-count">4.2k</div></div>
+        <div><div class="action-icon">💬</div><div class="action-count">87</div></div>
+        <div><div class="action-icon">➤</div><div class="action-count">312</div></div>
+        <div><div class="action-icon">⋯</div></div>
+      </div>
+    </div>
+
+    <div class="playback-row">
+      <button class="btn-play" id="btnPlay" title="Play / Pause">▶</button>
+      <select class="mood-select" id="moodSelect">
+        <option value="sophisticated">♩ Sophisticated &amp; Slow</option>
+        <option value="uplifting">♩ Warm &amp; Uplifting</option>
+        <option value="editorial">♩ Editorial / Cinematic</option>
+        <option value="energetic">♩ Energetic &amp; Bold</option>
+        <option value="ambient">♩ Ambient / Textural</option>
+      </select>
+    </div>
+
+    <div class="strip-wrap">
+      <div class="strip-label">Timeline</div>
+      <div class="strip" id="strip"></div>
+    </div>
+  </div>
+
+  <!-- RIGHT: scene editor -->
+  <div class="right-panel">
+    <div>
+      <div class="panel-title">Storyboard</div>
+      <div class="panel-sub">Edit scenes, drag to reorder, then copy captions</div>
+    </div>
+    <div class="scene-cards" id="sceneCards"></div>
+    <div class="copy-row">
+      <button class="btn-copy" id="btnCopy">Copy All Captions + Hashtags</button>
+      <span class="copy-hint" id="copyHint">Copies all scene captions as one post draft</span>
+    </div>
+  </div>
+</main>
+
+<div id="toast"></div>
+
+<script>
+/* ═══════════════════════════════════════════════════════════════════
+   DATA
+══════════════════════════════════════════════════════════════════ */
+const TYPE_PRESETS={
+  hook:{label:'Hook',color:'#c9a961',defaultCaption:'Stop scrolling. This is the texture your room has been missing.'},
+  reveal:{label:'Room Reveal',color:'#9b7fa0',defaultCaption:'A complete transformation. Same walls, new identity.'},
+  closeup:{label:'Close-up Texture',color:'#7aa0a0',defaultCaption:'Every detail is intentional. Feel the depth.'},
+  swatchgrid:{label:'Swatch Grid',color:'#a07a7a',defaultCaption:'Four finishes. One vision. Which speaks to you?'},
+  cta:{label:'CTA',color:'#a0a07a',defaultCaption:'Shop the link in bio. Free swatches available.'},
+};
+
+let scenes=[
+  {id:1,type:'hook',caption:'Stop scrolling. This is the texture your room has been missing.',dur:3},
+  {id:2,type:'reveal',caption:'A complete transformation — one wall, infinite presence.',dur:4},
+  {id:3,type:'closeup',caption:'Hand-woven grasscloth. The imperfection is the point.',dur:3},
+  {id:4,type:'swatchgrid',caption:'Four colorways. One obsession.',dur:3},
+  {id:5,type:'cta',caption:'Free sample swatches → link in bio. Let the wall do the talking.',dur:4},
+];
+
+let playState={active:false,scene:0,elapsed:0,raf:null,lastTs:null};
+let selectedCard=0;
+let dragSrc=null;
+
+/* ═══════════════════════════════════════════════════════════════════
+   INLINE SVG SCENE RENDERERS
+══════════════════════════════════════════════════════════════════ */
+function svgHook(){
+  return `<svg viewBox="0 0 270 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
+  <defs>
+    <linearGradient id="hg1" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#1a0e05"/>
+      <stop offset="100%" stop-color="#0d1a14"/>
+    </linearGradient>
+    <pattern id="hw" x="0" y="0" width="18" height="18" patternUnits="userSpaceOnUse">
+      <line x1="0" y1="0" x2="18" y2="18" stroke="#c9a961" stroke-width=".3" opacity=".18"/>
+      <line x1="18" y1="0" x2="0" y2="18" stroke="#c9a961" stroke-width=".2" opacity=".1"/>
+    </pattern>
+    <radialGradient id="hspot" cx="50%" cy="38%" r="55%">
+      <stop offset="0%" stop-color="#c9a961" stop-opacity=".22"/>
+      <stop offset="100%" stop-color="#c9a961" stop-opacity="0"/>
+    </radialGradient>
+  </defs>
+  <rect width="270" height="480" fill="url(#hg1)"/>
+  <rect width="270" height="480" fill="url(#hw)"/>
+  <rect width="270" height="480" fill="url(#hspot)"/>
+  <!-- dramatic vertical line accents -->
+  <line x1="67" y1="0" x2="67" y2="480" stroke="#c9a961" stroke-width=".4" opacity=".25"/>
+  <line x1="203" y1="0" x2="203" y2="480" stroke="#c9a961" stroke-width=".4" opacity=".25"/>
+  <!-- center ellipse glow -->
+  <ellipse cx="135" cy="200" rx="70" ry="90" fill="none" stroke="#c9a961" stroke-width=".6" opacity=".3"/>
+  <ellipse cx="135" cy="200" rx="50" ry="65" fill="none" stroke="#c9a961" stroke-width=".4" opacity=".2"/>
+  <!-- DW monogram -->
+  <text x="135" y="215" text-anchor="middle" font-size="38" font-family="Georgia,serif" fill="#c9a961" opacity=".55" letter-spacing="6">DW</text>
+  <text x="135" y="236" text-anchor="middle" font-size="8" font-family="'Arial',sans-serif" fill="#c9a961" opacity=".4" letter-spacing="5" text-transform="uppercase">DESIGNER WALLCOVERINGS</text>
+</svg>`;
+}
+
+function svgReveal(){
+  return `<svg viewBox="0 0 270 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
+  <defs>
+    <linearGradient id="rg1" x1="0" y1="0" x2="0" y2="1">
+      <stop offset="0%" stop-color="#1a1208"/>
+      <stop offset="55%" stop-color="#2a1f0e"/>
+      <stop offset="100%" stop-color="#0d0b09"/>
+    </linearGradient>
+    <linearGradient id="wallg" x1="0" y1="0" x2="1" y2="0">
+      <stop offset="0%" stop-color="#3d2a0f"/>
+      <stop offset="100%" stop-color="#5c3e1a"/>
+    </linearGradient>
+    <pattern id="damask" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
+      <rect width="40" height="40" fill="none"/>
+      <ellipse cx="20" cy="20" rx="8" ry="12" fill="none" stroke="#c9a961" stroke-width=".5" opacity=".25"/>
+      <ellipse cx="20" cy="20" rx="4" ry="7" fill="none" stroke="#c9a961" stroke-width=".4" opacity=".18"/>
+      <ellipse cx="20" cy="4" rx="3" ry="4" fill="#c9a961" fill-opacity=".1"/>
+      <ellipse cx="20" cy="36" rx="3" ry="4" fill="#c9a961" fill-opacity=".1"/>
+      <ellipse cx="4" cy="20" rx="4" ry="3" fill="#c9a961" fill-opacity=".1"/>
+      <ellipse cx="36" cy="20" rx="4" ry="3" fill="#c9a961" fill-opacity=".1"/>
+    </pattern>
+    <linearGradient id="floorlg" x1="0" y1="0" x2="0" y2="1">
+      <stop offset="0%" stop-color="#1a1208"/>
+      <stop offset="100%" stop-color="#0d0b09"/>
+    </linearGradient>
+  </defs>
+  <rect width="270" height="480" fill="url(#rg1)"/>
+  <!-- back wall -->
+  <rect x="20" y="60" width="230" height="260" rx="2" fill="url(#wallg)"/>
+  <rect x="20" y="60" width="230" height="260" rx="2" fill="url(#damask)"/>
+  <!-- baseboard -->
+  <rect x="20" y="316" width="230" height="6" fill="#7a5c28" opacity=".8"/>
+  <!-- floor -->
+  <rect x="0" y="322" width="270" height="158" fill="url(#floorlg)"/>
+  <!-- floor planks -->
+  <g opacity=".35" stroke="#c9a961" stroke-width=".4">
+    <line x1="0" y1="340" x2="270" y2="360"/><line x1="0" y1="380" x2="270" y2="400"/>
+    <line x1="0" y1="420" x2="270" y2="440"/><line x1="90" y1="322" x2="70" y2="480"/>
+    <line x1="180" y1="322" x2="160" y2="480"/>
+  </g>
+  <!-- sofa silhouette -->
+  <rect x="45" y="290" width="180" height="45" rx="6" fill="#1a1208"/>
+  <rect x="40" y="278" width="190" height="24" rx="8" fill="#231a0a"/>
+  <rect x="40" y="298" width="12" height="38" rx="4" fill="#1a1208"/>
+  <rect x="218" y="298" width="12" height="38" rx="4" fill="#1a1208"/>
+  <!-- cushions -->
+  <rect x="52" y="280" width="56" height="20" rx="4" fill="#2e2214" stroke="#c9a961" stroke-width=".4" stroke-opacity=".3"/>
+  <rect x="114" y="280" width="56" height="20" rx="4" fill="#2e2214" stroke="#c9a961" stroke-width=".4" stroke-opacity=".3"/>
+  <rect x="176" y="280" width="42" height="20" rx="4" fill="#2e2214" stroke="#c9a961" stroke-width=".4" stroke-opacity=".3"/>
+  <!-- side table + vase -->
+  <rect x="210" y="260" width="30" height="30" rx="2" fill="#15100a"/>
+  <ellipse cx="225" cy="260" rx="8" ry="3" fill="#3d2a0f"/>
+  <rect x="221" y="238" width="8" height="22" rx="4" fill="#4a3318"/>
+  <ellipse cx="225" cy="236" rx="10" ry="4" fill="#5c3e1a" opacity=".8"/>
+  <!-- frame on wall -->
+  <rect x="95" y="100" width="80" height="100" rx="2" stroke="#c9a961" stroke-width="1.5" fill="none" opacity=".5"/>
+  <rect x="100" y="105" width="70" height="90" rx="1" fill="#1a1208" fill-opacity=".6"/>
+  <text x="135" y="157" text-anchor="middle" font-size="20" fill="#c9a961" opacity=".3" font-family="Georgia">DW</text>
+  <!-- window light -->
+  <rect x="30" y="80" width="50" height="70" rx="2" fill="#e8d4a0" fill-opacity=".06" stroke="#c9a961" stroke-width=".5" stroke-opacity=".3"/>
+</svg>`;
+}
+
+function svgCloseup(){
+  return `<svg viewBox="0 0 270 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
+  <defs>
+    <linearGradient id="cg1" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#1a1510"/>
+      <stop offset="100%" stop-color="#0d1510"/>
+    </linearGradient>
+    <!-- Grasscloth weave -->
+    <pattern id="weave" x="0" y="0" width="8" height="12" patternUnits="userSpaceOnUse">
+      <rect width="8" height="12" fill="#2a2018"/>
+      <rect x="0" y="0" width="4" height="6" fill="#352a1c" rx=".5"/>
+      <rect x="4" y="6" width="4" height="6" fill="#352a1c" rx=".5"/>
+      <rect x="0" y="0" width="8" height=".5" fill="#c9a961" fill-opacity=".12"/>
+      <rect x="0" y="6" width="8" height=".5" fill="#c9a961" fill-opacity=".1"/>
+      <rect x="0" y="12" width="8" height=".5" fill="#c9a961" fill-opacity=".08"/>
+      <rect x="0" y="0" width=".5" height="6" fill="#e8d4a0" fill-opacity=".1"/>
+      <rect x="4" y="6" width=".5" height="6" fill="#e8d4a0" fill-opacity=".1"/>
+    </pattern>
+    <radialGradient id="cspot" cx="40%" cy="35%" r="50%">
+      <stop offset="0%" stop-color="#e8d4a0" stop-opacity=".35"/>
+      <stop offset="100%" stop-color="#e8d4a0" stop-opacity="0"/>
+    </radialGradient>
+    <linearGradient id="cvig" x1="0" y1="0" x2="0" y2="1">
+      <stop offset="0%" stop-color="#000" stop-opacity=".5"/>
+      <stop offset="30%" stop-color="#000" stop-opacity="0"/>
+      <stop offset="70%" stop-color="#000" stop-opacity="0"/>
+      <stop offset="100%" stop-color="#000" stop-opacity=".7"/>
+    </linearGradient>
+  </defs>
+  <rect width="270" height="480" fill="url(#cg1)"/>
+  <rect width="270" height="480" fill="url(#weave)"/>
+  <rect width="270" height="480" fill="url(#cspot)"/>
+  <rect width="270" height="480" fill="url(#cvig)"/>
+  <!-- fiber highlight strands -->
+  <g opacity=".18" stroke="#e8d4a0" stroke-width=".6">
+    <line x1="0" y1="42" x2="270" y2="48"/><line x1="0" y1="90" x2="270" y2="96"/>
+    <line x1="0" y1="138" x2="270" y2="143"/><line x1="0" y1="186" x2="270" y2="191"/>
+    <line x1="0" y1="234" x2="270" y2="239"/><line x1="0" y1="282" x2="270" y2="287"/>
+    <line x1="0" y1="330" x2="270" y2="335"/><line x1="0" y1="378" x2="270" y2="383"/>
+    <line x1="0" y1="426" x2="270" y2="431"/>
+  </g>
+  <!-- vertical warps -->
+  <g opacity=".1" stroke="#c9a961" stroke-width=".4">
+    <line x1="33" y1="0" x2="33" y2="480"/><line x1="99" y1="0" x2="99" y2="480"/>
+    <line x1="165" y1="0" x2="165" y2="480"/><line x1="231" y1="0" x2="231" y2="480"/>
+  </g>
+  <!-- scale indicator -->
+  <line x1="30" y1="52" x2="90" y2="52" stroke="#c9a961" stroke-width=".8" opacity=".6"/>
+  <line x1="30" y1="48" x2="30" y2="56" stroke="#c9a961" stroke-width=".8" opacity=".6"/>
+  <line x1="90" y1="48" x2="90" y2="56" stroke="#c9a961" stroke-width=".8" opacity=".6"/>
+  <text x="60" y="68" text-anchor="middle" font-size="7" fill="#c9a961" opacity=".7" font-family="Arial,sans-serif" letter-spacing="1">2 cm</text>
+</svg>`;
+}
+
+function svgSwatchGrid(){
+  return `<svg viewBox="0 0 270 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
+  <defs>
+    <linearGradient id="sg1" x1="0" y1="0" x2="0" y2="1">
+      <stop offset="0%" stop-color="#141010"/><stop offset="100%" stop-color="#0d0b09"/>
+    </linearGradient>
+    <!-- swatch textures as patterns -->
+    <pattern id="sw1p" x="0" y="0" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#3d2a0f"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#c9a961" stroke-width=".3" opacity=".3"/>
+    </pattern>
+    <pattern id="sw2p" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#1a2a2a"/>
+      <rect x="0" y="0" width="4" height="4" fill="#1f3030"/>
+      <rect x="4" y="4" width="4" height="4" fill="#1f3030"/>
+    </pattern>
+    <pattern id="sw3p" x="0" y="0" width="5" height="10" patternUnits="userSpaceOnUse">
+      <rect width="5" height="10" fill="#2a1f2a"/>
+      <rect x="0" y="0" width="5" height="5" fill="#352835"/>
+      <line x1="0" y1="0" x2="5" y2="5" stroke="#9b7fa0" stroke-width=".3" opacity=".3"/>
+    </pattern>
+    <pattern id="sw4p" x="0" y="0" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#1c1c14"/>
+      <ellipse cx="3" cy="3" rx="2" ry="2" fill="none" stroke="#a0a07a" stroke-width=".4" opacity=".3"/>
+    </pattern>
+  </defs>
+  <rect width="270" height="480" fill="url(#sg1)"/>
+  <!-- label top -->
+  <text x="135" y="52" text-anchor="middle" font-size="8" fill="#c9a961" opacity=".7" font-family="Arial,sans-serif" letter-spacing="4">COLORWAYS</text>
+  <!-- 2x2 swatch grid -->
+  <!-- SW1: Antique Gold -->
+  <rect x="22" y="65" width="108" height="160" rx="8" fill="url(#sw1p)"/>
+  <rect x="22" y="65" width="108" height="160" rx="8" fill="none" stroke="#c9a961" stroke-width=".5" opacity=".4"/>
+  <text x="76" y="198" text-anchor="middle" font-size="8" fill="#c9a961" opacity=".8" font-family="Arial,sans-serif">Antique Gold</text>
+  <!-- SW2: Deep Teal -->
+  <rect x="140" y="65" width="108" height="160" rx="8" fill="url(#sw2p)"/>
+  <rect x="140" y="65" width="108" height="160" rx="8" fill="none" stroke="#7aa0a0" stroke-width=".5" opacity=".4"/>
+  <text x="194" y="198" text-anchor="middle" font-size="8" fill="#7aa0a0" opacity=".8" font-family="Arial,sans-serif">Deep Teal</text>
+  <!-- SW3: Mauve Dusk -->
+  <rect x="22" y="238" width="108" height="160" rx="8" fill="url(#sw3p)"/>
+  <rect x="22" y="238" width="108" height="160" rx="8" fill="none" stroke="#9b7fa0" stroke-width=".5" opacity=".4"/>
+  <text x="76" y="370" text-anchor="middle" font-size="8" fill="#9b7fa0" opacity=".8" font-family="Arial,sans-serif">Mauve Dusk</text>
+  <!-- SW4: Sage Linen -->
+  <rect x="140" y="238" width="108" height="160" rx="8" fill="url(#sw4p)"/>
+  <rect x="140" y="238" width="108" height="160" rx="8" fill="none" stroke="#a0a07a" stroke-width=".5" opacity=".4"/>
+  <text x="194" y="370" text-anchor="middle" font-size="8" fill="#a0a07a" opacity=".8" font-family="Arial,sans-serif">Sage Linen</text>
+  <!-- corner dots -->
+  <circle cx="135" cy="213" r="3" fill="#c9a961" opacity=".5"/>
+</svg>`;
+}
+
+function svgCTA(){
+  return `<svg viewBox="0 0 270 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
+  <defs>
+    <radialGradient id="ctag" cx="50%" cy="45%" r="60%">
+      <stop offset="0%" stop-color="#2a1f0a"/>
+      <stop offset="100%" stop-color="#0d0b09"/>
+    </radialGradient>
+    <pattern id="ctap" x="0" y="0" width="30" height="30" patternUnits="userSpaceOnUse">
+      <rect width="30" height="30" fill="none"/>
+      <circle cx="15" cy="15" r="10" fill="none" stroke="#c9a961" stroke-width=".3" opacity=".12"/>
+      <circle cx="0" cy="0" r="5" fill="none" stroke="#c9a961" stroke-width=".25" opacity=".08"/>
+    </pattern>
+  </defs>
+  <rect width="270" height="480" fill="url(#ctag)"/>
+  <rect width="270" height="480" fill="url(#ctap)"/>
+  <!-- center composition -->
+  <rect x="40" y="120" width="190" height="240" rx="12" fill="none" stroke="#c9a961" stroke-width=".8" opacity=".3"/>
+  <rect x="52" y="132" width="166" height="216" rx="8" fill="none" stroke="#c9a961" stroke-width=".4" opacity=".18"/>
+  <!-- logo text -->
+  <text x="135" y="215" text-anchor="middle" font-size="12" fill="#c9a961" opacity=".9" font-family="Georgia,serif" letter-spacing="5">DESIGNER</text>
+  <text x="135" y="232" text-anchor="middle" font-size="12" fill="#c9a961" opacity=".9" font-family="Georgia,serif" letter-spacing="5">WALLCOVERINGS</text>
+  <!-- divider -->
+  <line x1="85" y1="245" x2="185" y2="245" stroke="#c9a961" stroke-width=".6" opacity=".5"/>
+  <!-- CTA pill -->
+  <rect x="78" y="262" width="114" height="30" rx="15" fill="#c9a961" fill-opacity=".15" stroke="#c9a961" stroke-width=".8" opacity=".7"/>
+  <text x="135" y="282" text-anchor="middle" font-size="10" fill="#c9a961" opacity=".9" font-family="Arial,sans-serif" letter-spacing="3">SHOP NOW</text>
+  <!-- decorative corners -->
+  <g stroke="#c9a961" stroke-width="1" opacity=".4">
+    <path d="M 50 140 L 50 128 L 62 128"/><path d="M 220 140 L 220 128 L 208 128"/>
+    <path d="M 50 340 L 50 352 L 62 352"/><path d="M 220 340 L 220 352 L 208 352"/>
+  </g>
+  <!-- small swatches at bottom -->
+  <g opacity=".55">
+    <rect x="75" y="310" width="22" height="22" rx="4" fill="#3d2a0f" stroke="#c9a961" stroke-width=".4"/>
+    <rect x="103" y="310" width="22" height="22" rx="4" fill="#1a2a2a" stroke="#7aa0a0" stroke-width=".4"/>
+    <rect x="131" y="310" width="22" height="22" rx="4" fill="#2a1f2a" stroke="#9b7fa0" stroke-width=".4"/>
+    <rect x="159" y="310" width="22" height="22" rx="4" fill="#1c1c14" stroke="#a0a07a" stroke-width=".4"/>
+  </g>
+</svg>`;
+}
+
+const SVG_MAP={hook:svgHook,reveal:svgReveal,closeup:svgCloseup,swatchgrid:svgSwatchGrid,cta:svgCTA};
+
+/* ═══════════════════════════════════════════════════════════════════
+   HASHTAGS
+══════════════════════════════════════════════════════════════════ */
+const HASHTAGS='#WallcoveringDesign #LuxuryInteriors #DesignerWallcoverings #InteriorDesign #WallDecor #TextureLovers #GrassclotheWall #WallpaperDesign #HomeDecor #ElevateYourSpace #WallArt #InteriorInspiration #LuxuryLiving #DesignDetails #WallpapersOfInstagram';
+
+/* ═══════════════════════════════════════════════════════════════════
+   RENDER PHONE SCENES
+══════════════════════════════════════════════════════════════════ */
+function buildPhoneScenes(){
+  const canvas=document.getElementById('scenesCanvas');
+  const bars=document.getElementById('progressBars');
+  canvas.innerHTML='';bars.innerHTML='';
+  scenes.forEach((sc,i)=>{
+    // scene div
+    const div=document.createElement('div');
+    div.className='scene'+(i===0?' active':'');
+    div.id='scene-'+sc.id;
+    // bg
+    const bg=document.createElement('div');
+    bg.className='scene-bg';
+    bg.innerHTML=SVG_MAP[sc.type]();
+    div.appendChild(bg);
+    // caption
+    const cap=document.createElement('div');
+    cap.className='caption-overlay';
+    cap.innerHTML=`<div class="scene-type-tag">${TYPE_PRESETS[sc.type].label}</div>
+      <div class="caption-label">Scene ${i+1} of ${scenes.length}</div>
+      <div class="caption-text" id="captext-${sc.id}">${esc(sc.caption)}</div>`;
+    div.appendChild(cap);
+    canvas.appendChild(div);
+    // progress bar segment
+    const seg=document.createElement('div');
+    seg.className='progress-seg'+(i===0?' active':'');
+    seg.id='seg-'+sc.id;
+    seg.innerHTML='<div class="progress-fill" id="fill-'+sc.id+'"></div>';
+    bars.appendChild(seg);
+  });
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+   RENDER STRIP
+══════════════════════════════════════════════════════════════════ */
+function buildStrip(){
+  const strip=document.getElementById('strip');
+  strip.innerHTML='';
+  scenes.forEach((sc,i)=>{
+    const th=document.createElement('div');
+    th.className='thumb'+(i===playState.scene?' active':'');
+    th.dataset.idx=i;
+    th.innerHTML=`<div style="transform:scale(0.54);transform-origin:top left;width:185%;height:185%;pointer-events:none">${SVG_MAP[sc.type]()}</div><span class="thumb-num">${i+1}</span>`;
+    th.addEventListener('click',()=>jumpToScene(i));
+    strip.appendChild(th);
+  });
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+   RENDER SCENE CARDS (right panel)
+══════════════════════════════════════════════════════════════════ */
+function buildCards(){
+  const container=document.getElementById('sceneCards');
+  container.innerHTML='';
+  scenes.forEach((sc,i)=>{
+    const card=document.createElement('div');
+    card.className='scene-card'+(i===selectedCard?' selected':'');
+    card.dataset.idx=i;
+    card.setAttribute('draggable','true');
+    card.innerHTML=`
+      <div class="card-header">
+        <div class="card-num">${i+1}</div>
+        <select class="field-select" data-field="type" style="font-size:11px;padding:3px 8px;flex:1" onchange="updateField(${i},'type',this.value)">
+          ${Object.entries(TYPE_PRESETS).map(([k,v])=>`<option value="${k}"${sc.type===k?' selected':''}>${v.label}</option>`).join('')}
+        </select>
+        <div class="card-controls">
+          <button class="card-btn" title="Move up" onclick="moveScene(${i},-1)">↑</button>
+          <button class="card-btn" title="Move down" onclick="moveScene(${i},1)">↓</button>
+        </div>
+      </div>
+      <div class="card-body">
+        <div class="card-thumb-mini">${SVG_MAP[sc.type]()}</div>
+        <div class="card-fields">
+          <div class="field-row">
+            <span class="field-label">Caption</span>
+            <textarea class="field-input" rows="2" oninput="updateField(${i},'caption',this.value)">${esc(sc.caption)}</textarea>
+          </div>
+          <div class="field-row">
+            <span class="field-label">Duration</span>
+            <input type="number" class="dur-input field-input" min="1" max="15" value="${sc.dur}" style="width:52px;flex:none" oninput="updateField(${i},'dur',+this.value)">
+            <span style="font-size:11px;color:var(--ink3);margin-left:4px">sec</span>
+          </div>
+        </div>
+      </div>`;
+    // click to select
+    card.addEventListener('click',e=>{
+      if(e.target.closest('button')||e.target.closest('select')||e.target.closest('textarea')||e.target.closest('input'))return;
+      selectedCard=i;jumpToScene(i);buildCards();
+    });
+    // drag
+    card.addEventListener('dragstart',e=>{dragSrc=i;card.classList.add('dragging');e.dataTransfer.effectAllowed='move'});
+    card.addEventListener('dragend',()=>{card.classList.remove('dragging');document.querySelectorAll('.scene-card').forEach(c=>c.classList.remove('drag-over'))});
+    card.addEventListener('dragover',e=>{e.preventDefault();card.classList.add('drag-over')});
+    card.addEventListener('dragleave',()=>card.classList.remove('drag-over'));
+    card.addEventListener('drop',e=>{
+      e.preventDefault();card.classList.remove('drag-over');
+      if(dragSrc===null||dragSrc===i)return;
+      const moved=scenes.splice(dragSrc,1)[0];
+      scenes.splice(i,0,moved);
+      selectedCard=i;dragSrc=null;
+      rebuildAll();
+    });
+    container.appendChild(card);
+  });
+}
+
+function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
+
+/* ═══════════════════════════════════════════════════════════════════
+   FIELD UPDATE
+══════════════════════════════════════════════════════════════════ */
+function updateField(idx,field,val){
+  scenes[idx][field]=val;
+  if(field==='caption'){
+    const el=document.getElementById('captext-'+scenes[idx].id);
+    if(el){el.innerHTML=esc(val);el.style.animation='none';requestAnimationFrame(()=>{el.style.animation=''})}
+  }
+  if(field==='type'){
+    // rebuild that scene's bg + card thumb
+    const scEl=document.getElementById('scene-'+scenes[idx].id);
+    if(scEl){
+      const bg=scEl.querySelector('.scene-bg');
+      if(bg)bg.innerHTML=SVG_MAP[val]();
+      const tag=scEl.querySelector('.scene-type-tag');
+      if(tag)tag.textContent=TYPE_PRESETS[val].label;
+    }
+    buildStrip();
+    buildCards();
+  }
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+   SCENE MOVEMENT
+══════════════════════════════════════════════════════════════════ */
+function moveScene(idx,dir){
+  const tgt=idx+dir;
+  if(tgt<0||tgt>=scenes.length)return;
+  [scenes[idx],scenes[tgt]]=[scenes[tgt],scenes[idx]];
+  selectedCard=tgt;
+  rebuildAll();
+}
+
+function rebuildAll(){
+  buildPhoneScenes();
+  buildStrip();
+  buildCards();
+  // re-apply active
+  updatePhoneActive(playState.scene);
+}
+
+function jumpToScene(idx){
+  playState.scene=idx;playState.elapsed=0;
+  updatePhoneActive(idx);
+  buildStrip();
+}
+
+function updatePhoneActive(idx){
+  document.querySelectorAll('.scene').forEach((el,i)=>{
+    el.classList.toggle('active',i===idx);
+  });
+  document.querySelectorAll('.progress-seg').forEach((el,i)=>{
+    el.classList.toggle('done',i<idx);
+    const fill=el.querySelector('.progress-fill');
+    if(fill)fill.style.width=i<idx?'100%':i===idx?'0%':'0%';
+  });
+  document.querySelectorAll('.thumb').forEach((el,i)=>{
+    el.classList.toggle('active',i===idx);
+  });
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+   PLAYBACK
+══════════════════════════════════════════════════════════════════ */
+function tick(ts){
+  if(!playState.active)return;
+  if(playState.lastTs===null)playState.lastTs=ts;
+  const dt=(ts-playState.lastTs)/1000;
+  playState.lastTs=ts;
+  const sc=scenes[playState.scene];
+  const dur=Math.max(1,sc?sc.dur:3);
+  playState.elapsed+=dt;
+  // update fill
+  const fill=document.getElementById('fill-'+sc.id);
+  if(fill)fill.style.width=Math.min(100,playState.elapsed/dur*100)+'%';
+  if(playState.elapsed>=dur){
+    const next=playState.scene+1;
+    if(next>=scenes.length){
+      // loop
+      playState.elapsed=0;playState.scene=0;playState.lastTs=ts;
+      updatePhoneActive(0);buildStrip();
+    } else {
+      playState.elapsed=0;playState.scene=next;playState.lastTs=ts;
+      updatePhoneActive(next);buildStrip();
+    }
+  }
+  playState.raf=requestAnimationFrame(tick);
+}
+
+document.getElementById('btnPlay').addEventListener('click',()=>{
+  playState.active=!playState.active;
+  document.getElementById('btnPlay').textContent=playState.active?'⏸':'▶';
+  if(playState.active){playState.lastTs=null;playState.raf=requestAnimationFrame(tick);}
+  else{if(playState.raf)cancelAnimationFrame(playState.raf);}
+});
+
+/* ═══════════════════════════════════════════════════════════════════
+   COPY
+══════════════════════════════════════════════════════════════════ */
+document.getElementById('btnCopy').addEventListener('click',()=>{
+  const mood=document.getElementById('moodSelect');
+  const moodLabel=mood.options[mood.selectedIndex].text;
+  let txt=`✨ Designer Wallcoverings — Instagram Reel\nMood: ${moodLabel}\n\n`;
+  scenes.forEach((sc,i)=>{
+    txt+=`[Scene ${i+1} — ${TYPE_PRESETS[sc.type].label} (${sc.dur}s)]\n${sc.caption}\n\n`;
+  });
+  txt+=HASHTAGS;
+  navigator.clipboard.writeText(txt).then(()=>showToast('Captions + hashtags copied!')).catch(()=>showToast('Copy failed — try selecting manually'));
+});
+
+function showToast(msg){
+  const t=document.getElementById('toast');
+  t.textContent=msg;t.classList.add('show');
+  clearTimeout(t._tid);t._tid=setTimeout(()=>t.classList.remove('show'),2600);
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+   KEYBOARD NAV
+══════════════════════════════════════════════════════════════════ */
+document.addEventListener('keydown',e=>{
+  if(e.target.tagName==='TEXTAREA'||e.target.tagName==='INPUT'||e.target.tagName==='SELECT')return;
+  if(e.key==='ArrowRight'||e.key==='ArrowDown'){
+    const n=Math.min(scenes.length-1,playState.scene+1);
+    jumpToScene(n);buildStrip();
+  }
+  if(e.key==='ArrowLeft'||e.key==='ArrowUp'){
+    const n=Math.max(0,playState.scene-1);
+    jumpToScene(n);buildStrip();
+  }
+  if(e.key===' '){
+    e.preventDefault();
+    document.getElementById('btnPlay').click();
+  }
+});
+
+/* ═══════════════════════════════════════════════════════════════════
+   TOUCH SWIPE ON PHONE
+══════════════════════════════════════════════════════════════════ */
+(()=>{
+  const phone=document.getElementById('phone');
+  let tx=null;
+  phone.addEventListener('touchstart',e=>{tx=e.touches[0].clientX},{passive:true});
+  phone.addEventListener('touchend',e=>{
+    if(tx===null)return;
+    const dx=e.changedTouches[0].clientX-tx;
+    if(Math.abs(dx)>40){
+      if(dx<0)jumpToScene(Math.min(scenes.length-1,playState.scene+1));
+      else jumpToScene(Math.max(0,playState.scene-1));
+      buildStrip();
+    }
+    tx=null;
+  },{passive:true});
+})();
+
+/* ═══════════════════════════════════════════════════════════════════
+   INIT
+══════════════════════════════════════════════════════════════════ */
+buildPhoneScenes();
+buildStrip();
+buildCards();
+</script>
+</body>
+</html>

← 2866307 Agent-driven tool: DW Shop by Color (frontend-developer) — i  ·  back to Model Arena  ·  Agent-driven trivia game: DW Design IQ (frontend-developer) e02303a →