← 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
M data/arcade-games.jsonA public/games/dw-style-quiz.html
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">×</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 →