[object Object]

← back to Model Arena

Agent-driven high-detail: DW Find Your Style quiz (token-safe rebuild) — 6 visual questions, 5 archetypes, shareable result card, inert modal, data: URIs. Registered.

193a9753cac5726172546128ea3f8f14ee84a189 · 2026-07-25 11:21:35 -0700 · Steve Abrams

Files touched

Diff

commit 193a9753cac5726172546128ea3f8f14ee84a189
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 11:21:35 2026 -0700

    Agent-driven high-detail: DW Find Your Style quiz (token-safe rebuild) — 6 visual questions, 5 archetypes, shareable result card, inert modal, data: URIs. Registered.
---
 data/arcade-games.json          |    6 +
 public/games/dw-style-quiz.html | 1329 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 1335 insertions(+)

diff --git a/data/arcade-games.json b/data/arcade-games.json
index 6cd9cb2..f6867c4 100644
--- a/data/arcade-games.json
+++ b/data/arcade-games.json
@@ -29,6 +29,12 @@
       "title": "DW Room Visualizer",
       "credit": "frontend-developer",
       "note": "Agent-built \u2014 perspectival room scene, swap 8 wallcoverings on the feature wall, day/night + scale + room types"
+    },
+    {
+      "slug": "dw-style-quiz",
+      "title": "DW Find Your Style",
+      "credit": "frontend-developer",
+      "note": "Agent-built \u2014 interactive interior-style quiz (6 visual questions \u2192 5 archetypes \u2192 shareable result card)"
     }
   ]
 }
\ No newline at end of file
diff --git a/public/games/dw-style-quiz.html b/public/games/dw-style-quiz.html
new file mode 100644
index 0000000..72d1485
--- /dev/null
+++ b/public/games/dw-style-quiz.html
@@ -0,0 +1,1329 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Find Your Style — Designer Wallcoverings</title>
+<style>
+:root{
+  --bg:#f4f1ea;--bg2:#eceae1;--ink:#1c1a17;--ink2:#4a4640;
+  --accent:#c9a961;--accent2:#8b6914;--white:#fdfcf8;
+  --serif:'Cormorant Garamond','Playfair Display','EB Garamond',Georgia,serif;
+  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
+  --ease:cubic-bezier(.4,0,.2,1);
+  --r:12px;
+}
+*{box-sizing:border-box;margin:0;padding:0}
+html{font-size:16px;-webkit-font-smoothing:antialiased}
+body{background:var(--bg);color:var(--ink);font-family:var(--sans);min-height:100vh;overflow-x:hidden}
+
+/* ── HEADER ── */
+header{
+  position:fixed;top:0;left:0;right:0;z-index:100;
+  padding:18px 32px;
+  display:flex;align-items:center;justify-content:center;
+  background:var(--bg);
+  border-bottom:1px solid rgba(28,26,23,.1);
+}
+.wordmark{
+  font-family:var(--serif);font-size:clamp(18px,2.5vw,26px);
+  font-weight:400;letter-spacing:.18em;text-transform:uppercase;
+  color:var(--ink);text-align:center;line-height:1.2;
+}
+
+/* ── SCREENS ── */
+.screen{
+  display:none;min-height:100vh;padding-top:80px;
+  flex-direction:column;align-items:center;justify-content:center;
+  padding-bottom:48px;
+}
+.screen.active{display:flex}
+
+/* ── INTRO ── */
+#intro{background:var(--bg);gap:32px;padding:80px 24px 48px}
+.intro-hero{
+  width:160px;height:160px;flex-shrink:0;
+}
+.intro-headline{
+  font-family:var(--serif);font-size:clamp(36px,7vw,68px);
+  font-weight:400;line-height:1.1;text-align:center;color:var(--ink);
+  max-width:700px;
+}
+.intro-sub{
+  font-size:16px;color:var(--ink2);text-align:center;
+  max-width:480px;line-height:1.65;letter-spacing:.02em;
+}
+.btn-primary{
+  background:var(--ink);color:var(--white);
+  border:none;cursor:pointer;
+  font-family:var(--serif);font-size:18px;letter-spacing:.12em;text-transform:uppercase;
+  padding:16px 56px;border-radius:2px;
+  transition:background .2s var(--ease),transform .15s var(--ease);
+}
+.btn-primary:hover{background:var(--accent2);transform:translateY(-2px)}
+.btn-primary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
+
+/* ── QUIZ ── */
+#quiz{padding:80px 24px 60px;gap:0}
+.progress-wrap{width:100%;max-width:680px;margin-bottom:36px}
+.progress-meta{display:flex;justify-content:space-between;margin-bottom:10px;font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink2)}
+.progress-track{height:3px;background:rgba(28,26,23,.12);border-radius:2px;overflow:hidden}
+.progress-fill{height:100%;background:var(--accent);border-radius:2px;transition:width .5s var(--ease)}
+.q-wrap{width:100%;max-width:680px}
+.q-label{font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--accent2);margin-bottom:12px}
+.q-text{font-family:var(--serif);font-size:clamp(22px,4vw,34px);font-weight:400;color:var(--ink);line-height:1.25;margin-bottom:32px}
+.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(148px,1fr));gap:14px}
+.tile{
+  cursor:pointer;border-radius:var(--r);overflow:hidden;
+  border:2px solid transparent;
+  transition:transform .2s var(--ease),box-shadow .2s var(--ease),border-color .2s var(--ease);
+  background:var(--white);
+  display:flex;flex-direction:column;
+  outline:none;
+}
+.tile:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(28,26,23,.14)}
+.tile:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px rgba(201,169,97,.35)}
+.tile.selected{border-color:var(--accent);box-shadow:0 6px 20px rgba(201,169,97,.4)}
+.tile-img{width:100%;aspect-ratio:1/1;display:block;overflow:hidden}
+.tile-img svg{width:100%;height:100%;display:block}
+.tile-label{padding:10px 12px;font-family:var(--serif);font-size:15px;color:var(--ink);text-align:center;letter-spacing:.04em;line-height:1.3}
+
+/* question transition */
+.q-wrap.fade-out{opacity:0;transform:translateX(-24px);transition:opacity .25s var(--ease),transform .25s var(--ease)}
+.q-wrap.fade-in{opacity:0;transform:translateX(24px)}
+.q-wrap.fade-in.show{opacity:1;transform:translateX(0);transition:opacity .3s var(--ease),transform .3s var(--ease)}
+
+/* ── RESULT ── */
+#result{padding:80px 24px 60px;gap:0;background:var(--bg)}
+.result-inner{max-width:720px;width:100%;display:flex;flex-direction:column;align-items:center;gap:40px}
+.archetype-eyebrow{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--accent2)}
+.archetype-name{font-family:var(--serif);font-size:clamp(40px,8vw,80px);font-weight:400;color:var(--ink);text-align:center;line-height:1}
+.archetype-desc{font-size:16px;line-height:1.75;color:var(--ink2);text-align:center;max-width:520px}
+.palette-row{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;justify-content:center}
+.swatch{display:flex;flex-direction:column;align-items:center;gap:8px;cursor:default}
+.swatch-box{width:72px;height:88px;border-radius:8px;overflow:hidden;box-shadow:0 4px 16px rgba(28,26,23,.15)}
+.swatch-box svg{width:100%;height:100%;display:block}
+.swatch-name{font-family:var(--serif);font-size:11px;letter-spacing:.07em;color:var(--ink2);text-align:center;max-width:72px;line-height:1.3}
+.rec-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:16px;width:100%}
+.rec-card{background:var(--white);border-radius:var(--r);overflow:hidden;box-shadow:0 4px 20px rgba(28,26,23,.08)}
+.rec-card-img{width:100%;aspect-ratio:3/4;display:block;overflow:hidden}
+.rec-card-img svg{width:100%;height:100%;display:block}
+.rec-card-body{padding:14px 16px}
+.rec-card-sku{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent2);margin-bottom:4px}
+.rec-card-name{font-family:var(--serif);font-size:16px;color:var(--ink);line-height:1.3}
+.rec-card-desc{font-size:12px;color:var(--ink2);margin-top:6px;line-height:1.5}
+.result-actions{display:flex;gap:14px;flex-wrap:wrap;justify-content:center}
+.btn-secondary{
+  background:transparent;color:var(--ink);
+  border:1.5px solid var(--ink);cursor:pointer;
+  font-family:var(--serif);font-size:16px;letter-spacing:.1em;text-transform:uppercase;
+  padding:13px 36px;border-radius:2px;
+  transition:all .2s var(--ease);
+}
+.btn-secondary:hover{background:var(--ink);color:var(--white)}
+.btn-secondary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
+
+/* reveal animation */
+.result-inner>*{opacity:0;transform:translateY(18px);transition:opacity .5s var(--ease),transform .5s var(--ease)}
+.result-inner.revealed>*{opacity:1;transform:none}
+.result-inner.revealed>*:nth-child(1){transition-delay:.05s}
+.result-inner.revealed>*:nth-child(2){transition-delay:.15s}
+.result-inner.revealed>*:nth-child(3){transition-delay:.25s}
+.result-inner.revealed>*:nth-child(4){transition-delay:.35s}
+.result-inner.revealed>*:nth-child(5){transition-delay:.45s}
+.result-inner.revealed>*:nth-child(6){transition-delay:.55s}
+.result-inner.revealed>*:nth-child(7){transition-delay:.65s}
+
+/* ── MODAL overlay for download card ── */
+#dl-modal{
+  position:fixed;inset:0;z-index:200;
+  display:flex;align-items:center;justify-content:center;
+  background:rgba(28,26,23,.7);
+  visibility:hidden;pointer-events:none;opacity:0;
+  transition:opacity .25s var(--ease);
+}
+#dl-modal.open{visibility:visible;pointer-events:auto;opacity:1}
+.modal-box{
+  background:var(--white);border-radius:16px;padding:32px 28px;
+  max-width:480px;width:90%;display:flex;flex-direction:column;gap:20px;
+  box-shadow:0 24px 80px rgba(28,26,23,.3);
+}
+.modal-title{font-family:var(--serif);font-size:24px;color:var(--ink)}
+.modal-preview{width:100%;aspect-ratio:1080/1350;border-radius:8px;overflow:hidden;background:var(--bg)}
+.modal-preview img{width:100%;height:100%;object-fit:cover;display:block}
+.modal-actions{display:flex;gap:12px;justify-content:flex-end}
+.btn-close{
+  background:none;border:none;cursor:pointer;font-size:22px;
+  color:var(--ink2);padding:4px;line-height:1;
+  transition:color .15s;
+}
+.btn-close:hover{color:var(--ink)}
+.modal-header{display:flex;justify-content:space-between;align-items:center}
+
+/* ── OFFSCREEN CANVAS for PNG export ── */
+#export-canvas{position:absolute;left:-9999px;top:-9999px;visibility:hidden}
+
+@media(max-width:480px){
+  .tiles{grid-template-columns:1fr 1fr}
+  .palette-row .swatch-box{width:56px;height:68px}
+}
+</style>
+</head>
+<body>
+
+<header>
+  <div class="wordmark">Designer Wallcoverings</div>
+</header>
+
+<!-- ══════════ INTRO ══════════ -->
+<section id="intro" class="screen active" role="main">
+  <svg class="intro-hero" viewBox="0 0 160 160" aria-hidden="true">
+    <defs>
+      <pattern id="ih-damask" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
+        <rect width="40" height="40" fill="#eceae1"/>
+        <ellipse cx="20" cy="20" rx="8" ry="12" fill="none" stroke="#c9a961" stroke-width="1"/>
+        <ellipse cx="20" cy="20" rx="4" ry="6" fill="#c9a961" opacity=".3"/>
+        <circle cx="20" cy="8" r="2" fill="#c9a961" opacity=".5"/>
+        <circle cx="20" cy="32" r="2" fill="#c9a961" opacity=".5"/>
+        <circle cx="8" cy="20" r="2" fill="#c9a961" opacity=".5"/>
+        <circle cx="32" cy="20" r="2" fill="#c9a961" opacity=".5"/>
+      </pattern>
+    </defs>
+    <circle cx="80" cy="80" r="78" fill="url(#ih-damask)" stroke="#c9a961" stroke-width="1.5"/>
+    <circle cx="80" cy="80" r="62" fill="none" stroke="#c9a961" stroke-width=".75" opacity=".5"/>
+  </svg>
+  <h1 class="intro-headline">What's your<br>wallcovering style?</h1>
+  <p class="intro-sub">Six quick questions reveal your signature aesthetic — and the patterns made to match it.</p>
+  <button class="btn-primary" id="begin-btn" onclick="startQuiz()">Begin</button>
+</section>
+
+<!-- ══════════ QUIZ ══════════ -->
+<section id="quiz" class="screen" role="main" aria-label="Style quiz">
+  <div class="progress-wrap">
+    <div class="progress-meta">
+      <span id="q-counter">Question 1 of 6</span>
+      <span id="q-archetype-hint"></span>
+    </div>
+    <div class="progress-track" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="6">
+      <div class="progress-fill" id="progress-fill" style="width:16.6%"></div>
+    </div>
+  </div>
+  <div class="q-wrap" id="q-wrap"></div>
+</section>
+
+<!-- ══════════ RESULT ══════════ -->
+<section id="result" class="screen" role="main" aria-label="Your style result">
+  <div class="result-inner" id="result-inner">
+    <div class="archetype-eyebrow">Your signature style</div>
+    <div class="archetype-name" id="res-name"></div>
+    <p class="archetype-desc" id="res-desc"></p>
+    <div class="palette-row" id="res-palette"></div>
+    <div class="rec-cards" id="res-cards"></div>
+    <div class="result-actions">
+      <button class="btn-secondary" onclick="openDownload()" aria-label="Download your style card">Download Card</button>
+      <button class="btn-primary" onclick="retake()">Retake Quiz</button>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════ DOWNLOAD MODAL ══════════ -->
+<div id="dl-modal" role="dialog" aria-modal="true" aria-label="Download your style card" aria-hidden="true">
+  <div class="modal-box">
+    <div class="modal-header">
+      <span class="modal-title">Your Style Card</span>
+      <button class="btn-close" onclick="closeDownload()" aria-label="Close">&times;</button>
+    </div>
+    <div class="modal-preview" id="modal-preview"></div>
+    <div class="modal-actions">
+      <button class="btn-secondary" onclick="closeDownload()">Close</button>
+      <button class="btn-primary" id="dl-btn" onclick="downloadCard()">Save PNG</button>
+    </div>
+  </div>
+</div>
+
+<canvas id="export-canvas" width="1080" height="1350" aria-hidden="true"></canvas>
+
+<script>
+/* ═══════════════════════════════════════════════════════
+   TEXTURE HELPERS — return SVG markup strings
+═══════════════════════════════════════════════════════ */
+function svgWrap(w,h,defs,content){
+  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}">${defs}${content}</svg>`;
+}
+
+// ── TILE TEXTURES (question answer visuals) ──
+const TX = {
+
+  // Roses & botanicals
+  floral_vintage(){
+    return svgWrap(200,200,
+      `<defs><pattern id="tv" width="50" height="50" patternUnits="userSpaceOnUse"><rect width="50" height="50" fill="#f0ebe0"/>
+      <circle cx="25" cy="25" r="9" fill="#c97c8a" opacity=".75"/>
+      <circle cx="25" cy="25" r="5" fill="#e8a5b0" opacity=".85"/>
+      <circle cx="25" cy="25" r="2" fill="#fff" opacity=".6"/>
+      <ellipse cx="14" cy="14" rx="5" ry="7" fill="#7a9c6a" opacity=".6" transform="rotate(-30,14,14)"/>
+      <ellipse cx="36" cy="14" rx="5" ry="7" fill="#7a9c6a" opacity=".6" transform="rotate(30,36,14)"/>
+      <ellipse cx="14" cy="36" rx="5" ry="7" fill="#8fad7d" opacity=".5" transform="rotate(30,14,36)"/>
+      <ellipse cx="36" cy="36" rx="5" ry="7" fill="#8fad7d" opacity=".5" transform="rotate(-30,36,36)"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#tv)"/>`
+    );
+  },
+
+  // Grasscloth / natural weave
+  grasscloth(){
+    return svgWrap(200,200,
+      `<defs><pattern id="gc" width="10" height="6" patternUnits="userSpaceOnUse">
+      <rect width="10" height="6" fill="#d4c4a0"/>
+      <line x1="0" y1="0" x2="10" y2="0" stroke="#b8a678" stroke-width=".8" opacity=".7"/>
+      <line x1="0" y1="3" x2="10" y2="3" stroke="#c9b88a" stroke-width=".5" opacity=".5"/>
+      <line x1="2" y1="0" x2="2" y2="6" stroke="#b0956a" stroke-width=".6" opacity=".4"/>
+      <line x1="6" y1="0" x2="6" y2="6" stroke="#b0956a" stroke-width=".6" opacity=".4"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#gc)"/>`
+    );
+  },
+
+  // Moody dark floral
+  dark_floral(){
+    return svgWrap(200,200,
+      `<defs><pattern id="df" width="50" height="50" patternUnits="userSpaceOnUse">
+      <rect width="50" height="50" fill="#1a1220"/>
+      <circle cx="25" cy="25" r="10" fill="#3d1f3a" opacity=".9"/>
+      <circle cx="25" cy="25" r="6" fill="#6b2d5e" opacity=".8"/>
+      <circle cx="25" cy="25" r="3" fill="#c9a961" opacity=".7"/>
+      <ellipse cx="12" cy="12" rx="4" ry="7" fill="#2a3d1a" opacity=".7" transform="rotate(-20,12,12)"/>
+      <ellipse cx="38" cy="38" rx="4" ry="7" fill="#1e3318" opacity=".7" transform="rotate(-20,38,38)"/>
+      <ellipse cx="38" cy="12" rx="3" ry="6" fill="#2f4020" opacity=".6" transform="rotate(20,38,12)"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#df)"/>`
+    );
+  },
+
+  // Coastal linen
+  coastal_linen(){
+    return svgWrap(200,200,
+      `<defs><pattern id="cl" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#e8f0f5"/>
+      <line x1="0" y1="4" x2="8" y2="4" stroke="#b8ced8" stroke-width=".7"/>
+      <line x1="4" y1="0" x2="4" y2="8" stroke="#c5d8e2" stroke-width=".5" opacity=".6"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#cl)"/>
+      <circle cx="100" cy="100" r="60" fill="none" stroke="#a8c4d0" stroke-width=".8" opacity=".4"/>
+      <circle cx="100" cy="100" r="40" fill="none" stroke="#a8c4d0" stroke-width=".5" opacity=".3"/>
+      <circle cx="100" cy="100" r="20" fill="#dbeaf2" opacity=".3"/>`
+    );
+  },
+
+  // Geo / minimalist
+  geo_minimal(){
+    return svgWrap(200,200,
+      `<defs><pattern id="gm" width="40" height="40" patternUnits="userSpaceOnUse">
+      <rect width="40" height="40" fill="#f7f5f1"/>
+      <rect x="0" y="0" width="20" height="20" fill="#ece9e2"/>
+      <rect x="20" y="20" width="20" height="20" fill="#ece9e2"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#gm)"/>
+      <line x1="0" y1="100" x2="200" y2="100" stroke="#c8c4bc" stroke-width=".8"/>
+      <line x1="100" y1="0" x2="100" y2="200" stroke="#c8c4bc" stroke-width=".8"/>`
+    );
+  },
+
+  // Art Deco fan
+  art_deco(){
+    return svgWrap(200,200,
+      `<defs><pattern id="ad" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#0d1f1a"/>
+      <path d="M30,60 A30,30,0,0,1,0,30 L30,30 Z" fill="none" stroke="#c9a961" stroke-width="1.2"/>
+      <path d="M30,60 A20,20,0,0,1,10,30 L30,30 Z" fill="none" stroke="#c9a961" stroke-width=".7" opacity=".6"/>
+      <path d="M60,30 A30,30,0,0,1,30,60 L30,30 Z" fill="none" stroke="#c9a961" stroke-width="1.2"/>
+      <path d="M60,30 A20,20,0,0,1,40,60 L30,30 Z" fill="none" stroke="#c9a961" stroke-width=".7" opacity=".6"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#ad)"/>`
+    );
+  },
+
+  // Maximalist jewel velvet feel
+  jewel_velvet(){
+    return svgWrap(200,200,
+      `<defs>
+      <radialGradient id="jv-g" cx="50%" cy="50%" r="50%">
+        <stop offset="0%" stop-color="#4a1060"/>
+        <stop offset="100%" stop-color="#1c0830"/>
+      </radialGradient>
+      <pattern id="jv" width="30" height="30" patternUnits="userSpaceOnUse">
+      <rect width="30" height="30" fill="url(#jv-g)"/>
+      <circle cx="15" cy="15" r="3" fill="#c9a961" opacity=".5"/>
+      <circle cx="0" cy="0" r="2" fill="#c9a961" opacity=".3"/>
+      <circle cx="30" cy="0" r="2" fill="#c9a961" opacity=".3"/>
+      <circle cx="0" cy="30" r="2" fill="#c9a961" opacity=".3"/>
+      <circle cx="30" cy="30" r="2" fill="#c9a961" opacity=".3"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#jv)"/>`
+    );
+  },
+
+  // Warm neutrals linen
+  warm_linen(){
+    return svgWrap(200,200,
+      `<defs><pattern id="wl" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#e8dfc8"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#d4c8a8" stroke-width=".8"/>
+      <line x1="3" y1="0" x2="3" y2="6" stroke="#cfc3a0" stroke-width=".5" opacity=".7"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#wl)"/>`
+    );
+  },
+
+  // Marble
+  marble(){
+    return svgWrap(200,200,
+      `<defs>
+      <filter id="mf"><feTurbulence type="turbulence" baseFrequency=".015 .05" numOctaves="4" seed="5" result="noise"/>
+      <feColorMatrix type="matrix" values="1 0 0 0 0.88  0 1 0 0 0.84  0 0 1 0 0.78  0 0 0 1 0" in="noise" result="col"/>
+      <feBlend in="SourceGraphic" in2="col" mode="multiply"/></filter></defs>`,
+      `<rect width="200" height="200" fill="#f0ece4" filter="url(#mf)"/>
+      <path d="M0,80 Q50,60,100,80 T200,80" fill="none" stroke="#c8bfb0" stroke-width="1.2" opacity=".6"/>
+      <path d="M20,120 Q80,100,140,130 T200,110" fill="none" stroke="#b8afa0" stroke-width=".8" opacity=".4"/>`
+    );
+  },
+
+  // Seagrass texture
+  seagrass(){
+    return svgWrap(200,200,
+      `<defs><pattern id="sg" width="12" height="12" patternUnits="userSpaceOnUse">
+      <rect width="12" height="12" fill="#c8b87a"/>
+      <line x1="0" y1="0" x2="12" y2="12" stroke="#b0a060" stroke-width=".8" opacity=".5"/>
+      <line x1="12" y1="0" x2="0" y2="12" stroke="#b8a868" stroke-width=".6" opacity=".4"/>
+      <rect x="4" y="4" width="4" height="4" fill="#d4c488" opacity=".4"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#sg)"/>`
+    );
+  },
+
+  // Stripe bold
+  bold_stripe(){
+    return svgWrap(200,200,
+      `<defs><pattern id="bs" width="30" height="30" patternUnits="userSpaceOnUse">
+      <rect width="30" height="30" fill="#1c1a17"/>
+      <rect x="0" y="0" width="10" height="30" fill="#c9a961"/>
+      <rect x="20" y="0" width="3" height="30" fill="#c9a961" opacity=".4"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#bs)"/>`
+    );
+  },
+
+  // Toile sketch feel
+  toile(){
+    return svgWrap(200,200,
+      `<defs><pattern id="to" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#f5f0e8"/>
+      <ellipse cx="30" cy="20" rx="10" ry="14" fill="none" stroke="#8b7355" stroke-width=".8"/>
+      <path d="M22,34 Q30,28,38,34" fill="none" stroke="#8b7355" stroke-width=".7"/>
+      <line x1="30" y1="34" x2="30" y2="50" stroke="#8b7355" stroke-width=".8"/>
+      <line x1="20" y1="42" x2="40" y2="42" stroke="#8b7355" stroke-width=".7"/>
+      <line x1="30" y1="50" x2="22" y2="60" stroke="#8b7355" stroke-width=".8"/>
+      <line x1="30" y1="50" x2="38" y2="60" stroke="#8b7355" stroke-width=".8"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#to)"/>`
+    );
+  },
+
+  // Concrete / plaster
+  concrete(){
+    return svgWrap(200,200,
+      `<defs>
+      <filter id="cf"><feTurbulence type="fractalNoise" baseFrequency=".065" numOctaves="3" seed="8"/>
+      <feColorMatrix type="saturate" values="0"/>
+      <feBlend in="SourceGraphic" mode="multiply"/></filter></defs>`,
+      `<rect width="200" height="200" fill="#d8d4ce" filter="url(#cf)"/>
+      <rect width="200" height="200" fill="#d0ccc4" opacity=".4"/>`
+    );
+  },
+
+  // Greek key / trellis
+  greek_key(){
+    return svgWrap(200,200,
+      `<defs><pattern id="gk" width="40" height="40" patternUnits="userSpaceOnUse">
+      <rect width="40" height="40" fill="#f7f4ee"/>
+      <path d="M4,4 h12 v12 h-8 v-8 h4" fill="none" stroke="#c9a961" stroke-width="1.5" stroke-linecap="square"/>
+      <path d="M24,24 h12 v12 h-8 v-8 h4" fill="none" stroke="#c9a961" stroke-width="1.5" stroke-linecap="square"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#gk)"/>`
+    );
+  },
+
+  // Chinoiserie branch
+  chinoiserie(){
+    return svgWrap(200,200,
+      `<defs><pattern id="ch" width="80" height="80" patternUnits="userSpaceOnUse">
+      <rect width="80" height="80" fill="#e8f4f0"/>
+      <path d="M10,70 Q30,40,50,30 T80,10" fill="none" stroke="#4a8060" stroke-width="1.5"/>
+      <circle cx="50" cy="30" r="5" fill="#6aaa84" opacity=".7"/>
+      <circle cx="65" cy="20" r="3" fill="#e87c8a" opacity=".8"/>
+      <circle cx="35" cy="45" r="4" fill="#c9a961" opacity=".6"/>
+      <ellipse cx="25" cy="52" rx="6" ry="4" fill="#4a8060" opacity=".5" transform="rotate(-20,25,52)"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#ch)"/>`
+    );
+  },
+
+  // Sand / plaster earthy
+  sand_plaster(){
+    return svgWrap(200,200,
+      `<defs>
+      <filter id="sp"><feTurbulence type="turbulence" baseFrequency=".04" numOctaves="5" seed="12"/>
+      <feColorMatrix type="matrix" values="1 0 0 0 0.8  0 1 0 0 0.72  0 0 1 0 0.58  0 0 0 0.5 0"/>
+      <feBlend in="SourceGraphic" mode="multiply"/></filter></defs>`,
+      `<rect width="200" height="200" fill="#e0d4b8" filter="url(#sp)"/>`
+    );
+  },
+
+  // Herringbone parquet
+  herringbone(){
+    return svgWrap(200,200,
+      `<defs><pattern id="hb" width="24" height="24" patternUnits="userSpaceOnUse">
+      <rect width="24" height="24" fill="#d4c4a0"/>
+      <rect x="0" y="0" width="12" height="6" fill="#c8b488" transform="rotate(45,6,3)"/>
+      <rect x="12" y="12" width="12" height="6" fill="#c8b488" transform="rotate(-45,18,15)"/>
+      <rect x="0" y="12" width="12" height="6" fill="#b8a47a" transform="rotate(45,6,15)"/>
+      <rect x="12" y="0" width="12" height="6" fill="#b8a47a" transform="rotate(-45,18,3)"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#hb)"/>`
+    );
+  },
+
+  // Ink brush / zen abstract
+  ink_brush(){
+    return svgWrap(200,200,
+      `<defs><pattern id="ib" width="80" height="80" patternUnits="userSpaceOnUse">
+      <rect width="80" height="80" fill="#f7f5f1"/>
+      <path d="M10,40 Q20,10,40,40 Q60,70,70,40" fill="none" stroke="#1c1a17" stroke-width="3" stroke-linecap="round" opacity=".8"/>
+      <path d="M0,65 Q25,55,40,65 T80,55" fill="none" stroke="#1c1a17" stroke-width="1.5" stroke-linecap="round" opacity=".3"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#ib)"/>`
+    );
+  },
+
+  // Bold tropical leaf
+  tropical_leaf(){
+    return svgWrap(200,200,
+      `<defs><pattern id="tl" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#1a2e20"/>
+      <path d="M10,60 Q30,20,50,10" fill="none" stroke="#3a6040" stroke-width="8" stroke-linecap="round" opacity=".8"/>
+      <path d="M0,30 Q20,15,40,5" fill="none" stroke="#4a7850" stroke-width="5" stroke-linecap="round" opacity=".6"/>
+      <path d="M30,60 Q40,40,60,30" fill="none" stroke="#2e5035" stroke-width="6" stroke-linecap="round" opacity=".7"/>
+      <line x1="10" y1="60" x2="50" y2="10" stroke="#1a2e20" stroke-width=".8" opacity=".4"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#tl)"/>`
+    );
+  },
+
+  // Classic damask
+  damask(){
+    return svgWrap(200,200,
+      `<defs><pattern id="dam" width="50" height="50" patternUnits="userSpaceOnUse">
+      <rect width="50" height="50" fill="#f0ebe0"/>
+      <ellipse cx="25" cy="25" rx="10" ry="16" fill="none" stroke="#c9a961" stroke-width="1.2"/>
+      <ellipse cx="25" cy="25" rx="6" ry="10" fill="#c9a961" opacity=".15"/>
+      <path d="M15,25 Q10,15,15,5" fill="none" stroke="#c9a961" stroke-width="1" opacity=".5"/>
+      <path d="M35,25 Q40,15,35,5" fill="none" stroke="#c9a961" stroke-width="1" opacity=".5"/>
+      <path d="M15,25 Q10,35,15,45" fill="none" stroke="#c9a961" stroke-width="1" opacity=".5"/>
+      <path d="M35,25 Q40,35,35,45" fill="none" stroke="#c9a961" stroke-width="1" opacity=".5"/>
+      <circle cx="25" cy="5" r="2.5" fill="#c9a961" opacity=".5"/>
+      <circle cx="25" cy="45" r="2.5" fill="#c9a961" opacity=".5"/>
+      <circle cx="5" cy="25" r="2.5" fill="#c9a961" opacity=".5"/>
+      <circle cx="45" cy="25" r="2.5" fill="#c9a961" opacity=".5"/>
+      </pattern></defs>`,
+      `<rect width="200" height="200" fill="url(#dam)"/>`
+    );
+  },
+
+};
+
+/* ═══════════════════════════════════════════════════════
+   SWATCH TEXTURES (for result palette + rec cards)
+═══════════════════════════════════════════════════════ */
+const SW = {
+  dusty_rose(){
+    return svgWrap(72,88,
+      `<defs><pattern id="sdr" width="10" height="10" patternUnits="userSpaceOnUse">
+      <rect width="10" height="10" fill="#d4a0a8"/>
+      <circle cx="5" cy="5" r="1.5" fill="#c08890" opacity=".4"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#sdr)"/>`);
+  },
+  sage_green(){
+    return svgWrap(72,88,
+      `<defs><pattern id="ssg" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#8aaa88"/>
+      <line x1="0" y1="4" x2="8" y2="4" stroke="#789878" stroke-width=".6" opacity=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#ssg)"/>`);
+  },
+  navy_dusk(){
+    return svgWrap(72,88,
+      `<defs><pattern id="snd" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#1e3050"/>
+      <circle cx="3" cy="3" r=".8" fill="#c9a961" opacity=".4"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#snd)"/>`);
+  },
+  warm_cream(){
+    return svgWrap(72,88,
+      `<defs><pattern id="swc" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#f0e8d0"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#d8d0b4" stroke-width=".5" opacity=".6"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#swc)"/>`);
+  },
+  antique_gold(){
+    return svgWrap(72,88,
+      `<defs><pattern id="sag" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#c9a961"/>
+      <line x1="0" y1="0" x2="8" y2="8" stroke="#b8943a" stroke-width=".7" opacity=".4"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#sag)"/>`);
+  },
+  // palette per archetype
+  midnight_plum(){
+    return svgWrap(72,88,
+      `<defs><pattern id="mp" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#2d0d3a"/>
+      <circle cx="4" cy="4" r="1" fill="#c9a961" opacity=".3"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#mp)"/>`);
+  },
+  wine(){
+    return svgWrap(72,88,
+      `<defs><pattern id="wn" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#6b1a2a"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#8b2a3a" stroke-width=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#wn)"/>`);
+  },
+  emerald(){
+    return svgWrap(72,88,
+      `<defs><pattern id="em" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#1a4a30"/>
+      <line x1="3" y1="0" x2="3" y2="6" stroke="#2a6040" stroke-width=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#em)"/>`);
+  },
+  seafoam(){
+    return svgWrap(72,88,
+      `<defs><pattern id="sf" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#a8d4c8"/>
+      <circle cx="4" cy="4" r="1.2" fill="#88c0b0" opacity=".4"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#sf)"/>`);
+  },
+  sand(){
+    return svgWrap(72,88,
+      `<defs><pattern id="sd" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#d4c4a0"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#c0b088" stroke-width=".5"/>
+      <line x1="3" y1="0" x2="3" y2="6" stroke="#c0b088" stroke-width=".4" opacity=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#sd)"/>`);
+  },
+  sky(){
+    return svgWrap(72,88,
+      `<defs><pattern id="sky" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#b8d4e8"/>
+      <line x1="0" y1="4" x2="8" y2="4" stroke="#a0c4dc" stroke-width=".5" opacity=".6"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#sky)"/>`);
+  },
+  white_linen(){
+    return svgWrap(72,88,
+      `<defs><pattern id="wli" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#f0ede4"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#ddd8cc" stroke-width=".5" opacity=".7"/>
+      <line x1="3" y1="0" x2="3" y2="6" stroke="#ddd8cc" stroke-width=".4" opacity=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#wli)"/>`);
+  },
+  charcoal(){
+    return svgWrap(72,88,
+      `<defs><pattern id="cc" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#2a2824"/>
+      <line x1="0" y1="3" x2="6" y2="3" stroke="#383430" stroke-width=".5" opacity=".5"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#cc)"/>`);
+  },
+  ivory(){
+    return svgWrap(72,88,
+      `<defs><pattern id="iv" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#f8f4ec"/>
+      <circle cx="4" cy="4" r="1" fill="#e0dcd0" opacity=".4"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#iv)"/>`);
+  },
+  deco_black(){
+    return svgWrap(72,88,
+      `<defs><pattern id="db" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#0d0b09"/>
+      <line x1="0" y1="0" x2="8" y2="8" stroke="#c9a961" stroke-width=".5" opacity=".3"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#db)"/>`);
+  },
+  champagne(){
+    return svgWrap(72,88,
+      `<defs><pattern id="chp" width="6" height="6" patternUnits="userSpaceOnUse">
+      <rect width="6" height="6" fill="#e8d4a0"/>
+      <line x1="3" y1="0" x2="3" y2="6" stroke="#d4c080" stroke-width=".6"/>
+      </pattern></defs>`,`<rect width="72" height="88" fill="url(#chp)"/>`);
+  },
+};
+
+/* ═══════════════════════════════════════════════════════
+   RECOMMENDED PATTERN CARD TEXTURES
+═══════════════════════════════════════════════════════ */
+const RC = {
+  // large-scale pattern previews for rec cards
+  gm_damask(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-dam" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#f5efd8"/>
+      <ellipse cx="30" cy="30" rx="12" ry="18" fill="none" stroke="#c9a961" stroke-width="1.5"/>
+      <ellipse cx="30" cy="30" rx="7" ry="11" fill="#c9a961" opacity=".12"/>
+      <circle cx="30" cy="12" r="3" fill="#c9a961" opacity=".5"/>
+      <circle cx="30" cy="48" r="3" fill="#c9a961" opacity=".5"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-dam)"/>`);
+  },
+  gm_floral(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-fl" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#f0e8d8"/>
+      <circle cx="30" cy="30" r="12" fill="#c97c8a" opacity=".7"/>
+      <circle cx="30" cy="30" r="7" fill="#e8a5b0" opacity=".8"/>
+      <circle cx="30" cy="30" r="3" fill="#fff" opacity=".6"/>
+      <ellipse cx="14" cy="14" rx="6" ry="9" fill="#7a9c6a" opacity=".5" transform="rotate(-30,14,14)"/>
+      <ellipse cx="46" cy="46" rx="6" ry="9" fill="#7a9c6a" opacity=".5" transform="rotate(-30,46,46)"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-fl)"/>`);
+  },
+  gm_toile(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-to" width="90" height="90" patternUnits="userSpaceOnUse">
+      <rect width="90" height="90" fill="#f8f2e6"/>
+      <ellipse cx="45" cy="30" rx="14" ry="18" fill="none" stroke="#9b7d55" stroke-width="1"/>
+      <path d="M32,48 Q45,38,58,48" fill="none" stroke="#9b7d55" stroke-width=".9"/>
+      <line x1="45" y1="48" x2="45" y2="70" stroke="#9b7d55" stroke-width="1"/>
+      <line x1="28" y1="60" x2="62" y2="60" stroke="#9b7d55" stroke-width=".9"/>
+      <line x1="45" y1="70" x2="32" y2="90" stroke="#9b7d55" stroke-width="1"/>
+      <line x1="45" y1="70" x2="58" y2="90" stroke="#9b7d55" stroke-width="1"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-to)"/>`);
+  },
+  cc_grasscloth(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-gc" width="12" height="8" patternUnits="userSpaceOnUse">
+      <rect width="12" height="8" fill="#cfc0a0"/>
+      <line x1="0" y1="0" x2="12" y2="0" stroke="#b8a880" stroke-width="1" opacity=".7"/>
+      <line x1="0" y1="4" x2="12" y2="4" stroke="#c4b494" stroke-width=".6" opacity=".5"/>
+      <line x1="3" y1="0" x2="3" y2="8" stroke="#a89870" stroke-width=".5" opacity=".4"/>
+      <line x1="7" y1="0" x2="7" y2="8" stroke="#a89870" stroke-width=".5" opacity=".4"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-gc)"/>`);
+  },
+  cc_linen(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-ln" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#dce8f0"/>
+      <line x1="0" y1="4" x2="8" y2="4" stroke="#b0c8d8" stroke-width=".7"/>
+      <line x1="4" y1="0" x2="4" y2="8" stroke="#b8d0e0" stroke-width=".5" opacity=".6"/>
+      </pattern></defs>`,
+      `<rect width="180" height="240" fill="url(#rc-ln)"/>
+      <circle cx="90" cy="120" r="70" fill="none" stroke="#a0c4d4" stroke-width=".8" opacity=".3"/>
+      <circle cx="90" cy="120" r="45" fill="none" stroke="#a0c4d4" stroke-width=".5" opacity=".2"/>`);
+  },
+  cc_seagrass(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-sg" width="14" height="14" patternUnits="userSpaceOnUse">
+      <rect width="14" height="14" fill="#c4b478"/>
+      <line x1="0" y1="0" x2="14" y2="14" stroke="#a89860" stroke-width=".9" opacity=".5"/>
+      <line x1="14" y1="0" x2="0" y2="14" stroke="#b0a468" stroke-width=".6" opacity=".4"/>
+      <rect x="5" y="5" width="4" height="4" fill="#d4c488" opacity=".4"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-sg)"/>`);
+  },
+  mm_darkfloral(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-df" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#1a1220"/>
+      <circle cx="30" cy="30" r="12" fill="#3d1f3a" opacity=".9"/>
+      <circle cx="30" cy="30" r="7" fill="#6b2d5e" opacity=".8"/>
+      <circle cx="30" cy="30" r="3" fill="#c9a961" opacity=".6"/>
+      <ellipse cx="12" cy="12" rx="5" ry="8" fill="#2a3d1a" opacity=".7" transform="rotate(-20,12,12)"/>
+      <ellipse cx="48" cy="48" rx="5" ry="8" fill="#1e3318" opacity=".7" transform="rotate(-20,48,48)"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-df)"/>`);
+  },
+  mm_tropical(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-tl" width="70" height="70" patternUnits="userSpaceOnUse">
+      <rect width="70" height="70" fill="#162418"/>
+      <path d="M10,70 Q35,25,60,10" fill="none" stroke="#3a6040" stroke-width="10" stroke-linecap="round" opacity=".85"/>
+      <path d="M0,35 Q25,18,50,5" fill="none" stroke="#4a7850" stroke-width="6" stroke-linecap="round" opacity=".6"/>
+      <path d="M40,70 Q52,48,70,35" fill="none" stroke="#2e5035" stroke-width="7" stroke-linecap="round" opacity=".7"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-tl)"/>`);
+  },
+  mm_velvet(){
+    return svgWrap(180,240,
+      `<defs>
+      <radialGradient id="rc-jv-g" cx="50%" cy="50%" r="50%">
+        <stop offset="0%" stop-color="#4a1060"/>
+        <stop offset="100%" stop-color="#1c0830"/>
+      </radialGradient>
+      <pattern id="rc-jv" width="40" height="40" patternUnits="userSpaceOnUse">
+      <rect width="40" height="40" fill="url(#rc-jv-g)"/>
+      <circle cx="20" cy="20" r="4" fill="#c9a961" opacity=".45"/>
+      <circle cx="0" cy="0" r="2.5" fill="#c9a961" opacity=".3"/>
+      <circle cx="40" cy="40" r="2.5" fill="#c9a961" opacity=".3"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-jv)"/>`);
+  },
+  mn_linen(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-ml" width="8" height="8" patternUnits="userSpaceOnUse">
+      <rect width="8" height="8" fill="#ede8df"/>
+      <line x1="0" y1="4" x2="8" y2="4" stroke="#d8d0c4" stroke-width=".6"/>
+      <line x1="4" y1="0" x2="4" y2="8" stroke="#d4ccc0" stroke-width=".4" opacity=".5"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-ml)"/>`);
+  },
+  mn_geo(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-mg" width="40" height="40" patternUnits="userSpaceOnUse">
+      <rect width="40" height="40" fill="#f5f2ec"/>
+      <rect x="0" y="0" width="20" height="20" fill="#ebe8e0"/>
+      <rect x="20" y="20" width="20" height="20" fill="#ebe8e0"/>
+      </pattern></defs>`,
+      `<rect width="180" height="240" fill="url(#rc-mg)"/>
+      <line x1="0" y1="120" x2="180" y2="120" stroke="#c0bbb0" stroke-width=".8"/>
+      <line x1="90" y1="0" x2="90" y2="240" stroke="#c0bbb0" stroke-width=".8"/>`);
+  },
+  mn_concrete(){
+    return svgWrap(180,240,
+      `<defs>
+      <filter id="rc-cf"><feTurbulence type="fractalNoise" baseFrequency=".055" numOctaves="3" seed="14"/>
+      <feColorMatrix type="saturate" values="0"/>
+      <feBlend in="SourceGraphic" mode="multiply"/></filter></defs>`,
+      `<rect width="180" height="240" fill="#d4d0ca" filter="url(#rc-cf)"/>`);
+  },
+  ad_deco(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-ad" width="60" height="60" patternUnits="userSpaceOnUse">
+      <rect width="60" height="60" fill="#0d1f1a"/>
+      <path d="M30,60 A30,30,0,0,1,0,30 L30,30 Z" fill="none" stroke="#c9a961" stroke-width="1.5"/>
+      <path d="M30,60 A20,20,0,0,1,10,30 L30,30 Z" fill="none" stroke="#c9a961" stroke-width=".8" opacity=".5"/>
+      <path d="M60,30 A30,30,0,0,1,30,60 L30,30 Z" fill="none" stroke="#c9a961" stroke-width="1.5"/>
+      <path d="M60,30 A20,20,0,0,1,40,60 L30,30 Z" fill="none" stroke="#c9a961" stroke-width=".8" opacity=".5"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-ad)"/>`);
+  },
+  ad_greek(){
+    return svgWrap(180,240,
+      `<defs><pattern id="rc-gk" width="48" height="48" patternUnits="userSpaceOnUse">
+      <rect width="48" height="48" fill="#1a1408"/>
+      <path d="M4,4 h16 v16 h-10 v-10 h5" fill="none" stroke="#c9a961" stroke-width="1.8" stroke-linecap="square"/>
+      <path d="M28,28 h16 v16 h-10 v-10 h5" fill="none" stroke="#c9a961" stroke-width="1.8" stroke-linecap="square"/>
+      </pattern></defs>`,`<rect width="180" height="240" fill="url(#rc-gk)"/>`);
+  },
+  ad_marble(){
+    return svgWrap(180,240,
+      `<defs>
+      <filter id="rc-mf"><feTurbulence type="turbulence" baseFrequency=".015 .05" numOctaves="4" seed="3" result="n"/>
+      <feColorMatrix type="matrix" values="1 0 0 0 0.86  0 1 0 0 0.82  0 0 1 0 0.75  0 0 0 1 0" in="n" result="c"/>
+      <feBlend in="SourceGraphic" in2="c" mode="multiply"/></filter></defs>`,
+      `<rect width="180" height="240" fill="#ece8e0" filter="url(#rc-mf)"/>
+      <path d="M0,80 Q50,55,100,80 T180,75" fill="none" stroke="#b8aea0" stroke-width="1.5" opacity=".5"/>
+      <path d="M20,150 Q80,125,140,160 T180,140" fill="none" stroke="#a8a090" stroke-width="1" opacity=".4"/>`);
+  },
+};
+
+/* ═══════════════════════════════════════════════════════
+   QUESTIONS DATA
+═══════════════════════════════════════════════════════ */
+const QUESTIONS = [
+  {
+    text:"When you walk into a room, you want to feel…",
+    tiles:[
+      {label:"Enveloped & Storied",key:"grand",tx:"floral_vintage"},
+      {label:"Serene & Breathed-in",key:"coast",tx:"coastal_linen"},
+      {label:"Dramatic & Alive",key:"moody",tx:"dark_floral"},
+      {label:"Clear & Elevated",key:"minim",tx:"geo_minimal"},
+    ]
+  },
+  {
+    text:"Your dream weekend is…",
+    tiles:[
+      {label:"Antique market + afternoon tea",key:"grand",tx:"toile"},
+      {label:"Beach cottage, salt air, bare feet",key:"coast",tx:"seagrass"},
+      {label:"Art opening + late supper",key:"moody",tx:"jewel_velvet"},
+      {label:"Gallery walk + minimalist café",key:"minim",tx:"marble"},
+      {label:"Jazz bar + cocktail hour",key:"deco",tx:"art_deco"},
+    ]
+  },
+  {
+    text:"Your ideal color story is…",
+    tiles:[
+      {label:"Butter, blush & hunter green",key:"grand",tx:"warm_linen"},
+      {label:"Chambray, sand & sea-foam",key:"coast",tx:"grasscloth"},
+      {label:"Midnight, plum & forest",key:"moody",tx:"jewel_velvet"},
+      {label:"Ivory, putty & concrete",key:"minim",tx:"concrete"},
+    ]
+  },
+  {
+    text:"A pattern that speaks to you is…",
+    tiles:[
+      {label:"A damask with a secret",key:"grand",tx:"damask"},
+      {label:"Woven grasscloth",key:"coast",tx:"grasscloth"},
+      {label:"Overscale moody floral",key:"moody",tx:"dark_floral"},
+      {label:"Structural herringbone",key:"minim",tx:"herringbone"},
+      {label:"Art Deco fan repeat",key:"deco",tx:"art_deco"},
+    ]
+  },
+  {
+    text:"The texture you want underfoot (and all around) is…",
+    tiles:[
+      {label:"Woven wool & aged leather",key:"grand",tx:"warm_linen"},
+      {label:"Jute, linen & seagrass",key:"coast",tx:"seagrass"},
+      {label:"Velvet & lacquered wood",key:"moody",tx:"jewel_velvet"},
+      {label:"Polished plaster & stone",key:"minim",tx:"concrete"},
+    ]
+  },
+  {
+    text:"Your shelfie includes…",
+    tiles:[
+      {label:"Chinoiserie & botanical prints",key:"grand",tx:"chinoiserie"},
+      {label:"Driftwood, shells, woven baskets",key:"coast",tx:"grasscloth"},
+      {label:"Vintage globes & bookshelves floor-to-ceiling",key:"moody",tx:"tropical_leaf"},
+      {label:"One perfect object on white plaster",key:"minim",tx:"ink_brush"},
+      {label:"Black lacquer & gilded mirrors",key:"deco",tx:"art_deco"},
+    ]
+  },
+];
+
+/* ═══════════════════════════════════════════════════════
+   ARCHETYPES
+═══════════════════════════════════════════════════════ */
+const ARCHETYPES = {
+  grand:{
+    name:"Grandmillennial",
+    desc:"You collect beauty with intention. Your rooms layer pattern on pattern — a toile here, a damask there — wrapped in heirlooms that earned their place. The past is not nostalgia; it's authority.",
+    palette:[
+      {name:"Dusty Rose",sw:"dusty_rose"},
+      {name:"Hunter Green",sw:"sage_green"},
+      {name:"Antique Ivory",sw:"warm_cream"},
+      {name:"Antique Gold",sw:"antique_gold"},
+      {name:"Navy Dusk",sw:"navy_dusk"},
+    ],
+    cards:[
+      {sku:"DW-GM-001",name:"Salon Damask",desc:"Grand-scale ivory & gold repeat on cream ground.",tx:"gm_damask",rc:"gm_damask"},
+      {sku:"DW-GM-002",name:"Rose Chintz",desc:"Hand-drawn rosa in blush, sage & gold.",tx:"floral_vintage",rc:"gm_floral"},
+      {sku:"DW-GM-003",name:"Toile de Branche",desc:"Classic pastoral scene in sepia on ecru.",tx:"toile",rc:"gm_toile"},
+    ]
+  },
+  coast:{
+    name:"Coastal Calm",
+    desc:"Rooms that breathe. You favor the unhurried quiet of morning light on linen — honest materials, soft palette, the sense that salt air could blow through at any moment.",
+    palette:[
+      {name:"Sea-foam",sw:"seafoam"},
+      {name:"Sand",sw:"sand"},
+      {name:"Sky",sw:"sky"},
+      {name:"White Linen",sw:"white_linen"},
+      {name:"Sage",sw:"sage_green"},
+    ],
+    cards:[
+      {sku:"DW-CC-001",name:"Natural Grasscloth",desc:"Hand-woven seagrass on paper backing, dune colorway.",tx:"grasscloth",rc:"cc_grasscloth"},
+      {sku:"DW-CC-002",name:"Coastal Linen",desc:"Woven linen texture in chambray and bleached sand.",tx:"coastal_linen",rc:"cc_linen"},
+      {sku:"DW-CC-003",name:"Seagrass Weave",desc:"Dense jute-effect textile in natural ecru.",tx:"seagrass",rc:"cc_seagrass"},
+    ]
+  },
+  moody:{
+    name:"Moody Maximalist",
+    desc:"You dress your walls the way others dress for a party — all in. Rich, layered, unapologetic. Dark grounds, overscale florals, jewel-toned botanicals that demand you look closer.",
+    palette:[
+      {name:"Midnight Plum",sw:"midnight_plum"},
+      {name:"Wine",sw:"wine"},
+      {name:"Emerald",sw:"emerald"},
+      {name:"Antique Gold",sw:"antique_gold"},
+      {name:"Dusty Rose",sw:"dusty_rose"},
+    ],
+    cards:[
+      {sku:"DW-MM-001",name:"Nocturne Floral",desc:"Overscale blooms on near-black ground.",tx:"dark_floral",rc:"mm_darkfloral"},
+      {sku:"DW-MM-002",name:"Jungle Dark",desc:"Tropical leaf drama in deep forest ink.",tx:"tropical_leaf",rc:"mm_tropical"},
+      {sku:"DW-MM-003",name:"Velvet Jewel",desc:"Tone-on-tone velvet texture, midnight with gold fleck.",tx:"jewel_velvet",rc:"mm_velvet"},
+    ]
+  },
+  minim:{
+    name:"Modern Minimalist",
+    desc:"Restraint as sophistication. You believe in the eloquence of a single perfect material — a plaster wall, a linen weave — where silence carries more weight than decoration.",
+    palette:[
+      {name:"Warm White",sw:"white_linen"},
+      {name:"Putty",sw:"warm_cream"},
+      {name:"Charcoal",sw:"charcoal"},
+      {name:"Ivory",sw:"ivory"},
+      {name:"Sand",sw:"sand"},
+    ],
+    cards:[
+      {sku:"DW-MN-001",name:"Paper Linen",desc:"Ultra-fine linen weave in oat and warm white.",tx:"warm_linen",rc:"mn_linen"},
+      {sku:"DW-MN-002",name:"Studio Geo",desc:"Tonal checkerboard in putty and warm ivory.",tx:"geo_minimal",rc:"mn_geo"},
+      {sku:"DW-MN-003",name:"Roman Plaster",desc:"Artisan-troweled plaster effect, cool greige.",tx:"concrete",rc:"mn_concrete"},
+    ]
+  },
+  deco:{
+    name:"Art Deco Glamour",
+    desc:"You are drawn to rooms that announce themselves — geometric precision, lacquered surfaces, gold against black. Every choice is intentional and unapologetically theatrical.",
+    palette:[
+      {name:"Deco Black",sw:"deco_black"},
+      {name:"Champagne",sw:"champagne"},
+      {name:"Antique Gold",sw:"antique_gold"},
+      {name:"Ivory",sw:"ivory"},
+      {name:"Emerald",sw:"emerald"},
+    ],
+    cards:[
+      {sku:"DW-AD-001",name:"Lalique Fan",desc:"Art Deco fan repeat in gold on lacquer black.",tx:"art_deco",rc:"ad_deco"},
+      {sku:"DW-AD-002",name:"Athens Key",desc:"Greek key border in polished gold on near-black.",tx:"greek_key",rc:"ad_greek"},
+      {sku:"DW-AD-003",name:"Carrara Luxe",desc:"Polished marble simulation in ivory and gold veining.",tx:"marble",rc:"ad_marble"},
+    ]
+  },
+};
+
+/* ═══════════════════════════════════════════════════════
+   QUIZ STATE
+═══════════════════════════════════════════════════════ */
+let qIdx = 0;
+let scores = {grand:0,coast:0,moody:0,minim:0,deco:0};
+let currentResult = null;
+
+function startQuiz(){
+  qIdx = 0;
+  scores = {grand:0,coast:0,moody:0,minim:0,deco:0};
+  showScreen('quiz');
+  renderQuestion(0,false);
+}
+
+function showScreen(id){
+  document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
+  document.getElementById(id).classList.add('active');
+  window.scrollTo(0,0);
+}
+
+function renderQuestion(idx, animate){
+  const q = QUESTIONS[idx];
+  const pct = ((idx)/QUESTIONS.length)*100;
+  document.getElementById('q-counter').textContent = `Question ${idx+1} of ${QUESTIONS.length}`;
+  document.getElementById('progress-fill').style.width = `${pct}%`;
+  document.querySelector('.progress-track').setAttribute('aria-valuenow', idx+1);
+
+  const wrap = document.getElementById('q-wrap');
+  if(animate){
+    wrap.classList.add('fade-in');
+    requestAnimationFrame(()=>{
+      requestAnimationFrame(()=>wrap.classList.add('show'));
+    });
+  }
+
+  wrap.innerHTML = `
+    <div class="q-label">Question ${idx+1}</div>
+    <div class="q-text">${q.text}</div>
+    <div class="tiles" role="listbox" aria-label="${q.text}">
+      ${q.tiles.map((t,i)=>`
+        <button class="tile" role="option" aria-selected="false" data-key="${t.key}" data-idx="${i}"
+          tabindex="${i===0?0:-1}" onclick="pickTile(event,'${t.key}')"
+          onkeydown="tileKeydown(event)">
+          <div class="tile-img">${TX[t.tx] ? TX[t.tx]() : ''}</div>
+          <div class="tile-label">${t.label}</div>
+        </button>
+      `).join('')}
+    </div>
+  `;
+}
+
+function tileKeydown(e){
+  const tiles = [...e.currentTarget.closest('.tiles').querySelectorAll('.tile')];
+  const cur = tiles.indexOf(e.currentTarget);
+  if(e.key==='ArrowRight'||e.key==='ArrowDown'){
+    e.preventDefault();
+    const next = tiles[(cur+1)%tiles.length];
+    tiles.forEach(t=>t.tabIndex=-1); next.tabIndex=0; next.focus();
+  } else if(e.key==='ArrowLeft'||e.key==='ArrowUp'){
+    e.preventDefault();
+    const prev = tiles[(cur-1+tiles.length)%tiles.length];
+    tiles.forEach(t=>t.tabIndex=-1); prev.tabIndex=0; prev.focus();
+  } else if(e.key===' '||e.key==='Enter'){
+    e.preventDefault();
+    pickTile(e, e.currentTarget.dataset.key);
+  }
+}
+
+function pickTile(e, key){
+  const tile = e.currentTarget||e.target.closest('.tile');
+  // Visual selection
+  document.querySelectorAll('.tile').forEach(t=>{
+    t.classList.remove('selected');
+    t.setAttribute('aria-selected','false');
+  });
+  tile.classList.add('selected');
+  tile.setAttribute('aria-selected','true');
+
+  scores[key] = (scores[key]||0) + 1;
+
+  setTimeout(()=>{
+    const wrap = document.getElementById('q-wrap');
+    wrap.classList.add('fade-out');
+    setTimeout(()=>{
+      wrap.classList.remove('fade-out','fade-in','show');
+      qIdx++;
+      if(qIdx >= QUESTIONS.length){
+        showResult();
+      } else {
+        renderQuestion(qIdx, true);
+      }
+    }, 260);
+  }, 380);
+}
+
+/* ═══════════════════════════════════════════════════════
+   RESULT
+═══════════════════════════════════════════════════════ */
+function showResult(){
+  // find winner
+  let winner='grand', best=0;
+  for(const k in scores){ if(scores[k]>best){best=scores[k];winner=k;} }
+  currentResult = ARCHETYPES[winner];
+  currentResult._key = winner;
+
+  document.getElementById('res-name').textContent = currentResult.name;
+  document.getElementById('res-desc').textContent = currentResult.desc;
+
+  // Palette
+  const pal = document.getElementById('res-palette');
+  pal.innerHTML = currentResult.palette.map(s=>`
+    <div class="swatch">
+      <div class="swatch-box">${SW[s.sw] ? SW[s.sw]() : ''}</div>
+      <div class="swatch-name">${s.name}</div>
+    </div>
+  `).join('');
+
+  // Rec cards
+  const cards = document.getElementById('res-cards');
+  cards.innerHTML = currentResult.cards.map(c=>`
+    <div class="rec-card">
+      <div class="rec-card-img">${RC[c.rc] ? RC[c.rc]() : ''}</div>
+      <div class="rec-card-body">
+        <div class="rec-card-sku">${c.sku}</div>
+        <div class="rec-card-name">${c.name}</div>
+        <div class="rec-card-desc">${c.desc}</div>
+      </div>
+    </div>
+  `).join('');
+
+  showScreen('result');
+  // trigger reveal after paint
+  const ri = document.getElementById('result-inner');
+  ri.classList.remove('revealed');
+  requestAnimationFrame(()=>requestAnimationFrame(()=>ri.classList.add('revealed')));
+}
+
+function retake(){
+  document.getElementById('result-inner').classList.remove('revealed');
+  qIdx=0; scores={grand:0,coast:0,moody:0,minim:0,deco:0};
+  showScreen('intro');
+}
+
+/* ═══════════════════════════════════════════════════════
+   DOWNLOAD — canvas-based PNG export (data: URI only)
+═══════════════════════════════════════════════════════ */
+function openDownload(){
+  if(!currentResult) return;
+  const modal = document.getElementById('dl-modal');
+  modal.classList.add('open');
+  modal.setAttribute('aria-hidden','false');
+  renderExportCard().then(dataUrl=>{
+    const prev = document.getElementById('modal-preview');
+    prev.innerHTML = `<img src="${dataUrl}" alt="Your ${currentResult.name} style card" style="width:100%;height:100%;object-fit:cover;display:block;border-radius:8px">`;
+    document.getElementById('dl-btn').dataset.url = dataUrl;
+  });
+  // trap focus to first focusable
+  setTimeout(()=>{
+    const closeBtn = modal.querySelector('.btn-close');
+    if(closeBtn) closeBtn.focus();
+  }, 50);
+}
+
+function closeDownload(){
+  const modal = document.getElementById('dl-modal');
+  modal.classList.remove('open');
+  modal.setAttribute('aria-hidden','true');
+  document.getElementById('modal-preview').innerHTML='';
+  document.getElementById('dl-btn').dataset.url='';
+}
+
+function downloadCard(){
+  const url = document.getElementById('dl-btn').dataset.url;
+  if(!url) return;
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = `dw-${(currentResult._key||'style')}-style-card.png`;
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+}
+
+function renderExportCard(){
+  return new Promise(resolve=>{
+    const c = document.getElementById('export-canvas');
+    const ctx = c.getContext('2d');
+    const W=1080, H=1350;
+    ctx.clearRect(0,0,W,H);
+
+    // Background
+    ctx.fillStyle = '#0d0b09';
+    ctx.fillRect(0,0,W,H);
+
+    // Subtle texture overlay via noise lines
+    ctx.globalAlpha=0.06;
+    for(let y=0;y<H;y+=4){
+      ctx.fillStyle = y%8===0?'#c9a961':'#ffffff';
+      ctx.fillRect(0,y,W,1);
+    }
+    ctx.globalAlpha=1;
+
+    // Gold border
+    ctx.strokeStyle='#c9a961';
+    ctx.lineWidth=3;
+    ctx.strokeRect(28,28,W-56,H-56);
+    ctx.lineWidth=1;
+    ctx.strokeStyle='rgba(201,169,97,.4)';
+    ctx.strokeRect(40,40,W-80,H-80);
+
+    // WORDMARK
+    ctx.fillStyle='#c9a961';
+    ctx.textAlign='center';
+    ctx.font='400 28px Georgia,serif';
+    ctx.letterSpacing='8px';
+    ctx.fillText('DESIGNER WALLCOVERINGS', W/2, 110);
+
+    // Divider
+    ctx.strokeStyle='rgba(201,169,97,.5)';
+    ctx.lineWidth=1;
+    ctx.beginPath(); ctx.moveTo(200,138); ctx.lineTo(W-200,138); ctx.stroke();
+
+    // "Your Style"
+    ctx.fillStyle='rgba(201,169,97,.7)';
+    ctx.font='300 22px Georgia,serif';
+    ctx.letterSpacing='6px';
+    ctx.fillText('YOUR SIGNATURE STYLE', W/2, 195);
+
+    // Archetype name
+    ctx.fillStyle='#f4f1ea';
+    ctx.font='400 96px Georgia,serif';
+    ctx.letterSpacing='2px';
+    ctx.fillText(currentResult.name, W/2, 320);
+
+    // Description
+    ctx.fillStyle='rgba(244,241,234,.7)';
+    ctx.font='300 28px Georgia,serif';
+    ctx.letterSpacing='0px';
+    const words = currentResult.desc.split(' ');
+    let line='', lineY=410, lineH=44, maxW=860;
+    for(const w of words){
+      const test = line ? line+' '+w : w;
+      if(ctx.measureText(test).width > maxW && line){
+        ctx.fillText(line, W/2, lineY); lineY+=lineH; line=w;
+      } else { line=test; }
+    }
+    if(line) ctx.fillText(line, W/2, lineY);
+
+    // Palette label
+    ctx.fillStyle='rgba(201,169,97,.8)';
+    ctx.font='300 18px Georgia,serif';
+    ctx.letterSpacing='5px';
+    ctx.fillText('SIGNATURE PALETTE', W/2, 570);
+
+    // Palette swatches (canvas drawn)
+    const swatchColors = {
+      dusty_rose:'#d4a0a8', sage_green:'#8aaa88', navy_dusk:'#1e3050',
+      warm_cream:'#f0e8d0', antique_gold:'#c9a961', midnight_plum:'#2d0d3a',
+      wine:'#6b1a2a', emerald:'#1a4a30', seafoam:'#a8d4c8', sand:'#d4c4a0',
+      sky:'#b8d4e8', white_linen:'#f0ede4', charcoal:'#2a2824',
+      ivory:'#f8f4ec', deco_black:'#0d0b09', champagne:'#e8d4a0',
+    };
+    const pal = currentResult.palette;
+    const sw=110, sh=140, gap=18;
+    const totalW = pal.length*(sw+gap)-gap;
+    let sx = (W-totalW)/2;
+    for(const p of pal){
+      const col = swatchColors[p.sw] || '#888';
+      ctx.fillStyle=col;
+      roundRect(ctx, sx, 590, sw, sh, 10);
+      ctx.fill();
+      ctx.strokeStyle='rgba(255,255,255,.15)';
+      ctx.lineWidth=1;
+      roundRect(ctx, sx, 590, sw, sh, 10);
+      ctx.stroke();
+      ctx.fillStyle='rgba(244,241,234,.7)';
+      ctx.font='300 16px Georgia,serif';
+      ctx.letterSpacing='0px';
+      ctx.fillText(p.name, sx+sw/2, 590+sh+24);
+      sx+=sw+gap;
+    }
+
+    // Pattern Cards label
+    ctx.fillStyle='rgba(201,169,97,.8)';
+    ctx.font='300 18px Georgia,serif';
+    ctx.letterSpacing='5px';
+    ctx.fillText('RECOMMENDED PATTERNS', W/2, 820);
+
+    // Pattern cards (simple color blocks with label)
+    const cardColors={
+      gm_damask:'#f5efd8', gm_floral:'#f0e8d8', gm_toile:'#f8f2e6',
+      cc_grasscloth:'#cfc0a0', cc_linen:'#dce8f0', cc_seagrass:'#c4b478',
+      mm_darkfloral:'#1a1220', mm_tropical:'#162418', mm_velvet:'#1c0830',
+      mn_linen:'#ede8df', mn_geo:'#f5f2ec', mn_concrete:'#d4d0ca',
+      ad_deco:'#0d1f1a', ad_greek:'#1a1408', ad_marble:'#ece8e0',
+    };
+    const cw=280, ch=300, cgap=30;
+    const totalCW=currentResult.cards.length*(cw+cgap)-cgap;
+    let cx2=(W-totalCW)/2;
+    for(const card of currentResult.cards){
+      const bg=cardColors[card.rc]||'#333';
+      ctx.fillStyle=bg;
+      roundRect(ctx, cx2, 848, cw, ch, 12); ctx.fill();
+      ctx.strokeStyle='rgba(201,169,97,.2)'; ctx.lineWidth=1;
+      roundRect(ctx, cx2, 848, cw, ch, 12); ctx.stroke();
+      ctx.fillStyle=bg.startsWith('#1')||bg.startsWith('#0')?'rgba(201,169,97,.8)':'rgba(28,26,23,.8)';
+      ctx.font='300 15px Georgia,serif'; ctx.letterSpacing='3px';
+      ctx.fillText(card.sku, cx2+cw/2, 848+ch-70);
+      ctx.font='400 22px Georgia,serif'; ctx.letterSpacing='0px';
+      ctx.fillText(card.name, cx2+cw/2, 848+ch-42);
+      cx2+=cw+cgap;
+    }
+
+    // Bottom wordmark
+    ctx.strokeStyle='rgba(201,169,97,.5)';
+    ctx.lineWidth=1;
+    ctx.beginPath(); ctx.moveTo(200,H-140); ctx.lineTo(W-200,H-140); ctx.stroke();
+    ctx.fillStyle='rgba(201,169,97,.6)';
+    ctx.font='300 20px Georgia,serif'; ctx.letterSpacing='6px';
+    ctx.fillText('DESIGNERWALLCOVERINGS.COM', W/2, H-100);
+    ctx.font='300 16px Georgia,serif'; ctx.letterSpacing='3px';
+    ctx.fillStyle='rgba(244,241,234,.4)';
+    ctx.fillText('Find Your Style · Quiz', W/2, H-65);
+
+    resolve(c.toDataURL('image/png'));
+  });
+}
+
+function roundRect(ctx,x,y,w,h,r){
+  ctx.beginPath();
+  ctx.moveTo(x+r,y);
+  ctx.lineTo(x+w-r,y); ctx.arcTo(x+w,y,x+w,y+r,r);
+  ctx.lineTo(x+w,y+h-r); ctx.arcTo(x+w,y+h,x+w-r,y+h,r);
+  ctx.lineTo(x+r,y+h); ctx.arcTo(x,y+h,x,y+h-r,r);
+  ctx.lineTo(x,y+r); ctx.arcTo(x,y,x+r,y,r);
+  ctx.closePath();
+}
+
+// Modal focus trap & ESC close
+document.addEventListener('keydown',e=>{
+  if(e.key==='Escape'){
+    const m=document.getElementById('dl-modal');
+    if(m.classList.contains('open')) closeDownload();
+  }
+  if(e.key==='Tab'){
+    const m=document.getElementById('dl-modal');
+    if(m.classList.contains('open')){
+      const focusable=[...m.querySelectorAll('button,[href],input,[tabindex]:not([tabindex="-1"])')];
+      if(!focusable.length) return;
+      const first=focusable[0], last=focusable[focusable.length-1];
+      if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+      else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+    }
+  }
+});
+</script>
+</body>
+</html>

← 0193528 5x sweep 1: fix Color Story modal — visibility:hidden when c  ·  back to Model Arena  ·  Agent-driven game: DW Swatch Memory (frontend-developer) — 3 dd56000 →