[object Object]

← back to Wallco Ai

cleanup: delete .bak files, orphan entrypoints, unused @dw/inline-editor dep

53c740ec12e2202ac187c557812b93ea232e5da9 · 2026-05-23 11:42:04 -0700 · Steve Abrams

- delete server.js.bak-ai-rename (15,683 LOC tracked snapshot from May 20)
- delete hot-or-not-server.js (220 LOC) — pm2 entry it referenced no longer
  exists; /hot-or-not routes are served inline by server.js
- delete server-admin.js (642 LOC) — not in ecosystem.config.js, already
  excluded from deploy-kamatera.sh, dead at rest
- delete 6 scripts/*.bak* (pollock, sherman-circus, night-builder ×4)
- delete data/designs.json.bak-20260520-pre-refresh
- delete scripts/__pycache__/spoonflower_lib.cpython-314.pyc
- drop @dw/inline-editor from package.json (declared but never required;
  the JWT comment in src/admin.js:31 is doc-only, not an import)
- mv reports/* docs/ops/ — these are May-19 ops artifacts, belong in docs/
- .gitignore: extend *.bak to *.bak-* (catches .bak-ai-rename style),
  add __pycache__/, *.pyc, logs/*.jsonl

git history preserves everything if any of these need recovery.

Files touched

Diff

commit 53c740ec12e2202ac187c557812b93ea232e5da9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 23 11:42:04 2026 -0700

    cleanup: delete .bak files, orphan entrypoints, unused @dw/inline-editor dep
    
    - delete server.js.bak-ai-rename (15,683 LOC tracked snapshot from May 20)
    - delete hot-or-not-server.js (220 LOC) — pm2 entry it referenced no longer
      exists; /hot-or-not routes are served inline by server.js
    - delete server-admin.js (642 LOC) — not in ecosystem.config.js, already
      excluded from deploy-kamatera.sh, dead at rest
    - delete 6 scripts/*.bak* (pollock, sherman-circus, night-builder ×4)
    - delete data/designs.json.bak-20260520-pre-refresh
    - delete scripts/__pycache__/spoonflower_lib.cpython-314.pyc
    - drop @dw/inline-editor from package.json (declared but never required;
      the JWT comment in src/admin.js:31 is doc-only, not an import)
    - mv reports/* docs/ops/ — these are May-19 ops artifacts, belong in docs/
    - .gitignore: extend *.bak to *.bak-* (catches .bak-ai-rename style),
      add __pycache__/, *.pyc, logs/*.jsonl
    
    git history preserves everything if any of these need recovery.
---
 .gitignore                                         |     8 +
 data/designs.json.bak-20260520-pre-refresh         |   212 -
 .../ops}/dw-stale-urls-heal-A-2026-05-19.md        |     0
 .../ops}/dw-stale-urls-push-A2-2026-05-19.md       |     0
 .../ops}/dw-stale-urls-rescrape-2026-05-19.md      |     0
 .../ops}/dw-stale-urls-trim-B-2026-05-19.md        |     0
 .../dw-stale-urls-trim-B-decision-2026-05-19.md    |     0
 hot-or-not-server.js                               |   220 -
 package-lock.json                                  |     6 +-
 package.json                                       |     1 -
 .../__pycache__/spoonflower_lib.cpython-314.pyc    |   Bin 13765 -> 0 bytes
 scripts/generate-pollock.sh.bak                    |    46 -
 scripts/generate-sherman-circus.sh.bak             |    75 -
 scripts/night-builder.js.bak                       |   906 --
 scripts/night-builder.js.bak-vendor-final          |   906 --
 scripts/night-builder.js.bak-vendor-scrub          |   906 --
 scripts/night-builder.js.bak-vendor-scrub2         |   906 --
 server-admin.js                                    |   642 -
 server.js.bak-ai-rename                            | 15683 -------------------
 19 files changed, 9 insertions(+), 20508 deletions(-)

diff --git a/.gitignore b/.gitignore
index b4ad035..43477b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,14 @@ tmp/
 .DS_Store
 dist/
 build/
+# backup variants (e.g. .bak-ai-rename, .bak-vendor-scrub) — plain *.bak alone misses these
+*.bak
+*.bak-*
+# python bytecode
+__pycache__/
+*.pyc
+# runtime queues / append-only logs (logs/pm2.* are pm2's, hon-reasons-queue is the votes queue)
+logs/*.jsonl
 data/images/
 data/generated/
 data/generated_pre_seamless_backup/
diff --git a/data/designs.json.bak-20260520-pre-refresh b/data/designs.json.bak-20260520-pre-refresh
deleted file mode 100644
index 3246dee..0000000
--- a/data/designs.json.bak-20260520-pre-refresh
+++ /dev/null
@@ -1,212 +0,0 @@
-[{"id":10457,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Trowelled-plaster-wall wallpaper with roses — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. Rose style: full-open garden roses in symmetric pairs with their stems crossing at the panel center. Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. EXACTLY TWO COLORS in the entire image — the ground tone, and the figure ink. ZERO third tones. ABSOLUTELY NO bleed between layers — bleed between layers LOOKS CHEAP and is FORBIDDEN. Single-layer screen-print only. NO ghost layer, NO faded background motifs, NO atmospheric perspective, NO depth, NO secondary layer of lighter copies behind the main figures. Every motif sits at 100% opacity directly on the ground. The ground is one completely uniform flat solid color from edge to edge — no texture, no mottling, no shadows, no second-tone elements behind anything. Reference aesthetic: SCREEN-PRINTED SERIGRAPH POSTER — Toulouse-Lautrec Moulin Rouge poster, WPA national-parks poster, Saul Bass film poster, pochoir stencil book illustration, single-pass silkscreen with two ink screens (one ground + one figure). Hard clean razor-sharp edges. No half-tones, no chromatic gradient, no fade, no watercolor wash, no airbrush, no crosshatch shading, no engraved line-shading. Every figure region is one flat color. Strictly NOT Haeckel, NOT Merian natural-history-plate, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar, NOT 3D-rendered. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":658103787,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10457","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779266530421_658103787.png","dominant_hex":"#dbd5c9","palette":[{"hex": "#dbd5c9", "pct": 26.99}, {"hex": "#787570", "pct": 25.52}, {"hex": "#bdb7ac", "pct": 21.04}, {"hex": "#9f9990", "pct": 14.97}, {"hex": "#87837c", "pct": 11.48}],"motifs":null,"tags":null,"category":"throttled-plaster-roses","is_published":true,"notes":null,"created_at":"2026-05-20T08:45:27.307395+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010457","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10449,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Trowelled-plaster-wall wallpaper with roses — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. Rose style: full-open garden roses in symmetric pairs with their stems crossing at the panel center. Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. EXACTLY TWO COLORS in the entire image — the ground tone, and the figure ink. ZERO third tones. ABSOLUTELY NO bleed between layers — bleed between layers LOOKS CHEAP and is FORBIDDEN. Single-layer screen-print only. NO ghost layer, NO faded background motifs, NO atmospheric perspective, NO depth, NO secondary layer of lighter copies behind the main figures. Every motif sits at 100% opacity directly on the ground. The ground is one completely uniform flat solid color from edge to edge — no texture, no mottling, no shadows, no second-tone elements behind anything. Reference aesthetic: SCREEN-PRINTED SERIGRAPH POSTER — Toulouse-Lautrec Moulin Rouge poster, WPA national-parks poster, Saul Bass film poster, pochoir stencil book illustration, single-pass silkscreen with two ink screens (one ground + one figure). Hard clean razor-sharp edges. No half-tones, no chromatic gradient, no fade, no watercolor wash, no airbrush, no crosshatch shading, no engraved line-shading. Every figure region is one flat color. Strictly NOT Haeckel, NOT Merian natural-history-plate, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar, NOT 3D-rendered. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":307049542,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10449","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779266395352_307049542.png","dominant_hex":"#666462","palette":[{"hex": "#666462", "pct": 32.27}, {"hex": "#b1a99e", "pct": 21.66}, {"hex": "#c8c0b5", "pct": 16.76}, {"hex": "#7f7b75", "pct": 16.11}, {"hex": "#d5cdc3", "pct": 13.21}],"motifs":null,"tags":null,"category":"throttled-plaster-roses","is_published":true,"notes":null,"created_at":"2026-05-20T08:42:04.994709+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010449","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10447,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Vertical stripe wallpaper — Linen Cream colorway. Ground: solid soft-cream (#EDE4D3). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. EXACTLY TWO COLORS in the entire image — the ground tone, and the figure ink. ZERO third tones. ABSOLUTELY NO bleed between layers — bleed between layers LOOKS CHEAP and is FORBIDDEN. Single-layer screen-print only. NO ghost layer, NO faded background motifs, NO atmospheric perspective, NO depth, NO secondary layer of lighter copies behind the main figures. Every motif sits at 100% opacity directly on the ground. The ground is one completely uniform flat solid color from edge to edge — no texture, no mottling, no shadows, no second-tone elements behind anything. Reference aesthetic: SCREEN-PRINTED SERIGRAPH POSTER — Toulouse-Lautrec Moulin Rouge poster, WPA national-parks poster, Saul Bass film poster, pochoir stencil book illustration, single-pass silkscreen with two ink screens (one ground + one figure). Hard clean razor-sharp edges. No half-tones, no chromatic gradient, no fade, no watercolor wash, no airbrush, no crosshatch shading, no engraved line-shading. Every figure region is one flat color. Strictly NOT Haeckel, NOT Merian natural-history-plate, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar, NOT 3D-rendered. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1878353623,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10447","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779266316922_1878353623.png","dominant_hex":"#afa89e","palette":[{"hex": "#afa89e", "pct": 24.65}, {"hex": "#9c958a", "pct": 24.24}, {"hex": "#c1bbb1", "pct": 20.73}, {"hex": "#cfc9c0", "pct": 16.69}, {"hex": "#d7d2c9", "pct": 13.68}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:39:49.954124+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010447","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10434,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Cactus Repeat — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. Sonoran desert cacti as FLAT STENCIL SILHOUETTES: tall saguaro forms with raised arms, scattered prickly-pear paddles, arranged in a tessellated seamless wallpaper repeat. Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. All cacti upright, oriented vertically, symmetric placement on the vertical axis. Edge-to-edge tessellation, generous breathing room of clean ground between motifs. EXACTLY TWO COLORS in the entire image — the ground tone, and the figure ink. ZERO third tones. ABSOLUTELY NO bleed between layers — bleed between layers LOOKS CHEAP and is FORBIDDEN. Single-layer screen-print only. NO ghost layer, NO faded background motifs, NO atmospheric perspective, NO depth, NO secondary layer of lighter copies behind the main figures. Every motif sits at 100% opacity directly on the ground. The ground is one completely uniform flat solid color from edge to edge. Reference: Toulouse-Lautrec Moulin Rouge poster, WPA national-parks poster, Saul Bass film poster, single-pass silkscreen with two ink screens. Hard clean razor-sharp edges. Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate. Square aspect ratio, seamless tile repeat, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":953300827,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10434","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265971843_953300827.png","dominant_hex":"#a0977f","palette":[{"hex": "#a0977f", "pct": 30.87}, {"hex": "#9f967f", "pct": 20.2}, {"hex": "#5e5a55", "pct": 19.16}, {"hex": "#958e7d", "pct": 16.45}, {"hex": "#aca697", "pct": 13.32}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:34:37.215972+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010434","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10419,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Drunk Zoo 36\" repeat wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of red pandas in symmetric grooming pose, central bottle between them. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":994678211,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10419","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265477121_994678211.png","dominant_hex":"#212222","palette":[{"hex": "#212222", "pct": 30.93}, {"hex": "#afa9a2", "pct": 24.07}, {"hex": "#ccc6bf", "pct": 17.1}, {"hex": "#5c5a57", "pct": 15.46}, {"hex": "#ddd8d1", "pct": 12.44}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:27:52.02312+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010419","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10416,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Vertical stripe wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":296599484,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10416","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265403382_296599484.png","dominant_hex":"#75624b","palette":[{"hex": "#75624b", "pct": 24.56}, {"hex": "#a4957f", "pct": 23.03}, {"hex": "#c2b7a4", "pct": 22.28}, {"hex": "#d1c8b8", "pct": 15.24}, {"hex": "#d9d2c3", "pct": 14.89}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:24:32.033864+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010416","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10415,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Drunk Zoo 36\" repeat wallpaper — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1907367644,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10415","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265274977_1907367644.png","dominant_hex":"#e6e0ce","palette":[{"hex": "#e6e0ce", "pct": 32.18}, {"hex": "#645b4d", "pct": 19.15}, {"hex": "#887d6b", "pct": 18.19}, {"hex": "#9b907e", "pct": 17.24}, {"hex": "#c6beab", "pct": 13.24}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:23:18.277384+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010415","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10407,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: dense agave rosettes with whip-stem ocotillo verticals, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2193583,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10407","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265256248_2193583.png","dominant_hex":"#c7c1af","palette":[{"hex": "#c7c1af", "pct": 25.9}, {"hex": "#797b6c", "pct": 25.84}, {"hex": "#777a6a", "pct": 25.51}, {"hex": "#969584", "pct": 18.92}, {"hex": "#727466", "pct": 3.83}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:21:04.652459+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010407","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10406,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":193541660,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10406","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265242730_193541660.png","dominant_hex":"#443d33","palette":[{"hex": "#443d33", "pct": 28.08}, {"hex": "#c6b493", "pct": 23.9}, {"hex": "#8e7f65", "pct": 17.92}, {"hex": "#e3d6b9", "pct": 15.62}, {"hex": "#dbcdaf", "pct": 14.47}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:20:51.162953+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010406","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10405,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2011411258,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10405","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265228194_2011411258.png","dominant_hex":"#bcb398","palette":[{"hex": "#bcb398", "pct": 27.17}, {"hex": "#bfb69e", "pct": 21.49}, {"hex": "#b9ae92", "pct": 20.39}, {"hex": "#c1b8a1", "pct": 17.68}, {"hex": "#c3bba4", "pct": 13.27}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:20:37.653739+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010405","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10404,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) single-ink. Motif: mountain-and-temple silhouette repeats with stylized scholar-cloud forms, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":179713475,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10404","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265214900_179713475.png","dominant_hex":"#6f5239","palette":[{"hex": "#6f5239", "pct": 27.81}, {"hex": "#d2c3ad", "pct": 25.18}, {"hex": "#957d66", "pct": 20.4}, {"hex": "#735339", "pct": 18.99}, {"hex": "#694a32", "pct": 7.62}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:20:23.121634+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010404","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10403,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1729701288,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10403","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265200669_1729701288.png","dominant_hex":"#b9ae91","palette":[{"hex": "#b9ae91", "pct": 26.18}, {"hex": "#bfb69b", "pct": 24.52}, {"hex": "#ada07e", "pct": 19.24}, {"hex": "#c3baa0", "pct": 16.73}, {"hex": "#c5bca2", "pct": 13.33}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:20:09.82714+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010403","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10402,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1203190215,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10402","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265187114_1203190215.png","dominant_hex":"#888880","palette":[{"hex": "#888880", "pct": 36.82}, {"hex": "#8a8a81", "pct": 24.8}, {"hex": "#54524c", "pct": 13.8}, {"hex": "#bebcb2", "pct": 12.7}, {"hex": "#84837b", "pct": 11.88}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:19:55.595052+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010402","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10399,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure: bone-ivory (#F2EADB) single-ink. Motif: lotus blossom roundels in vertical stacks separated by bamboo verticals, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1886663018,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10399","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265173191_1886663018.png","dominant_hex":"#9d7b4d","palette":[{"hex": "#9d7b4d", "pct": 27.4}, {"hex": "#8d6d41", "pct": 25.72}, {"hex": "#6f5433", "pct": 20.94}, {"hex": "#b99b71", "pct": 13.19}, {"hex": "#dbc7a6", "pct": 12.74}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:19:42.041549+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010399","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10396,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1805601154,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10396","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265159503_1805601154.png","dominant_hex":"#7c786f","palette":[{"hex": "#7c786f", "pct": 26.56}, {"hex": "#40403d", "pct": 25.87}, {"hex": "#afa99f", "pct": 21.34}, {"hex": "#5f5d56", "pct": 15.31}, {"hex": "#4b4a46", "pct": 10.92}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:19:28.115882+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010396","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10394,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":549573952,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10394","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265146083_549573952.png","dominant_hex":"#36342f","palette":[{"hex": "#36342f", "pct": 27.28}, {"hex": "#625d52", "pct": 21.41}, {"hex": "#9c917d", "pct": 21.12}, {"hex": "#bbae96", "pct": 17.22}, {"hex": "#c8baa0", "pct": 12.98}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:19:14.421922+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010394","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10392,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Stone Pewter colorway. Ground: solid cool-pewter (#7A736A). Figure: bone-ivory (#F2EADB) single-ink. Motif: kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1252168110,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10392","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265132182_1252168110.png","dominant_hex":"#5f5f57","palette":[{"hex": "#5f5f57", "pct": 26.91}, {"hex": "#a59d8e", "pct": 23.87}, {"hex": "#857f72", "pct": 21.39}, {"hex": "#bbb2a3", "pct": 16.36}, {"hex": "#cec7b7", "pct": 11.47}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:19:00.978294+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010392","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10391,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink. Motif: lotus blossom roundels in vertical stacks separated by bamboo verticals, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":998032082,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10391","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265116313_998032082.png","dominant_hex":"#afa691","palette":[{"hex": "#afa691", "pct": 28.25}, {"hex": "#807c6b", "pct": 26.56}, {"hex": "#d3cab6", "pct": 20.98}, {"hex": "#8d8774", "pct": 14.18}, {"hex": "#7c7766", "pct": 10.02}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:18:46.706225+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010391","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10390,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1590514855,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10390","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265102355_1590514855.png","dominant_hex":"#b8ab91","palette":[{"hex": "#b8ab91", "pct": 26.4}, {"hex": "#b09f81", "pct": 21.79}, {"hex": "#beb39b", "pct": 20.54}, {"hex": "#c3b9a3", "pct": 16.22}, {"hex": "#c1b7a0", "pct": 15.06}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:18:31.230942+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010390","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10389,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1750096487,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10389","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265089165_1750096487.png","dominant_hex":"#856e4e","palette":[{"hex": "#856e4e", "pct": 26.39}, {"hex": "#9b8262", "pct": 21.94}, {"hex": "#b09978", "pct": 21.68}, {"hex": "#bda787", "pct": 18.24}, {"hex": "#c8b395", "pct": 11.75}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:18:17.272732+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010389","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10388,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: awning stripe — uniform 6cm vertical bands alternating ground and figure tones. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":310180830,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10388","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265074939_310180830.png","dominant_hex":"#a69775","palette":[{"hex": "#a69775", "pct": 27.91}, {"hex": "#bfb79d", "pct": 22.67}, {"hex": "#b0a385", "pct": 20.79}, {"hex": "#c9c3ac", "pct": 14.9}, {"hex": "#d0cab3", "pct": 13.73}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:18:04.084267+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010388","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10387,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: ocotillo whip-stem fountain pattern with small barrel-cactus accents, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":224521077,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10387","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265061285_224521077.png","dominant_hex":"#453924","palette":[{"hex": "#453924", "pct": 27.01}, {"hex": "#e7d9b9", "pct": 21.61}, {"hex": "#908363", "pct": 19.57}, {"hex": "#d9cbaa", "pct": 17.58}, {"hex": "#e8dbba", "pct": 14.22}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:17:49.867063+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010387","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10386,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Mushroom Taupe colorway. Ground: solid designer-taupe (#8A7E70). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":395963562,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10386","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265048190_395963562.png","dominant_hex":"#8b7d68","palette":[{"hex": "#8b7d68", "pct": 30.53}, {"hex": "#b4a592", "pct": 21.81}, {"hex": "#9a8c78", "pct": 18.22}, {"hex": "#c3b5a2", "pct": 17.48}, {"hex": "#cec1ae", "pct": 11.96}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:17:36.211094+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010386","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10385,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":605406984,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10385","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265034980_605406984.png","dominant_hex":"#d8d0bc","palette":[{"hex": "#d8d0bc", "pct": 24.89}, {"hex": "#c3b8a2", "pct": 22.74}, {"hex": "#a79a84", "pct": 18.92}, {"hex": "#7c6f5d", "pct": 17.49}, {"hex": "#b4a891", "pct": 15.96}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:17:23.118051+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010385","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10383,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of capybaras in symmetric resting pose, central mint julep glass between them. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":350725124,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10383","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265021429_350725124.png","dominant_hex":"#61432a","palette":[{"hex": "#61432a", "pct": 25.52}, {"hex": "#cbbca4", "pct": 25.27}, {"hex": "#9c856a", "pct": 21.93}, {"hex": "#75593f", "pct": 15.26}, {"hex": "#64472d", "pct": 12.03}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:17:09.898833+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010383","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10382,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Linen Cream colorway. Ground: solid soft-cream (#EDE4D3). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":454057369,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10382","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779265008211_454057369.png","dominant_hex":"#d7ceb6","palette":[{"hex": "#d7ceb6", "pct": 27.76}, {"hex": "#beb299", "pct": 27.24}, {"hex": "#8d806a", "pct": 19.51}, {"hex": "#d2c8b0", "pct": 18.79}, {"hex": "#dcd3bb", "pct": 6.7}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:16:56.348183+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010382","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10381,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure: bone-ivory (#F2EADB) single-ink. Motif: pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":369780817,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10381","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264994470_369780817.png","dominant_hex":"#bba279","palette":[{"hex": "#bba279", "pct": 26.25}, {"hex": "#ab926d", "pct": 22.4}, {"hex": "#8d7350", "pct": 21.67}, {"hex": "#b9a078", "pct": 20.05}, {"hex": "#c3af8f", "pct": 9.62}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:16:43.12955+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010381","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10377,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: mother-and-calf giraffe pair, mother arching over the calf, both with bottles. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2062588883,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10377","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264981179_2062588883.png","dominant_hex":"#e3d6c1","palette":[{"hex": "#e3d6c1", "pct": 26.98}, {"hex": "#4e3725", "pct": 22.05}, {"hex": "#a28b72", "pct": 20.33}, {"hex": "#513a28", "pct": 15.92}, {"hex": "#67503b", "pct": 14.72}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:16:29.387723+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010377","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10374,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: ocotillo whip-stem fountain pattern with small barrel-cactus accents, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":521903411,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10374","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264967555_521903411.png","dominant_hex":"#e3dcc7","palette":[{"hex": "#e3dcc7", "pct": 29.29}, {"hex": "#1b1a19", "pct": 27.55}, {"hex": "#6e6b61", "pct": 19.73}, {"hex": "#cfc9b5", "pct": 15.25}, {"hex": "#e5dec8", "pct": 8.17}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:16:16.093108+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010374","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10372,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":199200603,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10372","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264953726_199200603.png","dominant_hex":"#676257","palette":[{"hex": "#676257", "pct": 29.43}, {"hex": "#96917e", "pct": 21.36}, {"hex": "#a5a08b", "pct": 19.58}, {"hex": "#777265", "pct": 18.78}, {"hex": "#b4ae97", "pct": 10.85}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:16:02.466506+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010372","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10371,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: tall saguaro forms with raised arms, scattered prickly-pear paddles, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1539405171,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10371","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264940271_1539405171.png","dominant_hex":"#6c6d5c","palette":[{"hex": "#6c6d5c", "pct": 29.25}, {"hex": "#dedcd8", "pct": 24.66}, {"hex": "#6e6e5e", "pct": 20.86}, {"hex": "#8f8e83", "pct": 18.22}, {"hex": "#6b6b5b", "pct": 7.01}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:15:48.643663+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010371","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10370,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: two African elephant calves in symmetric trunk-raised pose, central wine glass. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2046353301,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10370","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264927049_2046353301.png","dominant_hex":"#353430","palette":[{"hex": "#353430", "pct": 27.38}, {"hex": "#8a8377", "pct": 22.94}, {"hex": "#5d574f", "pct": 21.9}, {"hex": "#aea599", "pct": 15.37}, {"hex": "#cac2b4", "pct": 12.41}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:15:35.189686+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010370","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10369,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) single-ink. Motif: lotus blossom roundels in vertical stacks separated by bamboo verticals, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":952454218,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10369","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264913484_952454218.png","dominant_hex":"#846646","palette":[{"hex": "#846646", "pct": 37.54}, {"hex": "#6b4f34", "pct": 18.74}, {"hex": "#c1a781", "pct": 16.19}, {"hex": "#d5c09d", "pct": 14.23}, {"hex": "#997c58", "pct": 13.31}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:15:21.969151+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010369","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10368,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1885922118,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10368","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264899981_1885922118.png","dominant_hex":"#cfc5b1","palette":[{"hex": "#cfc5b1", "pct": 29.37}, {"hex": "#646059", "pct": 26.07}, {"hex": "#aea695", "pct": 19.06}, {"hex": "#847e73", "pct": 13.94}, {"hex": "#6d6860", "pct": 11.57}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:15:08.401523+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010368","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10367,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":888884966,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10367","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264886445_888884966.png","dominant_hex":"#806547","palette":[{"hex": "#806547", "pct": 26.46}, {"hex": "#b4a183", "pct": 24.62}, {"hex": "#8c7355", "pct": 18.21}, {"hex": "#7d6345", "pct": 16.66}, {"hex": "#7b5f42", "pct": 14.05}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:14:54.906316+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010367","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10365,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":247197730,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10365","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264872573_247197730.png","dominant_hex":"#b4a788","palette":[{"hex": "#b4a788", "pct": 25.92}, {"hex": "#ab9d7b", "pct": 24.58}, {"hex": "#aa9871", "pct": 19.21}, {"hex": "#a79772", "pct": 17.09}, {"hex": "#a3936d", "pct": 13.21}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:14:41.372246+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010365","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10361,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: dense agave rosettes with whip-stem ocotillo verticals, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":933401786,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10361","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264858929_933401786.png","dominant_hex":"#808964","palette":[{"hex": "#808964", "pct": 30.96}, {"hex": "#5b6649", "pct": 22.86}, {"hex": "#eae4cc", "pct": 17.84}, {"hex": "#bab99b", "pct": 14.31}, {"hex": "#e6dfc7", "pct": 14.02}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:14:27.500558+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010361","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10358,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Gucci Cocoa colorway. Ground: solid deep-cocoa (#5C4033). Figure: bone-ivory (#F2EADB) single-ink. Motif: lotus blossom roundels in vertical stacks separated by bamboo verticals, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1101062048,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10358","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264833717_1101062048.png","dominant_hex":"#5b4231","palette":[{"hex": "#5b4231", "pct": 26.28}, {"hex": "#cdb592", "pct": 25.25}, {"hex": "#8e724b", "pct": 21.24}, {"hex": "#664c33", "pct": 18.18}, {"hex": "#59402f", "pct": 9.04}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:14:02.00537+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010358","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10357,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Stone Pewter colorway. Ground: solid cool-pewter (#7A736A). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1080942593,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10357","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264818778_1080942593.png","dominant_hex":"#878173","palette":[{"hex": "#878173", "pct": 29.33}, {"hex": "#b7b09f", "pct": 25.02}, {"hex": "#c0b9a8", "pct": 16.68}, {"hex": "#9d9687", "pct": 15.12}, {"hex": "#c6bfae", "pct": 13.86}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:13:48.635062+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010357","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10356,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink. Motif: pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":327180469,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10356","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264805282_327180469.png","dominant_hex":"#9a9888","palette":[{"hex": "#9a9888", "pct": 37.29}, {"hex": "#979485", "pct": 21.18}, {"hex": "#d1c7ba", "pct": 15.35}, {"hex": "#ada697", "pct": 13.32}, {"hex": "#79746a", "pct": 12.86}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:13:33.695453+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010356","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10355,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":545159787,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10355","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264791882_545159787.png","dominant_hex":"#d2c8b4","palette":[{"hex": "#d2c8b4", "pct": 28.61}, {"hex": "#2e2c29", "pct": 26}, {"hex": "#9c9485", "pct": 18.94}, {"hex": "#5f5a52", "pct": 14.88}, {"hex": "#3d3a35", "pct": 11.57}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:13:20.200755+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010355","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10352,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1914313247,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10352","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264765125_1914313247.png","dominant_hex":"#d2c6a3","palette":[{"hex": "#d2c6a3", "pct": 32.57}, {"hex": "#626a52", "pct": 24.9}, {"hex": "#a29e7e", "pct": 20.61}, {"hex": "#cdc29f", "pct": 17.36}, {"hex": "#d3c8a4", "pct": 4.56}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:12:53.592663+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010352","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10351,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":200019555,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10351","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264751396_200019555.png","dominant_hex":"#a49d8c","palette":[{"hex": "#a49d8c", "pct": 28.02}, {"hex": "#9e9886", "pct": 22.04}, {"hex": "#afa996", "pct": 20.8}, {"hex": "#b9b29f", "pct": 16.19}, {"hex": "#c2bba8", "pct": 12.95}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:12:40.043797+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010351","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10349,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":113835435,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10349","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264724055_113835435.png","dominant_hex":"#b09d7a","palette":[{"hex": "#b09d7a", "pct": 29.39}, {"hex": "#c1b393", "pct": 19.88}, {"hex": "#a28b64", "pct": 18.54}, {"hex": "#9b835c", "pct": 18.33}, {"hex": "#a78f67", "pct": 13.86}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:12:13.242899+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010349","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10348,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure: bone-ivory (#F2EADB) single-ink. Motif: kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1042748324,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10348","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264710836_1042748324.png","dominant_hex":"#8a6c47","palette":[{"hex": "#8a6c47", "pct": 28.87}, {"hex": "#b6966d", "pct": 23.17}, {"hex": "#9e7e57", "pct": 18.76}, {"hex": "#c5a67d", "pct": 18.11}, {"hex": "#d4b993", "pct": 11.09}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:11:58.98434+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010348","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10347,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Gucci Cocoa colorway. Ground: solid deep-cocoa (#5C4033). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1463593859,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10347","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264697098_1463593859.png","dominant_hex":"#c8c0a4","palette":[{"hex": "#c8c0a4", "pct": 24.24}, {"hex": "#8a7a5b", "pct": 22.73}, {"hex": "#613f29", "pct": 20.53}, {"hex": "#67442e", "pct": 16.68}, {"hex": "#613b27", "pct": 15.81}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:11:45.764459+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010347","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10345,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":895817237,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10345","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264669015_895817237.png","dominant_hex":"#c2b9a2","palette":[{"hex": "#c2b9a2", "pct": 22.86}, {"hex": "#9f9070", "pct": 22.59}, {"hex": "#b5aa8f", "pct": 21.82}, {"hex": "#c5bca5", "pct": 19}, {"hex": "#c6bea7", "pct": 13.73}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:11:18.472162+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010345","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10344,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1305131253,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10344","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264655297_1305131253.png","dominant_hex":"#b0a48c","palette":[{"hex": "#b0a48c", "pct": 24.76}, {"hex": "#ac9f85", "pct": 23.38}, {"hex": "#b4ab94", "pct": 20.23}, {"hex": "#b8b09b", "pct": 17.86}, {"hex": "#bcb4a0", "pct": 13.76}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:11:03.941103+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010344","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10343,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1689629960,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10343","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264642079_1689629960.png","dominant_hex":"#d5c6ac","palette":[{"hex": "#d5c6ac", "pct": 26.55}, {"hex": "#95866f", "pct": 25.02}, {"hex": "#bdae94", "pct": 20.92}, {"hex": "#a89980", "pct": 15.35}, {"hex": "#9b8c75", "pct": 12.16}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:10:50.223146+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010343","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10342,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: awning stripe — uniform 6cm vertical bands alternating ground and figure tones. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":995841683,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10342","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264627701_995841683.png","dominant_hex":"#bcb5a3","palette":[{"hex": "#bcb5a3", "pct": 27.24}, {"hex": "#9a9486", "pct": 19.28}, {"hex": "#aca594", "pct": 19.15}, {"hex": "#969083", "pct": 18.51}, {"hex": "#9e9789", "pct": 15.82}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:10:37.007041+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010342","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10341,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Linen Cream colorway. Ground: solid soft-cream (#EDE4D3). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1695141463,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10341","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264613496_1695141463.png","dominant_hex":"#cdc5b0","palette":[{"hex": "#cdc5b0", "pct": 27.94}, {"hex": "#c7bfa7", "pct": 23.59}, {"hex": "#c5b99d", "pct": 20.4}, {"hex": "#bfb498", "pct": 14.12}, {"hex": "#c2b89d", "pct": 13.94}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:10:22.626562+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010341","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10339,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Antique Tan colorway. Ground: solid warm-camel (#A78863). Figure: bone-ivory (#F2EADB) single-ink. Motif: mountain-and-temple silhouette repeats with stylized scholar-cloud forms, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":286996527,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10339","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264599471_286996527.png","dominant_hex":"#af8d5f","palette":[{"hex": "#af8d5f", "pct": 26.84}, {"hex": "#8e6b44", "pct": 22.38}, {"hex": "#c8ae81", "pct": 19.09}, {"hex": "#d5bf93", "pct": 17.64}, {"hex": "#d3bc90", "pct": 14.04}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:10:08.414957+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010339","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10338,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Linen Cream colorway. Ground: solid soft-cream (#EDE4D3). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: mother-and-calf giraffe pair, mother arching over the calf, both with bottles. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1451925041,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10338","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264585969_1451925041.png","dominant_hex":"#aea48b","palette":[{"hex": "#aea48b", "pct": 35.76}, {"hex": "#857b68", "pct": 23.62}, {"hex": "#a19780", "pct": 15.7}, {"hex": "#ebe6d7", "pct": 13.02}, {"hex": "#c6bfac", "pct": 11.9}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:09:54.389701+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010338","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10337,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1461441643,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10337","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264572224_1461441643.png","dominant_hex":"#a59e85","palette":[{"hex": "#a59e85", "pct": 26.59}, {"hex": "#646054", "pct": 20.94}, {"hex": "#8c8571", "pct": 19.71}, {"hex": "#7a7464", "pct": 16.79}, {"hex": "#716c5d", "pct": 15.96}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:09:40.888793+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010337","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10336,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Gucci Cocoa colorway. Ground: solid deep-cocoa (#5C4033). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: fine triple-pinstripe groups separated by wider blank channels. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1063373494,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10336","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264558226_1063373494.png","dominant_hex":"#9e8b6d","palette":[{"hex": "#9e8b6d", "pct": 30.76}, {"hex": "#a9997b", "pct": 22.96}, {"hex": "#66523d", "pct": 19.77}, {"hex": "#c7bca1", "pct": 14.98}, {"hex": "#baad92", "pct": 11.53}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:09:27.142923+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010336","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10332,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Mushroom Taupe colorway. Ground: solid designer-taupe (#8A7E70). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":41092989,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10332","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264516818_41092989.png","dominant_hex":"#a0947c","palette":[{"hex": "#a0947c", "pct": 28.6}, {"hex": "#a89d86", "pct": 21.33}, {"hex": "#988b72", "pct": 20.06}, {"hex": "#ada48d", "pct": 16.98}, {"hex": "#b2aa94", "pct": 13.02}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:08:45.43562+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010332","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10331,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure: bone-ivory (#F2EADB) single-ink. Motif: lotus blossom roundels in vertical stacks separated by bamboo verticals, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":159622640,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10331","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264503400_159622640.png","dominant_hex":"#583b25","palette":[{"hex": "#583b25", "pct": 26.54}, {"hex": "#765131", "pct": 25.39}, {"hex": "#a17e57", "pct": 19.73}, {"hex": "#c1a67f", "pct": 15.75}, {"hex": "#d6c2a0", "pct": 12.59}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:08:31.735697+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010331","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10330,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Face-Skull Damask — Linen Cream colorway. Ground: solid soft-cream (#EDE4D3). Figure: bone-ivory (#F2EADB) single-ink. Composition: classic ogival damask lattice, but the medallion FILL is death's-head moth skull pattern at each damask cell, wings forming the connecting flourishes. Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. 19th-century engraved-natural-history-plate aesthetic, Vesalius anatomical-plate quality, NOT modern, NOT cartoon. Screen-print stencil look — every face/skull is a crisp flat single-ink silhouette, no half-tones, no chromatic gradient, no fade. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":259709191,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10330","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264489627_259709191.png","dominant_hex":"#d4c7ad","palette":[{"hex": "#d4c7ad", "pct": 28.32}, {"hex": "#9b8f73", "pct": 24.41}, {"hex": "#c3b79c", "pct": 20.77}, {"hex": "#aca085", "pct": 13.84}, {"hex": "#9f9377", "pct": 12.66}],"motifs":null,"tags":null,"category":"face-skull-damask","is_published":true,"notes":null,"created_at":"2026-05-20T08:08:18.318233+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010330","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10329,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":616961283,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10329","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264476459_616961283.png","dominant_hex":"#6b665d","palette":[{"hex": "#6b665d", "pct": 27.96}, {"hex": "#837d71", "pct": 23.28}, {"hex": "#45433e", "pct": 21.5}, {"hex": "#9a9385", "pct": 16.81}, {"hex": "#bbb3a3", "pct": 10.44}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:08:04.54455+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010329","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10328,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Saddle Mocha colorway. Ground: solid saddle-leather-brown (#4A3528). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: beaded vertical lines with small ornamental dot-clusters between stripes. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1984343570,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10328","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264463062_1984343570.png","dominant_hex":"#7a6549","palette":[{"hex": "#7a6549", "pct": 24.44}, {"hex": "#958265", "pct": 23.52}, {"hex": "#8a7658", "pct": 21.77}, {"hex": "#998668", "pct": 18.77}, {"hex": "#a08d6f", "pct": 11.51}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:07:51.387423+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010328","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10326,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Drunk Zoo 36\" repeat wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure: bone-ivory (#F2EADB) single-ink line-engraving. Vignette: pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them. Repeats as a wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, lots of breathing room of ground around each motif. Strict 19th-century natural-history-plate engraving aesthetic — Audubon-style accuracy without color (no bird species), Ernst Haeckel symmetry, Linnaean botanical-plate register. Realistic animal anatomy with patterned-skin rendered as crisp flat shapes (each spot/stripe/marking is a distinct solid silhouette, no gradient, no half-tone). Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1023434883,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10326","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264449641_1023434883.png","dominant_hex":"#a1947c","palette":[{"hex": "#a1947c", "pct": 27.69}, {"hex": "#c2b69e", "pct": 22.34}, {"hex": "#6c604d", "pct": 21.59}, {"hex": "#d8ceb9", "pct": 15.94}, {"hex": "#e9e1cf", "pct": 12.44}],"motifs":null,"tags":null,"category":"drunk-zoo-36","is_published":true,"notes":null,"created_at":"2026-05-20T08:07:37.9881+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010326","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10323,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink. Motif: mountain-and-temple silhouette repeats with stylized scholar-cloud forms, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2056242088,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10323","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264435882_2056242088.png","dominant_hex":"#908c7a","palette":[{"hex": "#908c7a", "pct": 39.47}, {"hex": "#676158", "pct": 17.41}, {"hex": "#898475", "pct": 15.68}, {"hex": "#a29b8a", "pct": 14.39}, {"hex": "#ccc4b3", "pct": 13.05}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:07:24.567544+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010323","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10320,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Chinoiserie wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure: bone-ivory (#F2EADB) single-ink. Motif: pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches, arranged in a tessellated seamless repeat. Cole & Son Mandarin / Ming-dynasty woodblock register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":662338093,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10320","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264422373_662338093.png","dominant_hex":"#b4a07e","palette":[{"hex": "#b4a07e", "pct": 28.97}, {"hex": "#8d7a5d", "pct": 21.23}, {"hex": "#e1d4b0", "pct": 17.76}, {"hex": "#d1c29f", "pct": 16.35}, {"hex": "#dfd2ae", "pct": 15.69}],"motifs":null,"tags":null,"category":"chinoiserie","is_published":true,"notes":null,"created_at":"2026-05-20T08:07:10.808155+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010320","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10316,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Charcoal Smoke colorway. Ground: solid near-black-charcoal (#3D3833). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1266202647,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10316","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264383532_1266202647.png","dominant_hex":"#e4decc","palette":[{"hex": "#e4decc", "pct": 31.64}, {"hex": "#34332e", "pct": 19.44}, {"hex": "#494742", "pct": 18.43}, {"hex": "#585650", "pct": 17.52}, {"hex": "#a29e91", "pct": 12.97}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:06:32.027992+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010316","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10315,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Diamond trellis wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink line-trellis. Field: crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections. NOT a damask, NOT a heavy medallion field — light open trellis only. Soane Britain / De Gournay archival trellis register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2001469214,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10315","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264367640_2001469214.png","dominant_hex":"#8a867b","palette":[{"hex": "#8a867b", "pct": 30.62}, {"hex": "#ccc6b9", "pct": 27.26}, {"hex": "#ada799", "pct": 18.75}, {"hex": "#8e8a7f", "pct": 14.17}, {"hex": "#858277", "pct": 9.2}],"motifs":null,"tags":null,"category":"diamond-trellis","is_published":true,"notes":null,"created_at":"2026-05-20T08:06:16.464286+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010315","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10314,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Vertical stripe wallpaper — Bone Ivory colorway. Ground: solid warm-ivory (#F2EADB). Figure stripes: bone-ivory (#F2EADB) single-ink. Pattern: awning stripe — uniform 6cm vertical bands alternating ground and figure tones. Robert Kime / Colefax & Fowler archival stripe register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":292702754,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10314","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264353215_292702754.png","dominant_hex":"#b4a480","palette":[{"hex": "#b4a480", "pct": 28.5}, {"hex": "#9f8e6a", "pct": 22.28}, {"hex": "#c2b597", "pct": 18.07}, {"hex": "#cbc1a7", "pct": 16.65}, {"hex": "#cec4ab", "pct": 14.49}],"motifs":null,"tags":null,"category":"stripe","is_published":true,"notes":null,"created_at":"2026-05-20T08:06:02.558914+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010314","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10312,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Stone Pewter colorway. Ground: solid cool-pewter (#7A736A). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":297592490,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10312","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264326243_297592490.png","dominant_hex":"#6c6b5f","palette":[{"hex": "#6c6b5f", "pct": 29.97}, {"hex": "#cfcab6", "pct": 24.84}, {"hex": "#9a9584", "pct": 21.79}, {"hex": "#737265", "pct": 17.15}, {"hex": "#6b6a5e", "pct": 6.25}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:05:34.572361+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010312","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10311,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Diamond trellis wallpaper — Smoke Ash colorway. Ground: solid warm-mid-grey (#6B6660). Figure: bone-ivory (#F2EADB) single-ink line-trellis. Field: crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections. NOT a damask, NOT a heavy medallion field — light open trellis only. Soane Britain / De Gournay archival trellis register. Strict bilateral symmetry — left half mirrors right half. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar, NOT mid-century-modern. CRISP FLAT COLOR ONLY — uniform solid figure ink, uniform solid ground, hard clean edges. No half-tones, no chromatic gradient, no fade, no watercolor wash. Handcrafted gouache or woodblock-print aesthetic only. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering, no watermark, no signature, no text, no logos.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1528271424,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10311","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264312735_1528271424.png","dominant_hex":"#c1baac","palette":[{"hex": "#c1baac", "pct": 27.35}, {"hex": "#aba597", "pct": 21.7}, {"hex": "#837f73", "pct": 18.49}, {"hex": "#878277", "pct": 17.86}, {"hex": "#948e81", "pct": 14.6}],"motifs":null,"tags":null,"category":"diamond-trellis","is_published":true,"notes":null,"created_at":"2026-05-20T08:05:21.169933+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010311","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10309,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Champagne Linen colorway. Ground: solid champagne-neutral (#D7C9A8). Figure: single-ink contrasting bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti: barrel cactus rosettes and cholla branches in tight tessellation, arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Edge-to-edge tessellation. Designer-grade Schumacher/Cole&Son register. Strict bilateral symmetry — the left half of the tile mirrors the right half, every cactus placed on the vertical axis of symmetry or in symmetric pairs. No off-center drift. 19th-century natural-history-plate engraving aesthetic — Linnaean botanical plate, Maria Sibylla Merian style 1700s, Ernst Haeckel Kunstformen der Natur 1899 symmetry. Strictly archival illustration, NOT modern, NOT cartoon, NOT Pixar. CRISP FLAT COLOR ONLY — every cactus silhouette is a uniform solid single ink color, the ground is a uniform solid tone. No half-tones, no chromatic gradient on any element, no fade between figure and ground, no watercolor wash, no atmospheric depth. Hard clean edges. Handcrafted gouache or woodblock-print aesthetic only. Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering.. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1788014565,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10309","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779264299106_1788014565.png","dominant_hex":"#dbd5bf","palette":[{"hex": "#dbd5bf", "pct": 23.84}, {"hex": "#a09363", "pct": 23.18}, {"hex": "#b1a67d", "pct": 19.25}, {"hex": "#9f9262", "pct": 18.6}, {"hex": "#9f9161", "pct": 15.14}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T08:05:07.653937+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010309","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":10029,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E3D8B8, #4A3A2A, #A39D8A (described as Wood Grain / Textured, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":2005400215,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10029","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261399413_inspired_DWRW-76278-Sample.png","dominant_hex":"#9c8772","palette":[{"hex": "#9c8772", "pct": 45.29}, {"hex": "#cbbca8", "pct": 24.87}, {"hex": "#d2c4b2", "pct": 11.9}, {"hex": "#917d69", "pct": 11.21}, {"hex": "#b29f8a", "pct": 6.73}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:46.500675+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010029","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWRW-76278-Sample","inspired_by_vendor":"Rebel Walls"}, 
- {"id":10027,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2E5D6, #E3D5C4 (described as Wood Grain / Geometric / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":17609469,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10027","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261391944_inspired_Maya-50512-Sample.png","dominant_hex":"#ae9e8f","palette":[{"hex": "#ae9e8f", "pct": 33.43}, {"hex": "#cbbfb0", "pct": 22.34}, {"hex": "#998a7b", "pct": 17.62}, {"hex": "#c3b5a6", "pct": 14.29}, {"hex": "#d6cbbf", "pct": 12.32}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:39.390569+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010027","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"Maya-50512-Sample","inspired_by_vendor":"Maya Romanoff"}, 
- {"id":10024,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C95929, #A2968A, #E8E4D9 (described as Stripe, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1409666830,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10024","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261384786_inspired_ROP-73668-sample.png","dominant_hex":"#938f85","palette":[{"hex": "#938f85", "pct": 23.25}, {"hex": "#bcb9af", "pct": 22.78}, {"hex": "#e1ded4", "pct": 22.44}, {"hex": "#a9a79f", "pct": 18.1}, {"hex": "#65696c", "pct": 13.42}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:31.92345+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010024","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"ROP-73668-sample","inspired_by_vendor":"Designer Wallcoverings"}, 
- {"id":10022,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #6B382C (described as Solid, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1455997477,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10022","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261377622_inspired_DWJP-12936-Sample.png","dominant_hex":"#6c7470","palette":[{"hex": "#6c7470", "pct": 27.57}, {"hex": "#c4bdad", "pct": 21.8}, {"hex": "#9c9d93", "pct": 19.38}, {"hex": "#ddd6c6", "pct": 17.14}, {"hex": "#e5dece", "pct": 14.11}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:24.764669+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010022","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWJP-12936-Sample","inspired_by_vendor":"Phillip Jeffries"}, 
- {"id":10018,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2F2F2, #D3D3D3 (described as Textured / Linen Texture, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":627034844,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10018","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261370087_inspired_DWK-30170.png","dominant_hex":"#e2d7c9","palette":[{"hex": "#e2d7c9", "pct": 26.25}, {"hex": "#d4c6b4", "pct": 25.06}, {"hex": "#af9d87", "pct": 20.52}, {"hex": "#8d7c66", "pct": 18.08}, {"hex": "#bdad97", "pct": 10.1}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:17.601321+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010018","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWK-30170","inspired_by_vendor":"Koroseal"}, 
- {"id":10016,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E5DED1, #927971, #7A835F (described as Textured / Concrete / Solid, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1037504161,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10016","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261362541_inspired_DWDC-330610-Sample.png","dominant_hex":"#bfb2a2","palette":[{"hex": "#bfb2a2", "pct": 25.69}, {"hex": "#aa9c8b", "pct": 23.74}, {"hex": "#8d8072", "pct": 23.53}, {"hex": "#ded3c5", "pct": 13.64}, {"hex": "#d0c3b4", "pct": 13.39}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:10.066908+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010016","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWDC-330610-Sample","inspired_by_vendor":"Coordonné"}, 
- {"id":10014,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A392B5, #99B38A, #E8E5E0 (described as Floral / Botanical, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1499343770,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10014","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261355074_inspired_DWKK-133887-Sample.png","dominant_hex":"#949892","palette":[{"hex": "#949892", "pct": 41.13}, {"hex": "#51544b", "pct": 17.35}, {"hex": "#e8dfd0", "pct": 16.34}, {"hex": "#c6bdac", "pct": 13.66}, {"hex": "#8d9088", "pct": 11.52}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:16:02.517665+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010014","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-133887-Sample","inspired_by_vendor":"Cole & Son"}, 
- {"id":10012,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F0ECE3, #E5E0D6, #D9D4C9 (described as Geometric / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1638128038,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10012","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261347953_inspired_DWA-88607-Sample.png","dominant_hex":"#e5dfd7","palette":[{"hex": "#e5dfd7", "pct": 25.57}, {"hex": "#bab0a4", "pct": 21.67}, {"hex": "#908e88", "pct": 18.57}, {"hex": "#3e464b", "pct": 17.34}, {"hex": "#686c6b", "pct": 16.86}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:55.051253+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010012","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWA-88607-Sample","inspired_by_vendor":"Scalamandre Wallpaper"}, 
- {"id":10011,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #B97A7A, #F5F0E6 (described as Bird / Novelty, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1777445026,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10011","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261340661_inspired_DWRW-75149-Sample.png","dominant_hex":"#4c5557","palette":[{"hex": "#4c5557", "pct": 31.34}, {"hex": "#ede8dc", "pct": 30.43}, {"hex": "#767873", "pct": 19.61}, {"hex": "#efeade", "pct": 12.48}, {"hex": "#cec9bd", "pct": 6.13}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:47.929803+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010011","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWRW-75149-Sample","inspired_by_vendor":"Rebel Walls"}, 
- {"id":10009,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C18E5A, #D3A97B (described as Wood Grain / Textured / Stripe, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1813610457,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10009","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261323617_inspired_SUR-22237-sample.png","dominant_hex":"#a5937f","palette":[{"hex": "#a5937f", "pct": 31.31}, {"hex": "#9a8773", "pct": 21.21}, {"hex": "#c7bcab", "pct": 20.15}, {"hex": "#c9bead", "pct": 18.13}, {"hex": "#bdb09e", "pct": 9.21}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:40.638197+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010009","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"SUR-22237-sample","inspired_by_vendor":"Surface Stick"}, 
- {"id":10007,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F7F2D9, #D9D1B3 (described as Solid / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1845236768,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10007","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261316435_inspired_DWKK-102082-Sample.png","dominant_hex":"#4f5b65","palette":[{"hex": "#4f5b65", "pct": 28.8}, {"hex": "#e7e0d4", "pct": 19.92}, {"hex": "#8b9397", "pct": 18.98}, {"hex": "#c3c3be", "pct": 18.23}, {"hex": "#f0eae0", "pct": 14.08}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:23.592379+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010007","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-102082-Sample","inspired_by_vendor":"Brunschwig & Fils"}, 
- {"id":10006,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A69A8D, #4A4540, #F2996B (described as Floral / Damask / Botanical, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":722273660,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10006","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261309304_inspired_DWC-NCW4304-04.png","dominant_hex":"#697072","palette":[{"hex": "#697072", "pct": 29.61}, {"hex": "#ddd4c7", "pct": 28.96}, {"hex": "#c7beb2", "pct": 23.49}, {"hex": "#b2aba0", "pct": 13.7}, {"hex": "#797d7b", "pct": 4.25}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:16.412401+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010006","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWC-NCW4304-04","inspired_by_vendor":"Nina Campbell"}, 
- {"id":10004,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2F2F2, #796A5A (described as Abstract, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1267190798,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10004","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261291995_inspired_DWCW-706546-Sample.png","dominant_hex":"#88847b","palette":[{"hex": "#88847b", "pct": 29.11}, {"hex": "#a4a096", "pct": 23.38}, {"hex": "#605850", "pct": 20}, {"hex": "#bab4aa", "pct": 15.25}, {"hex": "#d6d1c8", "pct": 12.26}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:15:09.282153+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010004","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWCW-706546-Sample","inspired_by_vendor":"China Seas"}, 
- {"id":10002,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E0D7C1, #FFFFFF, #E31B23 (described as Floral / Botanical / Novelty, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":59213016,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/10002","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261274822_inspired_Dig-510098-25-type-2-vinyl-Sample.png","dominant_hex":"#83837b","palette":[{"hex": "#83837b", "pct": 39.99}, {"hex": "#656053", "pct": 23.34}, {"hex": "#cbc0af", "pct": 15.44}, {"hex": "#e9e1d3", "pct": 12.67}, {"hex": "#949085", "pct": 8.56}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:14:51.973427+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_010002","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"Dig-510098-25-type-2-vinyl-Sample","inspired_by_vendor":"DW Bespoke Studio"}, 
- {"id":9997,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #D0E88F, #B3D963 (described as Damask / Floral, Victorian style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":798248201,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9997","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261250635_inspired_DWTT-310007-Sample.png","dominant_hex":"#e5dcd1","palette":[{"hex": "#e5dcd1", "pct": 30.45}, {"hex": "#656f72", "pct": 30.11}, {"hex": "#cbc4ba", "pct": 20.33}, {"hex": "#646d70", "pct": 10.88}, {"hex": "#888986", "pct": 8.23}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:14:34.800243+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009997","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWTT-310007-Sample","inspired_by_vendor":"Thibaut"}, 
- {"id":9992,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #B19FCB, #FAF7E8 (described as Geometric / Abstract, Art Deco style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1122279759,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9992","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261236601_inspired_DWCW-713146-Sample.png","dominant_hex":"#2d373d","palette":[{"hex": "#2d373d", "pct": 28.1}, {"hex": "#b7b2ab", "pct": 27.56}, {"hex": "#6d7677", "pct": 19.78}, {"hex": "#efeae0", "pct": 14.36}, {"hex": "#ddd6cc", "pct": 10.2}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:14:10.611709+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009992","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWCW-713146-Sample","inspired_by_vendor":"China Seas"}, 
- {"id":9990,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C4B08F, #E0D0B2, #F0E6D2 (described as Textured / Stripe, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":65618048,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9990","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261229244_inspired_RRD7465N.png","dominant_hex":"#c3baa9","palette":[{"hex": "#c3baa9", "pct": 29.81}, {"hex": "#909289", "pct": 24.16}, {"hex": "#595f5c", "pct": 23.47}, {"hex": "#e8e1d5", "pct": 20.43}, {"hex": "#dad2c3", "pct": 2.13}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:56.576995+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009990","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"RRD7465N","inspired_by_vendor":"Jeffrey Stevens"}, 
- {"id":9989,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #FFFFF0, #F5F5DC (described as Chevron / Geometric, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":701006896,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9989","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261222086_inspired_DWQW-56355-Sample.png","dominant_hex":"#8c979c","palette":[{"hex": "#8c979c", "pct": 32.54}, {"hex": "#ede7dd", "pct": 25.43}, {"hex": "#4d5861", "pct": 21.58}, {"hex": "#c4c5c0", "pct": 12}, {"hex": "#eee9df", "pct": 8.46}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:49.221969+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009989","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWQW-56355-Sample","inspired_by_vendor":"Malibu Wallpaper"}, 
- {"id":9987,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #9DC67F, #B2A79B, #FFFFFF (described as Geometric / Stripe / Dot, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":436906258,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9987","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261214869_inspired_DWC-NCW4306-03.png","dominant_hex":"#c3bdb0","palette":[{"hex": "#c3bdb0", "pct": 28.78}, {"hex": "#747064", "pct": 21.74}, {"hex": "#aca89a", "pct": 20.42}, {"hex": "#cfc8bc", "pct": 18.54}, {"hex": "#e2ddd2", "pct": 10.53}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:42.062221+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009987","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWC-NCW4306-03","inspired_by_vendor":"Nina Campbell"}, 
- {"id":9986,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #6B8E23, #8FBC8F, #F5F5DC (described as Floral / Botanical / Vine, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1418135304,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9986","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261204337_inspired_DWC-NCW4392-04.png","dominant_hex":"#a0a39b","palette":[{"hex": "#a0a39b", "pct": 29.78}, {"hex": "#4d5456", "pct": 26.58}, {"hex": "#e0d7ca", "pct": 23.06}, {"hex": "#96978e", "pct": 11.81}, {"hex": "#797a74", "pct": 8.76}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:34.845875+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009986","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWC-NCW4392-04","inspired_by_vendor":"Nina Campbell"}, 
- {"id":9984,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #D3D3D3, #F5F5F5, #A9A9A9 (described as Textured / Basketweave, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":800888462,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9984","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261196880_inspired_XXP-969828-Sample.png","dominant_hex":"#a1998c","palette":[{"hex": "#a1998c", "pct": 33.34}, {"hex": "#b3ab9e", "pct": 20.12}, {"hex": "#837c6f", "pct": 18.34}, {"hex": "#c4bcaf", "pct": 16.12}, {"hex": "#dbd3c7", "pct": 12.08}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:24.31405+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009984","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"XXP-969828-Sample","inspired_by_vendor":"Phillipe Romano"}, 
- {"id":9983,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #696969, #C0C0C0, #D3D3D3 (described as Abstract / Brushstroke / Ombre, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1160939679,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9983","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261186191_inspired_DWTT-70786-SAMPLE.png","dominant_hex":"#8f9697","palette":[{"hex": "#8f9697", "pct": 28.54}, {"hex": "#b3b5b0", "pct": 22.53}, {"hex": "#697376", "pct": 19.74}, {"hex": "#cfccc3", "pct": 18.51}, {"hex": "#e8e3d9", "pct": 10.69}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:16.857111+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009983","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWTT-70786-SAMPLE","inspired_by_vendor":"Thibaut"}, 
- {"id":9980,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #29231E, #C8B279 (described as Geometric, Art Deco style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1331055572,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9980","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261172235_inspired_Maya-50005-Sample.png","dominant_hex":"#31353a","palette":[{"hex": "#31353a", "pct": 26.9}, {"hex": "#8e887c", "pct": 23.18}, {"hex": "#e5e0d5", "pct": 18.23}, {"hex": "#cbbfad", "pct": 17.11}, {"hex": "#e4ded4", "pct": 14.58}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:13:06.166639+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009980","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"Maya-50005-Sample","inspired_by_vendor":"Maya Romanoff"}, 
- {"id":9978,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A9A08F, #D3D0C3, #E8E5D9 (described as Textured / Solid, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1728824659,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9978","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261158238_inspired_DWGB-800502-Sample.png","dominant_hex":"#a49585","palette":[{"hex": "#a49585", "pct": 29}, {"hex": "#bdaf9e", "pct": 22.1}, {"hex": "#6f6255", "pct": 19.99}, {"hex": "#d0c4b5", "pct": 19.25}, {"hex": "#dfd5c8", "pct": 9.66}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:52.208891+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009978","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWGB-800502-Sample","inspired_by_vendor":"Graham & Brown"}, 
- {"id":9976,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E3DCC9, #D1C9B6, #C0B8A4 (described as Textured / Linen Texture / Grasscloth Weave, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1406018726,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9976","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261150862_inspired_DWKK-128868-Sample.png","dominant_hex":"#6c674f","palette":[{"hex": "#6c674f", "pct": 30.25}, {"hex": "#b8b3a0", "pct": 23.85}, {"hex": "#e1d9c9", "pct": 16.94}, {"hex": "#e0d8c7", "pct": 16.61}, {"hex": "#908a73", "pct": 12.34}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:38.213817+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009976","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-128868-Sample","inspired_by_vendor":"Kravet"}, 
- {"id":9975,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #4A4447, #7A7E85, #806B5F (described as Scroll / Damask / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1908344908,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9975","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261140322_inspired_DWRC-202139.png","dominant_hex":"#808788","palette":[{"hex": "#808788", "pct": 41.78}, {"hex": "#e8dccd", "pct": 29.08}, {"hex": "#7f8586", "pct": 16.5}, {"hex": "#ccc5ba", "pct": 7.18}, {"hex": "#91918d", "pct": 5.46}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:30.837812+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009975","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWRC-202139","inspired_by_vendor":"Roberto Cavalli Wallpaper"}, 
- {"id":9973,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2F0EB, #C8C6C2 (described as Floral / Botanical / Vine, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1619725973,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9973","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261132857_inspired_EUR-70251-Sample.png","dominant_hex":"#807d70","palette":[{"hex": "#807d70", "pct": 26.58}, {"hex": "#616663", "pct": 25.76}, {"hex": "#ebe7de", "pct": 23.46}, {"hex": "#cdc4b7", "pct": 21.38}, {"hex": "#e8e3da", "pct": 2.82}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:20.299378+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009973","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"EUR-70251-Sample","inspired_by_vendor":"Designers Guild"}, 
- {"id":9972,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #B171A7, #1E57A8, #F8F5ED (described as Geometric, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":521779194,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9972","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261125536_inspired_DWCW-779946-Sample.png","dominant_hex":"#525b61","palette":[{"hex": "#525b61", "pct": 28.51}, {"hex": "#c3c1bc", "pct": 24.22}, {"hex": "#ddd7d0", "pct": 19.9}, {"hex": "#939796", "pct": 14.6}, {"hex": "#ece7e0", "pct": 12.78}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:12.834053+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009972","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWCW-779946-Sample","inspired_by_vendor":"China Seas"}, 
- {"id":9970,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A9A198, #99B3C7, #D1B1C1 (described as Stripe, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1987070087,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9970","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261111640_inspired_MIS-18396-sample.png","dominant_hex":"#9c968b","palette":[{"hex": "#9c968b", "pct": 28.94}, {"hex": "#666661", "pct": 21.44}, {"hex": "#cabfad", "pct": 20.36}, {"hex": "#bbb1a2", "pct": 17.94}, {"hex": "#e2d9c9", "pct": 11.31}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:12:05.512211+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009970","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"MIS-18396-sample","inspired_by_vendor":"Missoni Wallpaper"}, 
- {"id":9968,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2E8D9, #E5D9C6 (described as Textured / Wood Grain, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1204666845,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9968","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261104306_inspired_XKU-47542-sample.png","dominant_hex":"#8e7c6b","palette":[{"hex": "#8e7c6b", "pct": 23.05}, {"hex": "#aa9985", "pct": 22.68}, {"hex": "#c2b3a1", "pct": 22.46}, {"hex": "#c7b9a8", "pct": 19.76}, {"hex": "#cec1b0", "pct": 12.04}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:51.619044+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009968","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"XKU-47542-sample","inspired_by_vendor":"Hollywood Acoustical"}, 
- {"id":9967,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2F0D9, #E0D9A9 (described as Textured / Grasscloth Weave / Linen Texture, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":494654472,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9967","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261096787_inspired_DWWC-507700-Sample.png","dominant_hex":"#83847b","palette":[{"hex": "#83847b", "pct": 34.29}, {"hex": "#5e625c", "pct": 22.82}, {"hex": "#d2c8b5", "pct": 20.21}, {"hex": "#d0c6b4", "pct": 11.56}, {"hex": "#a7a293", "pct": 11.12}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:44.286022+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009967","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWWC-507700-Sample","inspired_by_vendor":"Phillipe Romano"}, 
- {"id":9965,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F5F2E6, #E5E0D1 (described as Textured / Linen Texture / Grasscloth Weave, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":2078489799,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9965","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261089495_inspired_PRG-71072-sample.png","dominant_hex":"#89918f","palette":[{"hex": "#89918f", "pct": 33.97}, {"hex": "#dbd1c3", "pct": 32.78}, {"hex": "#89908e", "pct": 22.35}, {"hex": "#d7cec1", "pct": 6.3}, {"hex": "#959a97", "pct": 4.6}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:36.766474+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009965","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"PRG-71072-sample","inspired_by_vendor":"Phillipe Romano"}, 
- {"id":9964,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C2305A, #F0F0F0 (described as Stripe / Bird / Novelty, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1824788519,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9964","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261072340_inspired_DIGAI-101025048-Vinyl-3.png","dominant_hex":"#a39e98","palette":[{"hex": "#a39e98", "pct": 29.81}, {"hex": "#c9c5bd", "pct": 21.73}, {"hex": "#535354", "pct": 18.85}, {"hex": "#e0dbd3", "pct": 15.67}, {"hex": "#ddd7cf", "pct": 13.94}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:29.474874+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009964","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DIGAI-101025048-Vinyl-3","inspired_by_vendor":"DW Bespoke Studio"}, 
- {"id":9962,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F5F3EE, #B2B2B2 (described as Geometric / Abstract, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1485532078,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9962","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261061835_inspired_XWY-53152-sample.png","dominant_hex":"#b7afa0","palette":[{"hex": "#b7afa0", "pct": 31.61}, {"hex": "#6f6a5f", "pct": 24.55}, {"hex": "#e1d9cc", "pct": 18.63}, {"hex": "#484b48", "pct": 14.19}, {"hex": "#938c7e", "pct": 11.01}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:12.319821+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009962","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"XWY-53152-sample","inspired_by_vendor":"Phillipe Romano"}, 
- {"id":9960,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #6B382C (described as Solid, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1567895726,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9960","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261048044_inspired_DWJP-11259-Sample.png","dominant_hex":"#e5dfd1","palette":[{"hex": "#e5dfd1", "pct": 27.12}, {"hex": "#95988f", "pct": 20.79}, {"hex": "#c2bcac", "pct": 20.7}, {"hex": "#70746e", "pct": 16.06}, {"hex": "#a8a394", "pct": 15.33}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:11:01.812906+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009960","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWJP-11259-Sample","inspired_by_vendor":"Phillip Jeffries"}, 
- {"id":9957,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #217A8A, #A3A45F, #D9723F (described as Botanical / Animal Print / Floral, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":632628051,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9957","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261027437_inspired_GRD-8805-SAMPLE.png","dominant_hex":"#aba79a","palette":[{"hex": "#aba79a", "pct": 27.9}, {"hex": "#ccc9bd", "pct": 27.62}, {"hex": "#70716a", "pct": 18.47}, {"hex": "#ddd5c7", "pct": 14.43}, {"hex": "#cac6ba", "pct": 11.58}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:10:48.023952+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009957","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"GRD-8805-SAMPLE","inspired_by_vendor":"Graduate Collection"}, 
- {"id":9952,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E3E6C6, #F0F2D9 (described as Textured / Grasscloth Weave / Solid, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":401398767,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9952","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261013447_inspired_DWA-89370-Sample.png","dominant_hex":"#b5ab9a","palette":[{"hex": "#b5ab9a", "pct": 24.77}, {"hex": "#c2b9a8", "pct": 22.58}, {"hex": "#a69c8b", "pct": 18.63}, {"hex": "#978d7c", "pct": 17.76}, {"hex": "#aca391", "pct": 16.25}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:10:27.415932+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009952","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWA-89370-Sample","inspired_by_vendor":"Scalamandre Wallpaper"}, 
- {"id":9948,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #3D7A3D, #F2B9C8, #A3C4C4 (described as Botanical / Tropical Leaf / Novelty, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1940405741,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9948","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779261006202_inspired_GRD-8822-SAMPLE.png","dominant_hex":"#797767","palette":[{"hex": "#797767", "pct": 30.42}, {"hex": "#40433b", "pct": 24.47}, {"hex": "#ebe6dc", "pct": 20.34}, {"hex": "#a1a094", "pct": 15.51}, {"hex": "#ded8ce", "pct": 9.27}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:10:13.42616+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009948","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"GRD-8822-SAMPLE","inspired_by_vendor":"Graduate Collection UK"}, 
- {"id":9947,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F0F0F0, #D3D3D3 (described as Scroll / Textured, Victorian style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":600762408,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9947","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260995456_inspired_DWRW-76254-Sample.png","dominant_hex":"#808482","palette":[{"hex": "#808482", "pct": 37.79}, {"hex": "#e3dbd1", "pct": 29.51}, {"hex": "#c5beb5", "pct": 18.9}, {"hex": "#858885", "pct": 8.66}, {"hex": "#7f8280", "pct": 5.15}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:10:06.177917+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009947","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWRW-76254-Sample","inspired_by_vendor":"Rebel Walls"}, 
- {"id":9945,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #DCD7C4, #E8E5D5 (described as Textured / Linen Texture / Basketweave, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1840136251,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9945","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260984950_inspired_DWK-30934.png","dominant_hex":"#ada293","palette":[{"hex": "#ada293", "pct": 31.52}, {"hex": "#b3a99a", "pct": 23.09}, {"hex": "#b7ad9e", "pct": 16.6}, {"hex": "#a19686", "pct": 15.95}, {"hex": "#bab0a1", "pct": 12.84}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:09:55.432359+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009945","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWK-30934","inspired_by_vendor":"Koroseal"}, 
- {"id":9943,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C0C0C0, #808080 (described as Textured / Abstract, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":715045499,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9943","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260945086_inspired_DWPN-100638.png","dominant_hex":"#c0b4a1","palette":[{"hex": "#c0b4a1", "pct": 31.07}, {"hex": "#958c7b", "pct": 19.87}, {"hex": "#d4c9b7", "pct": 19.25}, {"hex": "#756e62", "pct": 18.32}, {"hex": "#a79d8b", "pct": 11.49}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:09:44.926961+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009943","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWPN-100638","inspired_by_vendor":"Phillipe Romano"}, 
- {"id":9940,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E0D5C1, #F8F5F0 (described as Abstract / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1609344296,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9940","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260931201_inspired_DWK-30433.png","dominant_hex":"#dfd7c9","palette":[{"hex": "#dfd7c9", "pct": 25.86}, {"hex": "#c2bfb4", "pct": 21.52}, {"hex": "#535c5d", "pct": 21.38}, {"hex": "#a3a399", "pct": 20.23}, {"hex": "#8f8f86", "pct": 11.01}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:09:05.061803+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009940","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWK-30433","inspired_by_vendor":"Koroseal"}, 
- {"id":9938,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #333333, #C0C0C0 (described as Textured / Stripe, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":445656613,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9938","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260904460_inspired_RRD7467N.png","dominant_hex":"#909796","palette":[{"hex": "#909796", "pct": 30.1}, {"hex": "#adb0ad", "pct": 25.51}, {"hex": "#697376", "pct": 18.06}, {"hex": "#dfdbd1", "pct": 15.16}, {"hex": "#cacac3", "pct": 11.17}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:08:51.177576+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009938","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"RRD7467N","inspired_by_vendor":"Jeffrey Stevens"}, 
- {"id":9936,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #F2F0E6, #E6E2D6 (described as Textured / Solid, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":467199383,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9936","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260890553_inspired_DWKK-129649-Sample.png","dominant_hex":"#d8cebe","palette":[{"hex": "#d8cebe", "pct": 25.97}, {"hex": "#c7bca9", "pct": 23.97}, {"hex": "#80796d", "pct": 20.25}, {"hex": "#a09787", "pct": 15.88}, {"hex": "#b2a897", "pct": 13.94}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:08:24.436253+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009936","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-129649-Sample","inspired_by_vendor":"Kravet"}, 
- {"id":9934,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #C2C2C2, #A9A9A9 (described as Solid / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":153499739,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9934","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260883349_inspired_DWK-31635.png","dominant_hex":"#a99e92","palette":[{"hex": "#a99e92", "pct": 29}, {"hex": "#c5bcb0", "pct": 26.05}, {"hex": "#bbb2a5", "pct": 22.07}, {"hex": "#6e6155", "pct": 18.57}, {"hex": "#cdc6bb", "pct": 4.32}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:08:10.528425+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009934","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWK-31635","inspired_by_vendor":"Koroseal"}, 
- {"id":9933,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #6996C2, #F0F4F1 (described as Geometric / Lattice, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1480134172,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9933","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260872839_inspired_DWQW-57271-Sample.png","dominant_hex":"#a5a49e","palette":[{"hex": "#a5a49e", "pct": 28.09}, {"hex": "#50595f", "pct": 25.59}, {"hex": "#7e8585", "pct": 20.46}, {"hex": "#e8e1d6", "pct": 17.57}, {"hex": "#d3d0c9", "pct": 8.29}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:08:03.325093+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009933","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWQW-57271-Sample","inspired_by_vendor":"Malibu Wallpaper"}, 
- {"id":9931,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A9A18F, #D2C19F, #FFFFF0 (described as Abstract / Watercolor, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":757520971,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9931","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260862189_inspired_DWKK-129521-Sample.png","dominant_hex":"#88857c","palette":[{"hex": "#88857c", "pct": 28.36}, {"hex": "#b1aa9d", "pct": 21.58}, {"hex": "#52514d", "pct": 21.41}, {"hex": "#ccc5b7", "pct": 16.16}, {"hex": "#e3ddd2", "pct": 12.49}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:07:52.818234+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009931","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-129521-Sample","inspired_by_vendor":"Kravet"}, 
- {"id":9929,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #483C2A, #A39479 (described as Textured / Grasscloth Weave, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1755505196,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9929","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260848402_inspired_5004921.png","dominant_hex":"#978e79","palette":[{"hex": "#978e79", "pct": 29.38}, {"hex": "#c2b8a7", "pct": 26.08}, {"hex": "#bcb2a0", "pct": 23.92}, {"hex": "#b7ac99", "pct": 19.01}, {"hex": "#9e9580", "pct": 1.61}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:07:42.167673+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009929","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"5004921","inspired_by_vendor":"Schumacher"}, 
- {"id":9927,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #0A4A37, #225945 (described as Textured / Abstract / Solid, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1522595071,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9927","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260821359_inspired_DIG-2400098-SAMPLE.png","dominant_hex":"#cac0ae","palette":[{"hex": "#cac0ae", "pct": 24.01}, {"hex": "#6f6f65", "pct": 23.78}, {"hex": "#aca595", "pct": 22.03}, {"hex": "#d1c9b9", "pct": 16.41}, {"hex": "#dcd4c5", "pct": 13.77}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:07:28.381791+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009927","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DIG-2400098-SAMPLE","inspired_by_vendor":"DW Bespoke Studio"}, 
- {"id":9925,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #E4C144, #73854B, #F2E8D7 (described as Floral / Botanical, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1458082736,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9925","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260810888_inspired_DWGB-800131-Sample.png","dominant_hex":"#686c68","palette":[{"hex": "#686c68", "pct": 25.49}, {"hex": "#b7b0a2", "pct": 24.7}, {"hex": "#9f9585", "pct": 21.19}, {"hex": "#f0e9df", "pct": 17.35}, {"hex": "#e4dccf", "pct": 11.27}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T07:07:01.334254+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009925","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWGB-800131-Sample","inspired_by_vendor":"Graham & Brown"}, 
- {"id":9875,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #6B382C, #FFFFFF (described as Solid, Timeless style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1531783192,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9875","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260008113_inspired_DWJP-10250-Sample.png","dominant_hex":"#a5a7a1","palette":[{"hex": "#a5a7a1", "pct": 50.35}, {"hex": "#a7a8a0", "pct": 21.59}, {"hex": "#c8bfb0", "pct": 19.12}, {"hex": "#a39a8a", "pct": 5.78}, {"hex": "#a3a7a1", "pct": 3.17}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T06:53:35.290963+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009875","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWJP-10250-Sample","inspired_by_vendor":"Phillip Jeffries"}, 
- {"id":9874,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #808000, #F5F5DC, #483C32 (described as Stripe / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":228508305,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9874","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779260000490_inspired_VER-47274-sample.png","dominant_hex":"#a39c8b","palette":[{"hex": "#a39c8b", "pct": 26.66}, {"hex": "#cec2af", "pct": 25.46}, {"hex": "#89806f", "pct": 22.11}, {"hex": "#c8beab", "pct": 13.03}, {"hex": "#d9d2c1", "pct": 12.75}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T06:53:28.091995+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009874","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"VER-47274-sample","inspired_by_vendor":"Versace"}, 
- {"id":9873,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #528B5A, #F09199, #F2C75A (described as Floral / Botanical, Edwardian style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1762003201,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9873","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259992906_inspired_DWWM-14201-Sample.png","dominant_hex":"#9da39b","palette":[{"hex": "#9da39b", "pct": 35.69}, {"hex": "#636456", "pct": 23.53}, {"hex": "#e8e0d1", "pct": 16.45}, {"hex": "#8d8c7e", "pct": 13.98}, {"hex": "#cdc4b4", "pct": 10.35}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T06:53:20.468905+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009873","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWWM-14201-Sample","inspired_by_vendor":"William Morris"}, 
- {"id":9871,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #A69A8A, #E0D6C1, #D4C19F (described as Basketweave / Textured, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1140246277,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9871","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259985703_inspired_DWKK-129989-Sample.png","dominant_hex":"#919288","palette":[{"hex": "#919288", "pct": 37.56}, {"hex": "#c0bdae", "pct": 26.34}, {"hex": "#6c706a", "pct": 18.26}, {"hex": "#e3ded1", "pct": 12.75}, {"hex": "#afada0", "pct": 5.1}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T06:53:12.881103+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009871","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWKK-129989-Sample","inspired_by_vendor":"Kravet"}, 
- {"id":9870,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate-sdxl-inspired","prompt":"Inspired by an archival designer wallpaper with palette #779DBB, #9BB3C9, #BFC7D1 (described as Botanical / Leaf, Contemporary style), create a NEW ORIGINAL seamless wallpaper repeat that reinterprets the underlying mood — NOT a copy. Reimagine the motif in your own composition. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":"neon, fluorescent, electric, saturated, rainbow, more than 4 colors, multicolor, vivid, day-glo, highlighter colors, hot pink, primary red, primary yellow, primary blue, electric green, do not reproduce, do not copy, do not replicate any existing trademark or commercial design, no logos, no brand marks, low quality, blurry, edges, seam, border, frame, signature, watermark, text","seed":1431948600,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9870","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259978513_inspired_DWSS-74110-Sample.png","dominant_hex":"#697272","palette":[{"hex": "#697272", "pct": 36.95}, {"hex": "#ded8ce", "pct": 25.78}, {"hex": "#a9a79e", "pct": 19.83}, {"hex": "#737977", "pct": 12.61}, {"hex": "#666c6a", "pct": 4.82}],"motifs":null,"tags":null,"category":"inspired","is_published":true,"notes":"inspired-by-archival seed; settlement=skipped","created_at":"2026-05-20T06:53:05.67702+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009870","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":"DWSS-74110-Sample","inspired_by_vendor":"Sandberg"}, 
- {"id":9869,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Prickly Pear Plum colorway. Ground: solid prickly-pear-fruit plum magenta (#824050). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1357809492,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9869","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259965992_1357809492.png","dominant_hex":"#910f57","palette":[{"hex": "#910f57", "pct": 33.69}, {"hex": "#c4c6a6", "pct": 25.03}, {"hex": "#9f6571", "pct": 20.2}, {"hex": "#931559", "pct": 15.42}, {"hex": "#910d56", "pct": 5.66}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:54.553929+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009869","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9868,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Sonoran Espresso colorway. Ground: solid aged mesquite-barrel espresso brown (#3A2820). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1018582276,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9868","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259957030_1018582276.png","dominant_hex":"#67472d","palette":[{"hex": "#67472d", "pct": 25.26}, {"hex": "#6c5c48", "pct": 22.16}, {"hex": "#64452c", "pct": 22.02}, {"hex": "#9a9a8a", "pct": 22}, {"hex": "#66452c", "pct": 8.55}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:45.934878+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009868","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9865,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Indigo Atelier colorway. Ground: solid natural indigo-dye deep blue (#1E3550). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":705332994,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9865","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259948561_705332994.png","dominant_hex":"#293f58","palette":[{"hex": "#293f58", "pct": 27.5}, {"hex": "#586b72", "pct": 24.7}, {"hex": "#eee2c9", "pct": 18.17}, {"hex": "#b1b1a5", "pct": 15.68}, {"hex": "#e7dcc5", "pct": 13.95}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:36.976545+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009865","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9863,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Caliche Limestone colorway. Ground: solid warm caliche-limestone taupe (#B0A68C). Figure: single-ink mesquite charcoal (#2A2520) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1668032894,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9863","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259939968_1668032894.png","dominant_hex":"#bea78a","palette":[{"hex": "#bea78a", "pct": 34.77}, {"hex": "#383530", "pct": 26.66}, {"hex": "#887a67", "pct": 16.37}, {"hex": "#b8a286", "pct": 13.63}, {"hex": "#c1aa8d", "pct": 8.58}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:28.50391+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009863","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9860,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Cochineal Oxblood colorway. Ground: solid cochineal indigenous-textile deep red (#6B2A22). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":762129930,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9860","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259931183_762129930.png","dominant_hex":"#f0e5d7","palette":[{"hex": "#f0e5d7", "pct": 31.23}, {"hex": "#742029", "pct": 26.72}, {"hex": "#644f37", "pct": 14.83}, {"hex": "#99474c", "pct": 13.83}, {"hex": "#b59f8c", "pct": 13.39}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:19.912536+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009860","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9858,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Saguaro Patina colorway. Ground: solid aged saguaro skin green-grey (#345E51). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1997401828,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9858","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259922485_1997401828.png","dominant_hex":"#d8d2b7","palette":[{"hex": "#d8d2b7", "pct": 28.75}, {"hex": "#405139", "pct": 26.68}, {"hex": "#788466", "pct": 20.93}, {"hex": "#c8c4a8", "pct": 15.47}, {"hex": "#dad4b8", "pct": 8.17}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:11.126931+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009858","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9857,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Mesquite Charcoal colorway. Ground: solid fire-blackened mesquite charcoal (#2A2520). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":117017278,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9857","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259913875_117017278.png","dominant_hex":"#535346","palette":[{"hex": "#535346", "pct": 29.29}, {"hex": "#292722", "pct": 24.04}, {"hex": "#e1cfaf", "pct": 17.42}, {"hex": "#958d77", "pct": 17.2}, {"hex": "#d6c6a8", "pct": 12.06}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:52:02.428837+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009857","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9856,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Tortilla Bone colorway. Ground: solid chalky stucco cream (#E8DCC4). Figure: single-ink mesquite charcoal (#2A2520) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1456005048,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9856","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259905268_1456005048.png","dominant_hex":"#2c2925","palette":[{"hex": "#2c2925", "pct": 30.09}, {"hex": "#e8dfc8", "pct": 23.24}, {"hex": "#757164", "pct": 16.21}, {"hex": "#d5cdb8", "pct": 15.33}, {"hex": "#e9e1c9", "pct": 15.12}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:51:53.81911+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009856","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9855,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Adobe Sundown colorway. Ground: solid Spanish Colonial adobe terracotta (#A8623A). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":2130877941,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9855","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259896908_2130877941.png","dominant_hex":"#ba784c","palette":[{"hex": "#ba784c", "pct": 42.64}, {"hex": "#465542", "pct": 20.3}, {"hex": "#9c6f4a", "pct": 18.7}, {"hex": "#a29574", "pct": 11.24}, {"hex": "#aa7a52", "pct": 7.12}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:51:45.211567+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009855","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9854,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Cactus Repeat — Catalina Dusk colorway. Ground: solid Catalina Mountain twilight mauve (#7A4A52). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1136437843,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9854","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259888055_1136437843.png","dominant_hex":"#c9a4a4","palette":[{"hex": "#c9a4a4", "pct": 39.24}, {"hex": "#312c2b", "pct": 28.49}, {"hex": "#846c6c", "pct": 15.23}, {"hex": "#c6a1a0", "pct": 12.46}, {"hex": "#cba6a5", "pct": 4.57}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:51:36.850418+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009854","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9847,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"comfy","prompt":"Cactus Repeat — Adobe Sundown colorway. Ground: solid Spanish Colonial adobe terracotta (#A8623A). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":55543537,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9847","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259529240_55543537.png","dominant_hex":"#485a4b","palette":[{"hex": "#485a4b", "pct": 25.59}, {"hex": "#8b8d78", "pct": 24.92}, {"hex": "#bcb9a4", "pct": 22.79}, {"hex": "#6a715e", "pct": 15.49}, {"hex": "#526253", "pct": 11.22}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:47:42.151311+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009847","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":9840,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"comfy","prompt":"Cactus Repeat — Catalina Dusk colorway. Ground: solid Catalina Mountain twilight mauve (#7A4A52). Figure: single-ink tortilla-bone cream (#E8DCC4) — ALL cactus silhouettes in this exact tone. Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). Edge-to-edge tessellation with even spacing between motifs. Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, indigenous Tohono O'odham textile rhythm. No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).. Professional designer-wallpaper palette grounded in real-world high-end vendor catalogs (Schumacher, Maya Romanoff, Thibaut, Arte International, Cole & Son, Brunschwig & Fils, Scalamandre, Phillip Jeffries, Koroseal, Romo, Pierre Frey, Designers Guild, Osborne & Little — 23,817 real products analyzed in dw_unified.shopify_color_enrichment). Use ONLY colors from this learned palette: #FFFFFF (ivory), #6B382C (cocoa), #D3D3D3 (oyster), #F5F5DC (cream), #F5F5F5 (off-white), #C0C0C0 (dove), #D2B48C (fawn), #000000 (noir), #F2F2F2 (off-white), #F0F0F0 (cream), #4A4A4A (umber), #F5F3EE (off-white), #6B3827 (cocoa), #F0E68C (butter), #F5F0E1 (cream), #F5F5F0 (off-white), #333333 (charcoal), #A9A9A9 (stone), #363636 (charcoal), #F5F3ED (off-white), #F2E8D9 (pearl), #36454F (charcoal), #6B3A2A (cocoa), #808080 (slate), #008080 (malachite). Saturation distribution observed in real professional wallpaper: S<20% = 36.7% of all color usage, S 20–40% = 28.4%, S>60% = 11.1%. Most professional wallpapers use a low-saturation, tone-on-tone or 2–3 color story. Median distinct hues per design = 2; 90th percentile = 4. Keep total distinct hues per design ≤ 5. Never use neon, fluorescent, rainbow, primary-saturated red/yellow/blue, hot pink, or electric green. Prefer aged, slightly-grayed, archival pigments over pure chromatic colors.","negative_prompt":null,"seed":1932932610,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/9840","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1779259459173_1932932610.png","dominant_hex":"#e2dbc5","palette":[{"hex": "#e2dbc5", "pct": 26.6}, {"hex": "#4f5b43", "pct": 22.18}, {"hex": "#a09d7f", "pct": 18.4}, {"hex": "#787c5f", "pct": 17.81}, {"hex": "#716b54", "pct": 15.01}],"motifs":null,"tags":null,"category":"cactus","is_published":true,"notes":null,"created_at":"2026-05-20T06:45:29.133888+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_009840","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4676,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - vertical vesica piscis lens shapes, mirrored columns, single orientation. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation. Edge-to-edge tessellation - minimal negative space. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. No text, no watermark.","negative_prompt":null,"seed":1233241987,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4676","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778719240678_1233241987.png","dominant_hex":"#e1d7c0","palette":[{"hex": "#e1d7c0", "pct": 26.38}, {"hex": "#212e43", "pct": 24.18}, {"hex": "#a49e90", "pct": 21.34}, {"hex": "#696b6c", "pct": 14.49}, {"hex": "#424a56", "pct": 13.62}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:40:49.420481+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004676","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4675,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - vertical diamond rhombi stacked in identical orientation, tightly grid-locked. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":65149494,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4675","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778719152790_65149494.png","dominant_hex":"#dad1be","palette":[{"hex": "#dad1be", "pct": 26.08}, {"hex": "#1b223a", "pct": 25.18}, {"hex": "#8e8a85", "pct": 20.78}, {"hex": "#4e4f5a", "pct": 15.36}, {"hex": "#2e3245", "pct": 12.59}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:39:21.427608+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004675","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4674,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - uniform hexagonal honeycomb tiles, edge-to-edge tessellation. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1332263876,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4674","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778719133707_1332263876.png","dominant_hex":"#28354b","palette":[{"hex": "#28354b", "pct": 30.46}, {"hex": "#b5a58e", "pct": 21.54}, {"hex": "#585c63", "pct": 18.02}, {"hex": "#dac7a8", "pct": 15.63}, {"hex": "#e7d4b4", "pct": 14.35}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:39:12.751976+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004674","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4656,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":1,"generator":"replicate","prompt":"auto-variation mono of design #4646","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":[],"image_url":"/designs/img/by-id/4656","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/var_4646_mono_1778718846476.png","dominant_hex":"#3d4251","palette":null,"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":"auto-variation mono of design #4646","created_at":"2026-05-14T00:34:42.458401+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":4,"gd_critique":"This is a well-executed pattern with a nice balance of positive and negative space. The color palette is simple but effective, and the repeat is seamless.","id_score":3,"id_critique":"The pattern has a nice organic feel, but the layering makes it feel a bit busy and less refined for a luxury space. I would not specify this at full retail; it needs a cleaner execution to feel truly high-end.","combined_score":3.50,"rating_summary":"★ 3.50/5 — GD:4 ID:3 · This is a well-executed pattern with a nice balance of positive and negative space.","rated_at":"2026-05-14T00:34:56.627976+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004656","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4655,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":1,"generator":"replicate","prompt":"auto-variation hue-60 of design #4646","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":[],"image_url":"/designs/img/by-id/4655","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/var_4646_hue-60_1778718799456.png","dominant_hex":"#3d4251","palette":null,"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":"auto-variation hue-60 of design #4646","created_at":"2026-05-14T00:34:06.036538+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":3,"gd_critique":"The color palette is cohesive, but the motif density is a bit high, creating a busy overall feel. The repeat seems mostly functional, but the visual hierarchy is somewhat muddled due to the overlapping motifs.","id_score":3,"id_critique":"The pattern is interesting, but the color palette feels a bit dated and not quite luxurious enough for a high-end project. I would need to see it in person to assess the material quality and determine if it could work in a more casual setting.","combined_score":3.00,"rating_summary":"★ 3.00/5 — GD:3 ID:3 · The color palette is cohesive, but the motif density is a bit high, creating a busy overall feel.","rated_at":"2026-05-14T00:34:16.732279+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004655","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4654,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - alternating trapezoid blocks (wide-base and narrow-base alternation), grid-locked. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1287636987,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4654","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718822584_1287636987.png","dominant_hex":"#363f53","palette":[{"hex": "#363f53", "pct": 26.42}, {"hex": "#bbb3a3", "pct": 24.62}, {"hex": "#797a7c", "pct": 20.93}, {"hex": "#eae1ce", "pct": 16}, {"hex": "#dfd6c4", "pct": 12.03}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:34:03.889457+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004654","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4653,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - vertical lozenge shapes stacked, identical orientation, edge-to-edge. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":161773969,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4653","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718805644_161773969.png","dominant_hex":"#67696e","palette":[{"hex": "#67696e", "pct": 24.79}, {"hex": "#9d988e", "pct": 24.25}, {"hex": "#33394a", "pct": 24.08}, {"hex": "#ded2ba", "pct": 13.92}, {"hex": "#c4bba8", "pct": 12.96}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:33:41.982584+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004653","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4652,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - regular pentagons in cairo-style tessellation, single orientation. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1636617667,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4652","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718776733_1636617667.png","dominant_hex":"#e0d2b6","palette":[{"hex": "#e0d2b6", "pct": 26.73}, {"hex": "#a69a87", "pct": 20.63}, {"hex": "#2c3647", "pct": 19.6}, {"hex": "#525964", "pct": 18.4}, {"hex": "#6c6d6f", "pct": 14.64}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:33:23.801153+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004652","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4651,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - isoceles triangle wedges arranged in vertical fan columns, single orientation. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1071384245,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4651","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718709397_1071384245.png","dominant_hex":"#28364b","palette":[{"hex": "#28364b", "pct": 27.02}, {"hex": "#6a6d71", "pct": 21.65}, {"hex": "#c6b8a2", "pct": 19.3}, {"hex": "#aba291", "pct": 17.49}, {"hex": "#e5dbc9", "pct": 14.53}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:32:28.927744+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004651","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4650,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":1,"generator":"replicate","prompt":"auto-variation hue+60 of design #4646","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":[],"image_url":"/designs/img/by-id/4650","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/var_4646_hue+60_1778718600334.png","dominant_hex":"#3d4251","palette":null,"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":"auto-variation hue+60 of design #4646","created_at":"2026-05-14T00:31:46.585926+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004650","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4648,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - cluster of three vertical bars, repeated as a tessellated motif. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":90662573,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4648","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718612858_90662573.png","dominant_hex":"#ece2ce","palette":[{"hex": "#ece2ce", "pct": 30.31}, {"hex": "#283140", "pct": 24.92}, {"hex": "#6c6d6f", "pct": 16.3}, {"hex": "#b2ac9f", "pct": 16.1}, {"hex": "#454a53", "pct": 12.37}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:30:42.474419+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004648","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4647,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - concentric square frames nested four-deep, repeating on a flat grid. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":589915244,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4647","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718596872_589915244.png","dominant_hex":"#393f4c","palette":[{"hex": "#393f4c", "pct": 25.3}, {"hex": "#65625f", "pct": 24.54}, {"hex": "#948773", "pct": 21.04}, {"hex": "#b39f80", "pct": 16.26}, {"hex": "#c7ae88", "pct": 12.86}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:30:12.690346+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004647","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4646,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - perfect circles in a uniform dot-grid, equal spacing horizontal and vertical. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":482542349,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4646","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718567912_482542349.png","dominant_hex":"#3d4251","palette":[{"hex": "#3d4251", "pct": 23.97}, {"hex": "#cfc4b2", "pct": 23.28}, {"hex": "#797776", "pct": 22.74}, {"hex": "#54565e", "pct": 16.35}, {"hex": "#444955", "pct": 13.67}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:29:52.956757+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":3,"gd_critique":"The composition is a bit chaotic, with the overlapping motifs creating a muddy visual hierarchy. The color palette is harmonious, but the repeat fitness could be improved to minimize visible seams.","id_score":3,"id_critique":"The pattern is interesting, but the execution feels a bit flat for a luxury project. I'd need to see it in person to assess the material quality and depth before specifying it.","combined_score":3.00,"rating_summary":"★ 3.00/5 — GD:3 ID:3 · The composition is a bit chaotic, with the overlapping motifs creating a muddy visual hierarchy.","rated_at":"2026-05-14T00:30:11.149676+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004646","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4645,"brand":"wallco.ai","kind":"seamless_tile","width_in":24.00,"height_in":24.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - half-moon crescent scales, every crescent oriented horns-up, tight grid. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":871704411,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4645","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718419602_871704411.png","dominant_hex":"#5b5e64","palette":[{"hex": "#5b5e64", "pct": 33.84}, {"hex": "#383d4b", "pct": 21.21}, {"hex": "#807f7e", "pct": 19.14}, {"hex": "#ede3d4", "pct": 16.35}, {"hex": "#d3ccbf", "pct": 9.47}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:28:10.366778+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004645","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4644,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - stacked stepped-pyramid blocks (ziggurat profile), all upright. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1700720229,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4644","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718403659_1700720229.png","dominant_hex":"#1d2640","palette":[{"hex": "#1d2640", "pct": 29.3}, {"hex": "#51535d", "pct": 20.71}, {"hex": "#aba091", "pct": 19.06}, {"hex": "#dac8ad", "pct": 17.73}, {"hex": "#e9dec9", "pct": 13.2}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:26:59.266543+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004644","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4642,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - quarter-circle fan tiles, all curves opening upward, perfect grid alignment. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1937641180,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4642","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718385389_1937641180.png","dominant_hex":"#d5c9b4","palette":[{"hex": "#d5c9b4", "pct": 26.55}, {"hex": "#1c2234", "pct": 23.48}, {"hex": "#9d978d", "pct": 21.2}, {"hex": "#676666", "pct": 15.08}, {"hex": "#41434c", "pct": 13.7}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:26:42.850232+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004642","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4641,"brand":"wallco.ai","kind":"seamless_tile","width_in":52.00,"height_in":52.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - regular octagons in a tessellated grid with small square interstices. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1361593666,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4641","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718364595_1361593666.png","dominant_hex":"#242e3d","palette":[{"hex": "#242e3d", "pct": 27.33}, {"hex": "#53585c", "pct": 22.43}, {"hex": "#9d9688", "pct": 21.26}, {"hex": "#cebfa4", "pct": 14.83}, {"hex": "#e5d3b1", "pct": 14.15}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:26:24.214717+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004641","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":4640,"brand":"wallco.ai","kind":"seamless_tile","width_in":36.00,"height_in":36.00,"panels":null,"generator":"replicate","prompt":"Geometric block leaf pattern - narrow vertical rectangle columns, identical width, full-height stripes. Seamless tile wallpaper repeat. SINGLE INK COLOR only (deep midnight navy ink on warm cream paper). STRICT geometric abstraction - pure mathematical primitives. NO foliage, NO leaves, NO leaf shapes, NO plants, NO botanical motifs, NO ferns, NO palm, NO palm fronds, NO palm leaves, NO banana, NO monstera, NO tropical, NO jungle, NO fruit, NO grapes, NO birds, NO butterflies. ALL shapes in identical orientation - NO directional variation, NO tossed layout, NO multi-orientation rotation. Edge-to-edge tessellation - minimal negative space between primitives. Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. Archival paper texture. No text, no watermark, no signature.","negative_prompt":null,"seed":1939963961,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/4640","local_path":"/Users/stevestudio2/Projects/wallco-ai/data/generated/1778718322622_1939963961.png","dominant_hex":"#2b3447","palette":[{"hex": "#2b3447", "pct": 30.89}, {"hex": "#b8b2a7", "pct": 19.02}, {"hex": "#5d5f66", "pct": 18.18}, {"hex": "#e0d7c3", "pct": 16.01}, {"hex": "#dcd2c0", "pct": 15.9}],"motifs":null,"tags":null,"category":"geometric-block-leaves","is_published":true,"notes":null,"created_at":"2026-05-14T00:26:01.901995+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["wallco"],"source_shopify_id":null,"source_dw_sku":null,"source_url":null,"gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_004640","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":136,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Sigourney Zibby Blue on White Screen Printed | China Seas","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/136","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWCW-777746.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"china-seas","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6604516851763","source_dw_sku":"DWCW-777746","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/CHINA_SEAS_WALLPAPER_DWC_777700_Sigourney-Zibby-Blue-on-White-2470-15WP_jpg_92b63634-de7f-4ce4-b5bc-beb55b1d1c8b.jpg?v=1748371167","gd_score":3,"gd_critique":"The composition is unbalanced, with too much negative space in the upper right. The color palette is simple but effective, however, the motif scale is too large for a seamless repeat.","id_score":3,"id_critique":"The pattern is playful and could work in a casual setting, but it lacks the sophistication and depth expected for a high-end project. I wouldn't specify this for a client paying full retail, as it reads more mass-produced than bespoke.","combined_score":3.00,"rating_summary":"★ 3.00/5 — GD:3 ID:3 · The composition is unbalanced, with too much negative space in the upper right.","rated_at":"2026-05-12T05:00:04.611246+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000136","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":135,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Roberto Cavalli Wallcovering","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/135","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWRC-202149.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"roberto-cavalli-wallpaper","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/4502775824435","source_dw_sku":"DWRC-202149","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/RobertoCavalliWallpaper_dwrc16046.jpg?v=1587153720","gd_score":4,"gd_critique":"This wallpaper has a nice, organic texture and a sophisticated color palette. The repeat looks well-executed, and the density of the pattern is pleasing without being overwhelming.","id_score":3,"id_critique":"The pattern is interesting and could work in a specific setting, but it lacks the refinement and visual impact I'd expect at this price point. I'm not sure it reads as a true investment piece, and the color palette feels a bit dated.","combined_score":3.50,"rating_summary":"★ 3.50/5 — GD:4 ID:3 · This wallpaper has a nice, organic texture and a sophisticated color palette.","rated_at":"2026-05-12T05:00:06.650008+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000135","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":134,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Vintage Ikat - Beige, Indigo Wallcovering | Mind the Gap","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/134","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWWD-70680.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"mind-the-gap","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7430800670771","source_dw_sku":"DWWD-70680","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Screenshot_2024-09-26_at_3.01.54_PM.png?v=1748367453","gd_score":4,"gd_critique":"This is a well-executed ikat stripe pattern. The color palette is harmonious and the repeat is well-managed, though the seams are slightly visible.","id_score":3,"id_critique":"The ikat pattern is interesting, but the color palette feels a bit dated for a truly high-end project. I could see it working in a more casual setting, but it lacks the sophistication I'd expect at this price point.","combined_score":3.50,"rating_summary":"★ 3.50/5 — GD:4 ID:3 · This is a well-executed ikat stripe pattern.","rated_at":"2026-05-12T05:00:14.219881+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000134","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":133,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Japanese Garden Navy - Navy Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/133","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80799-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552724533299","source_dw_sku":"DWTT-80799-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T13307.jpg?v=1762258890","gd_score":4,"gd_critique":"This is a well-executed toile pattern with a classic feel. The composition is balanced and the color palette is harmonious, but the motif density could be slightly reduced to improve visual hierarchy.","id_score":4,"id_critique":"This Chinoiserie pattern is quite elegant and could work well in a high-end residential or boutique hotel setting. The color palette is classic, and the design feels sophisticated, though it could easily veer into 'grandma' territory if not styled carefully.","combined_score":4.00,"rating_summary":"★ 4.00/5 — GD:4 ID:4 · This is a well-executed toile pattern with a classic feel.","rated_at":"2026-05-12T05:00:16.674946+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000133","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":132,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Japanese Garden Wedgewood - Cobalt Blue Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/132","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80798-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552724500531","source_dw_sku":"DWTT-80798-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T13306.jpg?v=1762258859","gd_score":4,"gd_critique":"This is a well-executed toile pattern. The composition is balanced and the motif scale is appropriate, though the repeat might be slightly visible upon close inspection.","id_score":4,"id_critique":"This Chinoiserie pattern is quite elegant and could work well in a high-end residential or boutique hotel setting. The color palette is classic, and the design feels sophisticated, though it could easily veer into looking dated if not styled carefully.","combined_score":4.00,"rating_summary":"★ 4.00/5 — GD:4 ID:4 · This is a well-executed toile pattern.","rated_at":"2026-05-12T05:00:18.863944+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000132","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":130,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Tiger Flock Pearl - Pearl Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/130","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-71308-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6944708427827","source_dw_sku":"DWTT-71308-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T83064.jpg?v=1762228785","gd_score":4,"gd_critique":"The composition is well-balanced and the color palette is harmonious. The repeat fitness appears good, but the visual hierarchy could be stronger to define a clear primary motif.","id_score":3,"id_critique":"The pattern is interesting, but the execution feels a bit dated for a truly high-end project. I'd need to see it in person to assess the material quality and ensure it doesn't read as a lower-end print.","combined_score":3.50,"rating_summary":"★ 3.50/5 — GD:4 ID:3 · The composition is well-balanced and the color palette is harmonious.","rated_at":"2026-05-12T05:00:27.276798+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000130","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":129,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Daisy Bennett - Beige Wallcovering | Daisy Bennett","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/129","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWC-1001229.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"daisy-bennett","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/4693282619443","source_dw_sku":"DWC-1001229","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/db10202.jpg?v=1602006702","gd_score":4,"gd_critique":"The composition is well-balanced with a good distribution of motifs. The color palette is harmonious and creates a cohesive underwater feel, though the repeat fitness could be improved to minimize visible seams.","id_score":3,"id_critique":"The pattern is charming, but the watercolor style reads as more juvenile than luxurious. I could see it working in a high-end children's space or a very casual beach house, but not for my typical client.","combined_score":3.50,"rating_summary":"★ 3.50/5 — GD:4 ID:3 · The composition is well-balanced with a good distribution of motifs.","rated_at":"2026-05-12T05:00:32.377595+00:00","user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000129","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":128,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Lexington Drive - Oyster | Hollywood Wallcoverings","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/128","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/REW-6246.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"designer-wallcoverings","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1495546822768","source_dw_sku":"REW-6246","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/017d5204865e68e79f2d66c5cfa07d9a.jpg?v=1572309082","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000128","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":127,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"ZEBRA ROMANCE MISTERIOSO BY DOLCE & GABBANA WALLCOVERING","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/127","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DGW-806022-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"dolce-gabbana","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7102714019891","source_dw_sku":"DGW-806022-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TCW007TCAHOUZ018.jpg?v=1711558391","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000127","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":126,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Honshu Navy - Navy Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/126","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-81070-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552742686771","source_dw_sku":"DWTT-81070-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T75488.jpg?v=1762264074","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000126","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":125,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Wild Flower Beige - Beige Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/125","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-70725-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6944690602035","source_dw_sku":"DWTT-70725-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/T27023_886c6ed8-aed9-4bd1-b78d-af0c54ca32c2.jpg?v=1733895416","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000125","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":124,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Sensimille - Sueded Silver Mylar","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/124","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/SSM-67804.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"designer-wallcoverings","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1495789928560","source_dw_sku":"SSM-67804","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/8a51e523d535cd06d1fd8de011248ab6.jpg?v=1572309152","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000124","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":123,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Sacred Pheasants Jade | Coordone Europe","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/123","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWDC-330668.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"coordonn-","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7429097619507","source_dw_sku":"DWDC-330668","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/B00115_01_5a5a20d6-965b-4fdc-94de-99fe9d69470c.jpg?v=1739302569","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000123","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":122,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Rosalind Mist - Mist Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/122","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80749-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552722010163","source_dw_sku":"DWTT-80749-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T13603.jpg?v=1762257698","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000122","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":121,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Rosalind Blush - Blush Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/121","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80746-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552721846323","source_dw_sku":"DWTT-80746-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T13600.jpg?v=1762257623","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000121","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":120,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Crowned Crane Turquoise | Coordone Europe","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/120","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWDC-330714.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"coordonn-","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7429099651123","source_dw_sku":"DWDC-330714","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/B00161_01_1d8e9a7d-0925-4131-9472-425b93d14bb1.jpg?v=1739302480","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000120","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":119,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Norfolk Stripe Chocolate Brown - Brown Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/119","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80151-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552681967667","source_dw_sku":"DWTT-80151-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T44049.jpg?v=1762245561","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000119","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":118,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Puget's Pineapple","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/118","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/SCR-7916.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"designer-wallcoverings","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1495561863280","source_dw_sku":"SCR-7916","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/8fddd61703f313c432d8f939cd14e680.jpg?v=1572309083","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000118","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":117,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Norfolk Stripe Charcoal Black - Black Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/117","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80162-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552682491955","source_dw_sku":"DWTT-80162-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T44050.jpg?v=1762245842","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000117","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":116,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Jute Contemporary Grasscloth - Green | Architectural Wallcoverings","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/116","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWQW-56780.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"malibu-wallpaper","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7292151988275","source_dw_sku":"DWQW-56780","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/NA305.jpg?v=1748380823","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000116","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":115,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Opulent Leaf Copper - Beige Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/115","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29008.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519946178611","source_dw_sku":"DWK-29008","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI_TAK-AA04-02__FOIL.jpg?v=1739409104","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000115","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":114,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Moire Sauvage Copper - Taupe Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/114","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29241.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519963971635","source_dw_sku":"DWK-29241","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/47264_MOIRE-SAUVAGE_24x24-min.jpg?v=1739410099","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000114","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":113,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Opulent Leaf Gold - Golden Yellow Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/113","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29007.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519946113075","source_dw_sku":"DWK-29007","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI_TAK-AA04-01__FOIL.jpg?v=1739409100","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000113","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":112,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Illuminate Copper - Dark Brown Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/112","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29013.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519946571827","source_dw_sku":"DWK-29013","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI-ROOMSET_TAK-AA07-01__FOIL.jpg?v=1739409126","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000112","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":111,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Illuminate Gold - Dark Olive Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/111","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29014.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519946637363","source_dw_sku":"DWK-29014","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI-ROOMSET_TAK-AA07-02__FOIL.jpg?v=1739409130","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000111","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":110,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Subtle Leaf Gold - Gold Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/110","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29001.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519945588787","source_dw_sku":"DWK-29001","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI-ROOMSET_TAK-AA01-02__FOIL.jpg?v=1739409073","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000110","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":109,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Lily Curry | Coordone Europe","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/109","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWDC-330801.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"coordonn-","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7429102993459","source_dw_sku":"DWDC-330801","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/B00331_01_e03d18ca-abc3-4e88-bf12-6d0cca5e4488.jpg?v=1739302313","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000109","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":108,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Lily Clay | Coordone Europe","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/108","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWDC-330805.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"coordonn-","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7429103124531","source_dw_sku":"DWDC-330805","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/B00335_01_cac16519-f311-404b-8090-0871295d7b28.jpg?v=1739302304","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000108","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":107,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Katsu Cream | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/107","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-72083-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6944744767539","source_dw_sku":"DWTT-72083-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T1887.jpg?v=1762237917","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000107","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":106,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Blossom Mural - Sky Blue Wallcovering | Phillipe Romano","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/106","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWCC-31812.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7697041195059","source_dw_sku":"DWCC-31812","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/blossom-mural-sky-blue.jpg?v=1761801005","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000106","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":105,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Savannah Cheetah Teal Wallcovering | Graduate Collection","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/105","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/GRD-87000.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"graduate-collection","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/3668508966977","source_dw_sku":"GRD-87000","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2021-10-08at2.46.48PM.png?v=1633729673","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":5.00,"user_vote_count":1,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000105","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":104,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Provacetero Posa Wallcovering","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/104","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/XE5-66806.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1497123520624","source_dw_sku":"XE5-66806","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/522eeaf5fb9d3c7ee4af7ca033374206.jpg?v=1775136860","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000104","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":103,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Marlowe Floral - Prussian - Blue Wallcovering | Ralph Lauren","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/103","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RALPH-2031SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"ralph-lauren","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6860114395187","source_dw_sku":"RALPH-2031SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-11-04at2.46.09PM.png?v=1747254083","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000103","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":102,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Mother of Pearl On the Half Shell - Oyster | Maya Romanoff","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/102","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/MAYA-62400-SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"maya-romanoff","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7484873736243","source_dw_sku":"MAYA-62400-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ScreenShot2024-12-17at11.09.06AM.png?v=1738860937","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000102","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":101,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Phillipe Romano - Renaissance Metal Leaf","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/101","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RML-33019.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1501551951984","source_dw_sku":"RML-33019","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/7113595132cc9f3ee7b10e181d602890.jpg?v=1572310601","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000101","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":100,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Glitter Threads Fabric - White Silver Iris | Glitter Walls","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/100","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/GLM-4165-SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"glitter-walls","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6761728442419","source_dw_sku":"GLM-4165-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-06-30at4.37.26PM.png?v=1748366344","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000100","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":99,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Alicante Velvet Walls Cream","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/99","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWVE-429560.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6621005643827","source_dw_sku":"DWVE-429560","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/RM_410_01_a27c5e93-1e47-4dba-b311-94601d61aee1.jpg?v=1635521098","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000099","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":98,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Alicante Velvet Walls Cabernet Red","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/98","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWVE-430560.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6621006004275","source_dw_sku":"DWVE-430560","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/RM_410_52_d2170c73-ea7c-4827-a179-336d2974ce9d.jpg?v=1635521114","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000098","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":97,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Wabi-Sabi Pumice Beige - Beige Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/97","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-31727.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7584991051827","source_dw_sku":"DWK-31727","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/WABI-SABI-PUMICE_CL04-04_db286c08-f15e-45ed-96a7-35941d24c7df.jpg?v=1751949048","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000097","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":96,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Mica Contemporary Grasscloth - Copper, Orange/Rust | Architectural Wallcoverings","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/96","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWQW-56806.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"malibu-wallpaper","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7292162474035","source_dw_sku":"DWQW-56806","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/NA526.jpg?v=1748380767","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000096","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":95,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Alicante Velvet Walls Old Gold","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/95","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWVE-430860.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6621006135347","source_dw_sku":"DWVE-430860","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/RM_410_64_9a5c3d21-d4d0-4905-8e0c-7d75bc0bc83a.jpg?v=1635521119","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000095","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":94,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Alicante Velvet Walls Hunter Green","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/94","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWVE-430760.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6621006102579","source_dw_sku":"DWVE-430760","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/RM_410_63_c7211a54-a518-428d-a7de-728ff9fb9f8c.jpg?v=1635521117","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000094","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":93,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Travelers Palm Spa Blue - Spa Blue Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/93","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/Palm-1002 - Pink Salmon.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/4656362160179","source_dw_sku":"Palm-1002 - Pink Salmon","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T10128_9827be9b-056c-4f33-859a-626e6366bd0a.jpg?v=1762220117","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000093","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":92,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Gardens Of Jaipur | Mind The Gap","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/92","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWWD-70619.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"mind-the-gap","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7430796836915","source_dw_sku":"DWWD-70619","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Screen_Shot_2024-09-27_at_10.42.20_AM.png?v=1748367531","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000092","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":91,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Clover Cacao | Coordone Europe","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/91","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWDC-330787.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"coordonn-","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7429102403635","source_dw_sku":"DWDC-330787","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/B00317_01_51ea4e7e-1e9f-4955-b37b-dfeb0fcac7d9.jpg?v=1739302344","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000091","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":90,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Showgirls Metallic Bronze & Cream Wallcovering | Graduate Collection","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/90","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/GRD-87014-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"graduate-collection","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6945995423795","source_dw_sku":"GRD-87014-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Showgirls_MetallicBronze_Cream_1024x1024_3f9df313-e6aa-40c7-adac-97b90df0b067.jpg?v=1730488554","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000090","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":89,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Marlowe Floral - - Slate Wallcovering | Ralph Lauren","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/89","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RALPH-2026SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"ralph-lauren","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6860111740979","source_dw_sku":"RALPH-2026SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-11-04at2.36.24PM.png?v=1747254090","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000089","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":88,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Ashfield Floral - - Tourmaline Wallcovering | Ralph Lauren","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/88","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RALPH-2008SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"ralph-lauren","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6860084805683","source_dw_sku":"RALPH-2008SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-11-04at12.48.34PM.png?v=1747254122","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000088","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":87,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Ryker Stripe Cream - Cream Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/87","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-80171-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7552682950707","source_dw_sku":"DWTT-80171-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T44059.jpg?v=1762246081","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000087","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":86,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Showgirls Metallic Bronze & Black Wallcovering | Graduate Collection","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/86","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/GRD-87015.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"graduate-collection","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6945997258803","source_dw_sku":"GRD-87015","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ScreenShot2023-09-11at1.51.20PM.png?v=1694465520","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":5.00,"user_vote_count":1,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000086","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":85,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Languid Leaf Gold - Gold Commercial Wallcovering | Koroseal","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/85","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWK-29016.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"koroseal","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7519946833971","source_dw_sku":"DWK-29016","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/TAKUMI-ROOMSET_TAK-AA08-02__FOIL.jpg?v=1739409140","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000085","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":84,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Oceania - Black, Blue, Green, Orange, Red Wallcovering | Mind the Gap","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/84","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWWD-70255.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"mind-the-gap","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7430001590323","source_dw_sku":"DWWD-70255","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/OCEANIA_866837de-9bd6-47ee-9ca6-6e586557e852.jpg?v=1748368117","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000084","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":83,"brand":"wallco.ai","kind":"best_seller_seed","width_in":36.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Algerian Tale - Green, Beige, Red, Brown Wallcovering | Mind the Gap","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/83","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWWD-70734.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"mind-the-gap","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/7430800998451","source_dw_sku":"DWWD-70734","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Screen_Shot_2024-10-02_at_1.07.32_PM.png?v=1748367447","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000083","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":82,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Egarton Plaid - Gunmetal - Taupe Wallcovering | Ralph Lauren","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/82","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RALPH-2003SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"ralph-lauren","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6860074909747","source_dw_sku":"RALPH-2003SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-11-04at12.28.20PM.png?v=1747254130","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000082","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":81,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Katsura Cream and Lavender - Cream and Lavender Wallcovering | Thibaut","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/81","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/DWTT-70254-SAMPLE.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"thibaut","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6944673595443","source_dw_sku":"DWTT-70254-SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/T13622_182088c0-769b-4fc3-bb72-ccbab6abbed3.jpg?v=1733896463","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000081","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":80,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Flattering Flamingo Midnight Blue | Traditional Whimsy","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/80","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/WHM-78001-ROOM.jpg","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"traditional-whimsy","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/1501668999280","source_dw_sku":"WHM-78001-ROOM","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/2c9368d065b0516b88325cc003c4acb9_82f30ff7-31ce-4b67-be3f-bac3605527d7.jpg?v=1733964591","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000080","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":79,"brand":"wallco.ai","kind":"best_seller_seed","width_in":52.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Marlowe Floral - Mother of - Pearl Wallcovering | Ralph Lauren","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/79","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/RALPH-2028SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"ralph-lauren","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6860112855091","source_dw_sku":"RALPH-2028SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-11-04at2.40.28PM.png?v=1747254087","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000079","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}, 
- {"id":78,"brand":"wallco.ai","kind":"best_seller_seed","width_in":24.00,"height_in":null,"panels":null,"generator":"dw-shopify","prompt":"Victory Damask Flocked Velvet - Red on Cream","negative_prompt":null,"seed":0,"steps":null,"cfg_scale":null,"pd_source_ids":null,"image_url":"/designs/img/by-id/78","local_path":"/Users/stevestudio2/Projects/thesetdecorator/public/img/seed/PRNC-11008SAMPLE.png","dominant_hex":null,"palette":null,"motifs":null,"tags":null,"category":"phillipe-romano","is_published":true,"notes":null,"created_at":"2026-05-12T04:07:50.196278+00:00","parent_design_id":null,"chat_session_id":null,"request_text":null,"verticals":["setdecorator"],"source_shopify_id":"gid://shopify/Product/6754523349043","source_dw_sku":"PRNC-11008SAMPLE","source_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/ScreenShot2022-06-15at2.33.13PM.png?v=1655328934","gd_score":null,"gd_critique":null,"id_score":null,"id_critique":null,"combined_score":null,"rating_summary":null,"rated_at":null,"user_score_avg":null,"user_vote_count":0,"poll_wins":0,"poll_losses":0,"elo":1200.00,"room_mockups":null,"user_removed":false,"user_color_good":null,"user_style_good":null,"user_scale_good":null,"user_stars":null,"user_rated_at":null,"ai_title":null,"ai_pattern":null,"ai_color_name":null,"dig_number":"DIG_AI_000078","ai_reviewed_at":null,"ai_review_raw":null,"spoonflower_design_id":null,"spoonflower_url":null,"spoonflower_uploaded_at":null,"shopify_product_id":null,"shopify_product_url":null,"shopify_uploaded_at":null,"settlement_verdict":null,"inspired_by_sku":null,"inspired_by_vendor":null}]
diff --git a/reports/dw-stale-urls-heal-A-2026-05-19.md b/docs/ops/dw-stale-urls-heal-A-2026-05-19.md
similarity index 100%
rename from reports/dw-stale-urls-heal-A-2026-05-19.md
rename to docs/ops/dw-stale-urls-heal-A-2026-05-19.md
diff --git a/reports/dw-stale-urls-push-A2-2026-05-19.md b/docs/ops/dw-stale-urls-push-A2-2026-05-19.md
similarity index 100%
rename from reports/dw-stale-urls-push-A2-2026-05-19.md
rename to docs/ops/dw-stale-urls-push-A2-2026-05-19.md
diff --git a/reports/dw-stale-urls-rescrape-2026-05-19.md b/docs/ops/dw-stale-urls-rescrape-2026-05-19.md
similarity index 100%
rename from reports/dw-stale-urls-rescrape-2026-05-19.md
rename to docs/ops/dw-stale-urls-rescrape-2026-05-19.md
diff --git a/reports/dw-stale-urls-trim-B-2026-05-19.md b/docs/ops/dw-stale-urls-trim-B-2026-05-19.md
similarity index 100%
rename from reports/dw-stale-urls-trim-B-2026-05-19.md
rename to docs/ops/dw-stale-urls-trim-B-2026-05-19.md
diff --git a/reports/dw-stale-urls-trim-B-decision-2026-05-19.md b/docs/ops/dw-stale-urls-trim-B-decision-2026-05-19.md
similarity index 100%
rename from reports/dw-stale-urls-trim-B-decision-2026-05-19.md
rename to docs/ops/dw-stale-urls-trim-B-decision-2026-05-19.md
diff --git a/hot-or-not-server.js b/hot-or-not-server.js
deleted file mode 100644
index b5b1847..0000000
--- a/hot-or-not-server.js
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env node
-/**
- * Hot-or-Not standalone — port 9794.
- *
- * Self-contained mini-app: serves the gamified card-flick UI on its own
- * port + pm2 process, independent of the main wallco-ai server (9792).
- * Backend API calls (random design, user vote) proxy to the main server
- * so design data and votes stay in one place.
- *
- * Run: PORT=9794 node hot-or-not-server.js
- * pm2: pm2 start hot-or-not-server.js --name wallco-hot-or-not
- */
-'use strict';
-require('dotenv').config({ path: require('path').join(__dirname, '.env') });
-const express = require('express');
-const path    = require('path');
-const fs      = require('fs');
-
-const app  = express();
-const PORT = parseInt(process.env.PORT || '9794', 10);
-
-// Upstream wallco-ai server for API + image assets
-const UPSTREAM = process.env.WALLCO_UPSTREAM || 'http://127.0.0.1:9792';
-
-app.use(express.json({ limit: '2mb' }));
-
-// Serve the local game engine
-app.use('/js', express.static(path.join(__dirname, 'public', 'js'), { maxAge: '1h' }));
-
-// Proxy: GET /api/design/random → upstream
-app.get('/api/design/random', async (req, res) => {
-  try {
-    const url = UPSTREAM + '/api/design/random' + (req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '');
-    const r = await fetch(url, { headers: { 'cookie': req.headers.cookie || '' } });
-    const json = await r.json();
-    res.json(json);
-  } catch (e) { res.status(502).json({ error: 'upstream: ' + e.message }); }
-});
-
-// Proxy: POST /api/design/:id/user-vote → upstream
-app.post('/api/design/:id/user-vote', async (req, res) => {
-  try {
-    const r = await fetch(`${UPSTREAM}/api/design/${req.params.id}/user-vote`, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-        'cookie': req.headers.cookie || ''
-      },
-      body: JSON.stringify(req.body || {})
-    });
-    res.set('set-cookie', r.headers.get('set-cookie') || '');
-    const j = await r.json();
-    res.json(j);
-  } catch (e) { res.status(502).json({ error: 'upstream: ' + e.message }); }
-});
-
-// Proxy: image assets so /designs/img/<file>.png works (load via main server)
-app.get('/designs/img/:fn', async (req, res) => {
-  try {
-    const r = await fetch(`${UPSTREAM}/designs/img/${encodeURIComponent(req.params.fn)}`);
-    if (!r.ok) return res.status(r.status).end();
-    res.set('content-type', r.headers.get('content-type') || 'image/png');
-    res.set('cache-control', 'public, max-age=86400');
-    const buf = Buffer.from(await r.arrayBuffer());
-    res.end(buf);
-  } catch (e) { res.status(502).end(); }
-});
-
-// ── Main page ───────────────────────────────────────────────────────
-app.get('/', (req, res) => {
-  res.type('html').send(`<!doctype html>
-<html lang="en">
-<head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
-<title>Hot or Not · wallco.ai</title>
-<meta name="description" content="Speed-vote on AI-original wallpaper. Every tap teaches the system your taste.">
-<meta name="theme-color" content="#0f0e0c">
-<link rel="canonical" href="https://hotornot.wallco.ai/">
-<style>
-  *,*::before,*::after { box-sizing: border-box; }
-  html, body { margin:0; padding:0; background:#0f0e0c; color:#f0eadc; font:15px/1.45 -apple-system, system-ui, Segoe UI, sans-serif; min-height:100vh; }
-  header { padding:14px 18px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #221f1c; }
-  header a.brand { font-family:'Cormorant Garamond', Georgia, serif; font-weight:300; font-size:22px; color:#f0eadc; text-decoration:none; letter-spacing:.5px; }
-  header .tag { font-size:11px; color:#888; text-transform:uppercase; letter-spacing:.1em; }
-  main { padding:18px; max-width:520px; margin:0 auto; }
-  h1 { font-family:'Cormorant Garamond', Georgia, serif; font-weight:300; font-size:30px; margin:0 0 4px; text-align:center; }
-  .sub { color:#888; font-size:12px; text-align:center; margin:0 0 12px; }
-  #wc-game-banner { background:#1a1816; border-radius:10px; padding:14px 16px; margin-bottom:16px; border:1px solid rgba(210,177,92,.25); }
-  #card { position:relative; border:1px solid rgba(255,255,255,.08); border-radius:18px; overflow:hidden; background:#1a1816; box-shadow:0 24px 60px rgba(0,0,0,.5); transition:transform .26s ease, opacity .26s ease; }
-  #card img { display:block; width:100%; aspect-ratio:1; object-fit:cover; pointer-events:none; }
-  #card .meta { padding:14px 20px; display:flex; justify-content:space-between; align-items:center; gap:12px; }
-  #card .title { font-family:'Cormorant Garamond', Georgia, serif; font-weight:300; font-size:22px; }
-  #card .sub2 { font-size:10px; color:#888; text-transform:uppercase; letter-spacing:.06em; margin-top:3px; }
-  #card .viewfull { font-size:11px; color:#d2b15c; text-decoration:underline; }
-  #card.swipe-left  { transform:translateX(-130%) rotate(-14deg); opacity:0; }
-  #card.swipe-right { transform:translateX( 130%) rotate( 14deg); opacity:0; }
-  #card.swipe-up    { transform:translateY(-130%); opacity:0; }
-  #buttons { display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; margin-top:18px; }
-  .btn { padding:18px 12px; font-size:14px; font-weight:700; letter-spacing:.12em; border:0; border-radius:12px; cursor:pointer; min-height:62px; transition:transform .12s; }
-  .btn:active { transform:scale(.96); }
-  .btn-not  { background:linear-gradient(180deg,#3a1a1a,#2a1212); color:#fbb; border:1px solid #5a2222; }
-  .btn-skip { background:#2a2825; color:#aaa; border:1px solid #3a3835; }
-  .btn-hot  { background:linear-gradient(180deg,#e0bd64,#d2b15c); color:#1a1a1a; border:1px solid #b89745; }
-  footer { margin-top:32px; padding:24px 18px; text-align:center; color:#666; font-size:11px; }
-  footer a { color:#888; text-decoration:none; }
-  footer a:hover { color:#d2b15c; }
-  .history { display:grid; grid-template-columns:repeat(6, 1fr); gap:6px; margin-top:22px; }
-  .history a { display:block; aspect-ratio:1; border-radius:5px; overflow:hidden; border:2px solid transparent; }
-  .history a img { width:100%; height:100%; object-fit:cover; display:block; }
-  .history a.hot  { border-color:#d2b15c; }
-  .history a.not  { border-color:#a44; }
-  .history a.skip { border-color:#555; }
-  .keys { font-size:10px; color:#666; margin-top:6px; text-align:center; }
-  @media (min-width: 700px) {
-    main { padding:32px; max-width:560px; }
-    h1 { font-size:36px; }
-  }
-</style>
-</head>
-<body>
-<header>
-  <a class="brand" href="https://wallco.ai">wallco.ai</a>
-  <span class="tag">Hot or Not · v1</span>
-</header>
-<main>
-  <h1>Hot or Not</h1>
-  <p class="sub">Speed-vote AI-original wallpaper. Your taste teaches the system.</p>
-  <div id="wc-game-banner"></div>
-
-  <div id="card">
-    <img id="card-img" alt="design">
-    <div class="meta">
-      <div>
-        <div id="card-title" class="title"></div>
-        <div id="card-sub" class="sub2"></div>
-      </div>
-      <a id="card-view" class="viewfull" target="_blank">view full →</a>
-    </div>
-  </div>
-
-  <div id="buttons">
-    <button class="btn btn-not"  data-action="not">NOT</button>
-    <button class="btn btn-skip" data-action="skip">SKIP</button>
-    <button class="btn btn-hot"  data-action="hot">HOT</button>
-  </div>
-  <div class="keys">arrows: ← NOT · → HOT · space SKIP</div>
-
-  <div class="history" id="history"></div>
-</main>
-<footer>
-  <p>© <span id="yr"></span> SAND, LLC · part of <a href="https://wallco.ai">wallco.ai</a> / <a href="https://designerwallcoverings.com">Designer Wallcoverings</a></p>
-</footer>
-<script>document.getElementById('yr').textContent = new Date().getFullYear();</script>
-<script src="/js/wallco-game.js"></script>
-<script>
-var seen = [], picks = [];
-
-async function load(){
-  var url = '/api/design/random' + (seen.length ? '?exclude=' + seen.slice(-30).join(',') : '');
-  var r = await (await fetch(url)).json();
-  if (!r.id) return;
-  document.getElementById('card').className = '';
-  document.getElementById('card-img').src = r.image_url;
-  document.getElementById('card-title').textContent = r.title || '';
-  document.getElementById('card-sub').textContent = (r.category||'') + (r.dominant_hex ? ' · ' + r.dominant_hex : '');
-  document.getElementById('card-view').href = 'https://wallco.ai/design/' + r.id;
-  document.getElementById('card').dataset.id = r.id;
-  document.getElementById('card').dataset.title = r.title || '';
-  document.getElementById('card').dataset.image = r.image_url;
-  seen.push(r.id);
-}
-
-async function act(action, evt){
-  var card = document.getElementById('card');
-  var id = parseInt(card.dataset.id, 10);
-  if (!id) return;
-  var anim = action==='hot' ? 'swipe-right' : action==='not' ? 'swipe-left' : 'swipe-up';
-  card.classList.add(anim);
-
-  if (action === 'hot' || action === 'not') {
-    var score = action === 'hot' ? 5 : 1;
-    fetch('/api/design/' + id + '/user-vote', {
-      method:'POST',
-      headers:{'Content-Type':'application/json'},
-      body: JSON.stringify({score: score, quick: true})
-    });
-  }
-  if (window.WC) WC.reward(action, evt);
-
-  picks.unshift({id: id, action: action, title: card.dataset.title, image_url: card.dataset.image});
-  picks = picks.slice(0, 6);
-  document.getElementById('history').innerHTML = picks.map(function(h){
-    return '<a class="' + h.action + '" href="https://wallco.ai/design/' + h.id + '" target="_blank" title="' + h.title + '"><img src="' + h.image_url + '"></a>';
-  }).join('');
-
-  setTimeout(load, 280);
-}
-
-document.querySelectorAll('.btn').forEach(function(b){
-  b.addEventListener('click', function(evt){ act(b.dataset.action, evt); });
-});
-document.addEventListener('keydown', function(e){
-  if (e.key === 'ArrowLeft')  act('not');
-  else if (e.key === 'ArrowRight') act('hot');
-  else if (e.key === ' ' || e.key === 'ArrowUp') { e.preventDefault(); act('skip'); }
-});
-load();
-</script>
-</body>
-</html>`);
-});
-
-app.get('/health', (_req, res) => {
-  res.json({ ok: true, service: 'wallco-hot-or-not', port: PORT, upstream: UPSTREAM });
-});
-
-app.listen(PORT, '0.0.0.0', () => {
-  console.log(`wallco hot-or-not → http://0.0.0.0:${PORT}  · upstream: ${UPSTREAM}`);
-});
diff --git a/package-lock.json b/package-lock.json
index f1a06cf..0ed869e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,6 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
-        "@dw/inline-editor": "file:../inline-editor",
         "compression": "^1.8.1",
         "dotenv": "^17.4.2",
         "express": "^5.2.1",
@@ -22,6 +21,7 @@
     "../inline-editor": {
       "name": "@dw/inline-editor",
       "version": "0.1.0",
+      "extraneous": true,
       "dependencies": {
         "cheerio": "^1.0.0",
         "cookie-parser": "^1.4.7",
@@ -31,10 +31,6 @@
         "sanitize-html": "^2.13.1"
       }
     },
-    "node_modules/@dw/inline-editor": {
-      "resolved": "../inline-editor",
-      "link": true
-    },
     "node_modules/accepts": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
diff --git a/package.json b/package.json
index cee773d..65a4891 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,6 @@
   "license": "ISC",
   "description": "",
   "dependencies": {
-    "@dw/inline-editor": "file:../inline-editor",
     "compression": "^1.8.1",
     "dotenv": "^17.4.2",
     "express": "^5.2.1",
diff --git a/scripts/__pycache__/spoonflower_lib.cpython-314.pyc b/scripts/__pycache__/spoonflower_lib.cpython-314.pyc
deleted file mode 100644
index ca8e1a8..0000000
Binary files a/scripts/__pycache__/spoonflower_lib.cpython-314.pyc and /dev/null differ
diff --git a/scripts/generate-pollock.sh.bak b/scripts/generate-pollock.sh.bak
deleted file mode 100755
index 622d6cc..0000000
--- a/scripts/generate-pollock.sh.bak
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env bash
-# One-shot batch: 12 Jackson Pollock-style drip/splatter wallpaper designs
-# in Steve's muted Designer-Wallcoverings palette.
-# Inspired by Pollock "Number 17A" ($200M private 2015) and tapestry-scale
-# drip canvases. NOT a copy — emulate the gestural drip-splatter language
-# in our color vocabulary.
-# 12 base designs × 3 colorway variants = 48 total.
-
-cd "$(dirname "$0")/.."
-PSQL="/opt/homebrew/opt/postgresql@14/bin/psql"
-
-# 12 colorway pairs — each is (ground, drip-figure, accent, shadow), all in
-# the same hue family per Steve's hue-sync directive.
-declare -a CWS=(
-  "Cream Loden|#F2EADB|#3C4844|#9B8050|#1A2520"
-  "Bone Ink|#F2EADB|#1F2740|#C9A14B|#0B0F1C"
-  "Charcoal Cream|#F2EADB|#3D3833|#C9A14B|#0F0D0B"
-  "Saddle Mocha|#F2EADB|#5C4033|#A78863|#2A1810"
-  "Smoke Ash|#F2EADB|#6B6660|#A8A39C|#3A3530"
-  "Forest Loden|#F2EADB|#3C4844|#A8B79A|#1A2520"
-  "Bordeaux Velvet|#F2EADB|#5C2329|#C84A3A|#2A0E10"
-  "Oxblood|#F2EADB|#722F2F|#A85040|#3A1010"
-  "Midnight Navy|#F2EADB|#1B2A3A|#3E6B82|#0B1018"
-  "Deep Plum|#F2EADB|#3D2B3F|#856080|#1A101E"
-  "Pine Charcoal|#F2EADB|#3A4A45|#9CA09B|#1A2520"
-  "Greenhouse Glass|#F2EADB|#1F4E47|#85A095|#0B201C"
-)
-
-for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
-  IFS='|' read -r NAME GROUND FIGURE ACCENT SHADOW <<< "${CWS[$((i-1))]}"
-  PROMPT="Jackson Pollock drip-splatter abstract wallpaper — ${NAME} colorway. Ground: solid ${GROUND}. Primary drip ink: ${FIGURE}. Secondary accent splatter: ${ACCENT}. Tertiary depth splatter: ${SHADOW}. EXACTLY 4 COLORS — ground + figure + accent + shadow, all in the same hue family (hue-sync). Composition: chaotic gestural drip painting — long thrown lines of ink, splatter clusters, dripped pools, swirling skeins. The marks are FLAT FILLED SHAPES (not painted brush strokes) — each splatter, drip, and pool is one uniform solid ink color with hard edges. NO half-tones, NO chromatic gradient, NO color bleed between layers. The 4 colors are layered in distinct flat zones — drip-line, accent-splatter, shadow-pool, ground — never blended. References: Pollock 'Number 17A' \$200M drip painting, but rendered as a 4-color screen-print serigraph (Andy Warhol pop-art treatment of Pollock's drip language), Maya Romanoff handcrafted luxe wallcovering. Generous breathing room — at least 60% of the canvas shows clean ground tone between drip clusters. Square aspect ratio, seamless tile repeat, no edge artifacts."
-  echo "=== generating $i/12: $NAME ==="
-  RESULT=$(GEN_BACKEND=replicate node scripts/generate_designs.js --n 1 --kind seamless_tile --width 54 --height 54 --category pollock-splatter --prompts "$PROMPT" 2>&1 | tail -3)
-  ID=$(echo "$RESULT" | grep -oE "#[0-9]+" | head -1 | tr -d '#')
-  if [ -n "$ID" ]; then
-    LP=$("$PSQL" dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=$ID;")
-    if [ -f "$LP" ]; then
-      # 4-color quantize to enforce hue-sync palette
-      QUANT_COLORS=4 python3 scripts/quantize-2tone.py "$LP" 2>&1 | grep -v Deprecation | tail -1
-      "$PSQL" dw_unified -c "UPDATE spoon_all_designs SET is_published=TRUE WHERE id=$ID;" >/dev/null 2>&1
-      echo "  published id=$ID"
-    fi
-  fi
-sleep 5
-done
-echo "=== done — all 12 Pollock-splatter designs published ==="
diff --git a/scripts/generate-sherman-circus.sh.bak b/scripts/generate-sherman-circus.sh.bak
deleted file mode 100755
index 1181c79..0000000
--- a/scripts/generate-sherman-circus.sh.bak
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env bash
-# Cindy Sherman-inspired circus wallpaper collection — 20 designs.
-# Sherman's "Clowns" series (2003) + "Untitled Film Stills" + her hyper-staged
-# theatrical-grotesque vibe, rendered as Designer Wallcoverings 4-color
-# screen-print wallpaper. Tight hue-sync palette per design.
-
-cd "$(dirname "$0")/.."
-PSQL="/opt/homebrew/opt/postgresql@14/bin/psql"
-
-# 20 Sherman-circus subjects
-declare -a SUBJECTS=(
-  "sad-eyed clown portrait silhouette, painted lips drooping, ruffled collar, single tear"
-  "menacing ringmaster in tall top hat silhouette holding a curled whip"
-  "trapeze artist mid-swing, silhouette frozen against starburst rays"
-  "lion tamer with arched whip facing a single roaring lion silhouette"
-  "fortune teller draped in scarves, hands cupped around a crystal ball"
-  "bearded lady in profile silhouette, beard cascading to the shoulder"
-  "strongman with handlebar mustache lifting a barbell with stars at each end"
-  "fire-eater silhouette mid-breath with curling flame ribbon"
-  "tightrope walker carrying a parasol mid-step on a thin line"
-  "snake charmer cross-legged with cobra rising from a woven basket"
-  "contortionist folded into impossible knot silhouette"
-  "tattooed lady silhouette with stars + anchors + roses visible as cut-outs"
-  "knife-thrower mid-throw with knife silhouette frozen in flight"
-  "circus ticket-booth attendant peering out of an arched window"
-  "ventriloquist with dummy on knee, both in profile"
-  "human cannonball silhouette emerging from a cannon mouth with smoke ribbon"
-  "carousel horse with arched neck and flowing mane silhouette"
-  "circus elephant standing on a pedestal with one foot raised"
-  "magician pulling rabbit silhouette from top hat in profile"
-  "clown family group portrait — three figures in descending sizes with ruffled collars"
-)
-
-# 20 hue-sync 4-color quartets
-declare -a CWS=(
-  "Greasepaint White|#F2EADB|#3D3833|#C84A3A|#0F0D0B"
-  "Velvet Bordeaux|#F2EADB|#5C2329|#A85040|#2A0E10"
-  "Carnival Ink|#F2EADB|#1F2740|#C9A14B|#0B0F1C"
-  "Midnight Sawdust|#E8D9B8|#3A2A1C|#7A4A28|#1A100A"
-  "Ringmaster Oxblood|#F2EADB|#722F2F|#A85040|#3A1010"
-  "Stardust Forest|#F2EADB|#3C4844|#A8B79A|#1A2520"
-  "Smoke Curtain|#F2EADB|#6B6660|#A8A39C|#3A3530"
-  "Plum Cabaret|#F2EADB|#3D2B3F|#856080|#1A101E"
-  "Saddle Pony|#F2EADB|#7A4A28|#A78863|#3A2010"
-  "Mossy Big-Top|#F2EADB|#3A4A45|#9CA09B|#1A2520"
-  "Gilded Lobby|#F2EADB|#1F2740|#9B8050|#0B0F1C"
-  "Charcoal Sideshow|#F2EADB|#3D3833|#A8A39C|#0F0D0B"
-  "Bone Tightrope|#E8D9B8|#3A2A1C|#D8C8AA|#1A100A"
-  "Indigo Pavilion|#F2EADB|#1B2A3A|#3E6B82|#0B1018"
-  "Pewter Tent|#F2EADB|#8A857B|#C9A14B|#3A3530"
-  "Greenhouse Greasepaint|#F2EADB|#1F4E47|#85A095|#0B201C"
-  "Antique Brass|#F2EADB|#9B8050|#3A2A1C|#3D2F18"
-  "Carnival Charcoal|#F2EADB|#3D3833|#5C2329|#0F0D0B"
-  "Smoke and Mirrors|#F2EADB|#566554|#9CA09B|#1A2520"
-  "Bone Velvet|#F2EADB|#722F2F|#D7C9A8|#3A1010"
-)
-
-for i in $(seq 1 20); do
-  IFS='|' read -r NAME GROUND FIGURE ACCENT SHADOW <<< "${CWS[$((i-1))]}"
-  SUBJECT="${SUBJECTS[$((i-1))]}"
-  PROMPT="Cindy Sherman circus wallpaper — ${NAME} colorway. Ground: solid ${GROUND}. Figure ink: ${FIGURE}. Accent: ${ACCENT}. Shadow: ${SHADOW}. EXACTLY 4 COLORS — ground + figure + accent + shadow, all in the same hue family (hue-sync). Subject: ${SUBJECT}. ONE central figure or vignette per tile, anchored on the vertical axis of symmetry, large-scale, generous breathing room of clean ground around it. Theatrical / grotesque / Cindy Sherman 'Clowns 2003' staging — slightly unnerving, hyper-staged, art-photography composition. Each figure rendered as a FLAT STENCIL SILHOUETTE in the figure ink, with small ACCENT details (lips, eyes, fingernails, sequins, ribbon trim) in the accent color, and SHADOW pools beneath the figure in the shadow color. NO bleed between layers — each of the 4 colors is a distinct flat zone. References: Cindy Sherman 'Clowns 2003' photography, Maya Romanoff handcrafted luxe wallcovering, Designers Guild theatrical, Saul Bass film-poster minimalism. Square aspect ratio, seamless tile repeat. NO halftone, NO gradient, NO airbrush, NO ghost layer."
-  echo "=== sherman $i/20: $NAME — $SUBJECT ==="
-  RESULT=$(GEN_BACKEND=replicate node scripts/generate_designs.js --n 1 --kind seamless_tile --width 54 --height 54 --category sherman-circus --prompts "$PROMPT" 2>&1 | tail -3)
-  ID=$(echo "$RESULT" | grep -oE "#[0-9]+" | head -1 | tr -d '#')
-  if [ -n "$ID" ]; then
-    LP=$("$PSQL" dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=$ID;")
-    if [ -f "$LP" ]; then
-      QUANT_COLORS=4 python3 scripts/quantize-2tone.py "$LP" 2>&1 | grep -v Deprecation | tail -1
-      "$PSQL" dw_unified -c "UPDATE spoon_all_designs SET is_published=TRUE WHERE id=$ID;" >/dev/null 2>&1
-      echo "  published id=$ID"
-    fi
-  fi
-sleep 5
-done
-echo "=== done — all 20 Sherman-circus designs published ==="
diff --git a/scripts/night-builder.js.bak b/scripts/night-builder.js.bak
deleted file mode 100755
index 96fed35..0000000
--- a/scripts/night-builder.js.bak
+++ /dev/null
@@ -1,906 +0,0 @@
-#!/usr/bin/env node
-// All-night Replicate generator.
-//
-// Two tracks, interleaved:
-//   A) Muted Gucci-palette CACTUS variants (extends the existing 10 colorways)
-//   B) Drunk-animals → designer-elevated reinterpretation (heritage-textile/British-archival
-//      register, muted Gucci palette, single-ink, archival gouache)
-//
-// Steve's brief: "continue creating patterns all night with the subdued hues
-// of grey, taupe, cream, ivory, gucci browns, recreate the drunk animals with
-// much more designer like designs."
-//
-// Hard rules:
-//   - Budget cap: $20 (≈2000 Replicate SDXL images at ~$0.01 ea)
-//   - Halt: touch /tmp/halt-night-builder.flag to stop after current image
-//   - Stop time: 07:30 local (Steve wakes ~8 AM)
-//   - Settlement gate inline (single-ink + no Part B elements = OK)
-//   - is_published=TRUE so Steve sees them in /designs immediately
-//   - Category: 'drunk-animals-designer' for the B-track (NOT 'drunk-animals')
-//     so Steve can sort + dedupe vs the kitsch originals
-//   - Cooldown: 5s between calls to be polite to Replicate
-//
-// Cost tracking: each Replicate image hits the cost-tracker via log.js
-// (already wired in generate_designs.js).
-
-const { spawnSync } = require('child_process');
-const fs = require('fs');
-const path = require('path');
-const { execSync } = require('child_process');
-
-const ROOT = path.join(__dirname, '..');
-const HALT_FLAG = '/tmp/halt-night-builder.flag';
-const LOG = '/tmp/night-builder.log';
-const STOP_HOUR = 7;   // local: stop at 07:30 next-morning
-const STOP_MIN  = 30;
-const BUDGET_CAP_USD = 20.0;
-const PER_IMAGE_USD = 0.011;   // Replicate SDXL ~$0.01 (incl. 10% safety margin)
-const COOLDOWN_MS = 5000;
-
-// Compute the actual next-7:30-AM deadline relative to script start.
-function computeStopDeadline() {
-  const now = new Date();
-  const d = new Date(now);
-  d.setHours(STOP_HOUR, STOP_MIN, 0, 0);
-  // If 7:30 today is already in the past, target 7:30 tomorrow.
-  if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
-  return d;
-}
-const STOP_DEADLINE = computeStopDeadline();
-
-const MUTED_PALETTE = [
-  { slug: 'smoke-ash',       name: 'Smoke Ash',       hex: '#6B6660', tone: 'warm-mid-grey' },
-  { slug: 'mushroom-taupe',  name: 'Mushroom Taupe',  hex: '#8A7E70', tone: 'designer-taupe' },
-  { slug: 'linen-cream',     name: 'Linen Cream',     hex: '#EDE4D3', tone: 'soft-cream' },
-  { slug: 'bone-ivory',      name: 'Bone Ivory',      hex: '#F2EADB', tone: 'warm-ivory' },
-  { slug: 'gucci-cocoa',     name: 'Gucci Cocoa',     hex: '#5C4033', tone: 'deep-cocoa' },
-  { slug: 'antique-tan',     name: 'Antique Tan',     hex: '#A78863', tone: 'warm-camel' },
-  { slug: 'stone-pewter',    name: 'Stone Pewter',    hex: '#7A736A', tone: 'cool-pewter' },
-  { slug: 'saddle-mocha',    name: 'Saddle Mocha',    hex: '#4A3528', tone: 'saddle-leather-brown' },
-  { slug: 'champagne-linen', name: 'Champagne Linen', hex: '#D7C9A8', tone: 'champagne-neutral' },
-  { slug: 'charcoal-smoke',  name: 'Charcoal Smoke',  hex: '#3D3833', tone: 'near-black-charcoal' },
-];
-
-const DB = 'dw_unified';
-const PSQL = `psql ${DB} -At -q`;
-function psql(sql) {
-  return execSync(PSQL, { input: sql, encoding: 'utf8' }).trim();
-}
-
-function nowIso() { return new Date().toISOString(); }
-function logLine(s) {
-  const line = `[${nowIso()}] ${s}`;
-  console.log(line);
-  fs.appendFileSync(LOG, line + '\n');
-}
-
-function pickColorway() {
-  return MUTED_PALETTE[Math.floor(Math.random() * MUTED_PALETTE.length)];
-}
-
-// ── Track A: cactus variants in muted palette ────────────────────────
-const CACTUS_PATTERNS = [
-  'tall saguaro forms with raised arms, scattered prickly-pear paddles',
-  'dense agave rosettes with whip-stem ocotillo verticals',
-  'barrel cactus rosettes and cholla branches in tight tessellation',
-  'sprawling prickly-pear paddle clusters as a vertical wallpaper repeat',
-  'ocotillo whip-stem fountain pattern with small barrel-cactus accents',
-];
-
-function composeCactusPrompt(cw) {
-  const pattern = CACTUS_PATTERNS[Math.floor(Math.random() * CACTUS_PATTERNS.length)];
-  return (
-    `Cactus Repeat — ${cw.name} colorway. ` +
-    `Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. ` +
-    `Sonoran desert cacti as FLAT STENCIL SILHOUETTES: ${pattern}, arranged in a tessellated seamless wallpaper repeat. ` +
-    `Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. ` +
-    `All cacti upright, oriented vertically, symmetric placement on the vertical axis. ` +
-    `Edge-to-edge tessellation, generous breathing room of clean ground between motifs. ` +
-    `Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track B: DEBAUCHED ANIMALS (rewritten 2026-05-20 per Steve directive) ──
-// References:
-//   1. Asterix drunk-Roman-legionnaire pose (Uderzo): staggering gait, leg
-//      kicking sideways, helmet askew, X-pupil eyes, wobble-line motion arc.
-//   2. Eadweard Muybridge "Animal Locomotion" sequential-motion plates: each
-//      animal captured at multiple frozen poses showing the full gait cycle,
-//      tiled side-by-side. The original "movement sneak" before cinema existed.
-// Tone: HEDONISTIC, DEBAUCHED, BACCHANALIAN. Cocktail party gone fully off
-// the rails. Animals are mid-stagger, mid-collapse, mid-tumble — never in
-// dignified repose.
-const ANIMAL_DESCRIPTORS = [
-  // STAGGERING / FALLING
-  'staggering bear mid-stumble with one paw airborne, Asterix-drunk-legionnaire pose',
-  'tumbling fox falling backward limbs splayed',
-  'reticulated giraffe slumped against an invisible wall, legs splayed Bambi-on-ice',
-  'red panda hanging upside down from a phantom branch, eyes spiraled',
-  'lemur mid-cartwheel, limbs flung in four directions, Uderzo motion arcs around it',
-  'sloth lying flat on its back, all four limbs in the air, classic dead-drunk pose',
-  'capybara mid-belly-laugh slumped sideways, hiccup bubble above',
-  'orangutan kicking one leg high in a Charleston-stagger, the other arm windmilling',
-  'raccoon in a top hat falling face-first toward the viewer',
-  'elephant calf doing a full pratfall on its back legs, trunk waving wildly',
-  'tree frog mid-leap and missing, arms grasping at empty air',
-  'Bornean orangutan blowing a kiss with one eye crossed, drink mid-spill',
-  'koala wrapped around a bottle larger than itself, swaying',
-  'wallaby boxer mid-haymaker against nothing, swinging at phantoms',
-  'tasmanian devil mid-spin, dust-cloud lines around it',
-  'fennec fox flopped on its side, tongue lolling out',
-  // DANCING / CABARET POSES
-  'flamingo in a high-kick cancan pose, feathers flying',
-  'okapi in a tango dip, partner is an invisible silhouette',
-  'pangolin doing a one-legged drunken pirouette, scales spread out',
-  'capybara in a top hat doing the Charleston',
-  'meerkat trio in a chorus-line kick',
-  'aardvark mid-Lindy-hop swing-out, partner missing',
-  'lemur ballroom-glide with one paw extended, the other holding a cocktail',
-  'red panda in disco pose, one arm up Saturday-Night-Fever style',
-  // PASSED OUT / OBLIVION
-  'walrus snoring with three Zs above it, X-pupils',
-  'turtle on its back legs in the air, hiccup bubble',
-  'manatee floating belly-up with bubbles around its mouth',
-  'wombat sprawled on a velvet ottoman, paws curled',
-  'lazy panther draped over a bar-stool silhouette',
-  'sleepy hedgehog curled inside an empty cocktail glass',
-  // MID-PRATFALL
-  'chimpanzee mid-banana-peel slip (peel falling, not held)',
-  'baboon doing a forward roll into a stack of empty glasses',
-  'mandrill mid-faceplant, legs in the air',
-  'gorilla doing a dignified slow-motion topple, one finger raised',
-  // INTOXICATED VIBE
-  'platypus floating in zero-gravity with a snifter, monocle askew',
-  'armadillo curled mid-tumble, motion-arc behind it',
-  'tapir wobbling on two legs holding a martini glass overhead',
-  'binturong (bearcat) dangling from a velvet rope by its tail, drink in mouth',
-  'ocelot lounging on a chaise with smoking pipe and one eye closed',
-  'civet stretched out on a Persian rug, paws to the ceiling',
-  // BACCHANALIAN HARVEST
-  'wild boar reclined against a barrel, snout sniffing the air',
-  'bison crowned with a wreath of curlicue smoke, head tipped back',
-  'ibex doing a goat-headstand on a stack of bottles',
-  'fallow deer mid-leap with hooves crossed, antlers tangled with ribbons',
-  // CLASSIC CARTOON-DRUNK
-  'cat with five-o\'clock-shadow holding a cocktail like Tom of Tom-and-Jerry',
-  'penguin in a bowtie tipping a top hat with one flipper',
-  'walrus playing a tiny piano with one tusk',
-  'badger in a smoking jacket striking a Sherlock Holmes pose',
-  'opossum hanging by its tail clutching a martini upside-down',
-  // SMOKE-CLOUD / VAPOR POSES
-  'fox blowing a perfect smoke-ring, eye squinted',
-  'rabbit reclining in a hookah-style pose, pipe in paw',
-  'mongoose with three smoke-curls rising from a pipe',
-  'lizard breathing out a single curling vapor-trail',
-  // CRYPTID DEBAUCHERY (real animals only, no fantasy)
-  'aye-aye gesturing wildly with a long finger raised',
-  'star-nosed mole sniffing a cocktail with thirteen tentacles',
-  'pangolin doing a slow Charleston in scaly tuxedo',
-  'kinkajou dangling from a chandelier with a coupe',
-  // MISC SURREAL POSES
-  'sloth doing a slow-motion crowd-surf on raised paws',
-  'porcupine quill-up flexing arms strongman pose',
-  'capybara DJ-ing on a turntable made of orange slices',
-  'badger conducting a phantom orchestra, baton in paw',
-];
-
-const DRINK_DESCRIPTORS = [
-  'half-empty long-stemmed coupe tilted at 45° with champagne sloshing out',
-  'cut-crystal whiskey tumbler frozen mid-toss, amber liquid arcing through the air',
-  'martini glass tipped over, two olives rolling away',
-  'bottle of pilsner held inverted, foam splashing onto the ground',
-  'red wine balloon mid-spill, dark liquid streaming sideways',
-  'frosted vodka decanter being chugged straight from the neck',
-  'tequila shot raised in toast, lime wedge between teeth',
-  'cognac snifter cradled like a baby',
-  'Aperol spritz in one hand, second drink in the other',
-  'magnum of champagne held aloft and shaken, cork popping with a comic burst',
-  'rolled joint trailing a wispy smoke-curl ribbon',
-  'oversize pipe with three smoke-curls forming a clover',
-  'cocktail with three umbrellas crammed into it',
-  'pint glass overflowing with foam pouring over the sides',
-];
-
-// Muybridge-style: sometimes show the animal in a sequence of 3 frozen motion
-// poses (instead of one), tiled side-by-side. Used 30% of the time.
-const MOTION_FORMATS = [
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  // 3 out of 10 → Muybridge sequence:
-  'Muybridge-style sequence of three frozen poses of the SAME animal mid-stagger, tiled side-by-side left-to-right showing the full stumble cycle',
-  'Muybridge-style sequence of three frozen poses of the SAME animal collapsing forward, tiled side-by-side left-to-right',
-  'Muybridge-style sequence of four frozen poses of the SAME animal mid-pratfall, tiled in a 2x2 grid',
-];
-
-const BOTANICAL_SETTINGS = [
-  'set among silhouetted Ghost Orchids',
-  'against a damask-trellis backdrop',
-  'framed by Botticelli-style arabesques',
-  'surrounded by hand-painted Magnolia branches',
-  'within a heritage-textile-Audubon herbarium frame',
-  'set in a British-archival chinoiserie panel',
-  'flanked by ogival damask medallions',
-  'centered on a wunderkammer specimen plate',
-];
-
-function composeDrunkAnimalPrompt(cw) {
-  const animal    = ANIMAL_DESCRIPTORS[Math.floor(Math.random() * ANIMAL_DESCRIPTORS.length)];
-  const drink     = DRINK_DESCRIPTORS[Math.floor(Math.random() * DRINK_DESCRIPTORS.length)];
-  return (
-    `Cocktail-hour wallpaper — ${animal} cradling a ${drink}. ` +
-    `Colorway: ${cw.name} (${cw.hex}) ground with bone-ivory (#F2EADB) figure ink. ` +
-    `Each animal-and-drink vignette is one FLAT STENCIL SILHOUETTE in bone-ivory — no interior shading, no anatomical detail, no fur texture. The drink glass is part of the same silhouette, also flat ink. ` +
-    `Vignettes arranged in a symmetric tessellated repeat, generous breathing room of clean ground between them. ` +
-    `Strictly no botanical background, no foliage filler, no decorative arabesques behind the figures — the ground is one uniform flat color with nothing on it except the silhouette vignettes. ` +
-    `No bananas, no grapes, no birds, no butterflies. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── SHARED art direction — rewritten 2026-05-20 to ELIMINATE the bleed-through
-// layered-background problem. Anchor refs swapped from natural-history plates
-// (Haeckel/Merian — those DO have atmospheric depth + layered washes) to pure
-// 2-tone serigraph references (Warhol/Lichtenstein/Toulouse-Lautrec/WPA poster
-// /Saul Bass/pochoir stencil — those are GUARANTEED flat 2-tone with NO depth).
-// Rewritten 2026-05-20 — Steve directive: "way too busy for most designs..
-// keep it simple". Old SHARED_ARTDIR encouraged tessellated edge-to-edge
-// filling. New version forces SPARSE motifs + GENEROUS NEGATIVE SPACE.
-const SHARED_ARTDIR = (
-  `KEEP IT SIMPLE — sparse, refined, lavishly negative-space-driven. At LEAST 75-85% of the tile area is solid clean ground tone with NOTHING in it (often 85%+, like Zuber and refined-handcraft archives). Motifs are FEW (2-4 per tile maximum), well-spaced, never edge-to-edge, never tessellated, never cluttered. The empty ground IS the design — motifs are punctuation, not pattern-fill. ` +
-  `Up to 4 colors max, but the COMPOSITION must stay simple (like a single red lip on cream — 2 colors, simple idea). Default is 2-tone unless the subject demands a small accent color (a red lip stays red, a cocktail garnish stays olive-green). Never more than 4 distinct ink tones. ` +
-  `ABSOLUTELY NO bleed between layers — bleed looks cheap and is FORBIDDEN. Single-layer screen-print only. ` +
-  `NO ghost layer, NO faded background, NO atmospheric depth, NO secondary motifs behind the main figures. The ground is one completely uniform flat solid color from edge to edge. ` +
-  `Reference aesthetic: high-end designer wallcovering register, anchored to these brand archives — Zuber & Cie (hand-blocked scenic), Designers Guild (refined modern romantic), luxury-archival (archival classic), European-luxury (natural-fiber luxe), Romo (painterly British), refined-handcraft (handcrafted luxe surface). Subtle. Sparse. Refined. Negative-space-driven. Single-pass silkscreen with two ink screens. ` +
-  `Hard clean razor-sharp edges. No half-tones, no gradient, no watercolor, no crosshatch shading. Every figure region is one flat color. ` +
-  `Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar. ` +
-  `Square aspect ratio, seamless tile repeat (edges meet cleanly), flat-paper rendering, no watermark, no signature, no text, no logos.`
-);
-
-// Universal negative prompt — passed to Replicate as --negative-prompt. Catches
-// the model's instinct to add depth/layers even when text anti-prompts fail.
-const SHARED_NEGATIVE = (
-  `gradient, ombre, fade, faded, ghosted, ghost layer, atmospheric perspective, depth, layered background, ` +
-  `secondary motifs behind, lighter background copies, halftone, half-tone, chromatic gradient, watercolor wash, ` +
-  `airbrush, soft edges, blurry edges, anti-aliased blur, crosshatching, engraving lines, stippling, ` +
-  `three colors, four colors, multi-color, textured ground, mottled background, shadow, drop shadow, ` +
-  `Haeckel, Merian, Audubon, natural-history plate, botanical plate, engraving, cartoon, Pixar, 3D render`
-);
-
-const CHINOISERIE_MOTIFS = [
-  'pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches',
-  'kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above',
-  'lotus blossom roundels in vertical stacks separated by bamboo verticals',
-  'mountain-and-temple silhouette repeats with stylized scholar-cloud forms',
-];
-function composeChinoiseriePrompt(cw) {
-  const motif = CHINOISERIE_MOTIFS[Math.floor(Math.random() * CHINOISERIE_MOTIFS.length)];
-  return (
-    `Chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Motif: ${motif}, arranged in a tessellated seamless repeat. ` +
-    `British-archival Mandarin / Ming-dynasty woodblock register. ` + SHARED_ARTDIR
-  );
-}
-
-const TOILE_VIGNETTES = [
-  'a pair of mirror-image country-cottage scenes flanking a central fountain',
-  'symmetric pastoral courtship scene under arching elm-tree silhouettes',
-  'paired equestrian-rider vignettes facing each other across a ribbon-tied bouquet',
-  'symmetric harvest-basket scene with sheaves of wheat and hand-tools',
-];
-function composeToilePrompt(cw) {
-  const vig = TOILE_VIGNETTES[Math.floor(Math.random() * TOILE_VIGNETTES.length)];
-  return (
-    `Toile de Jouy wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-engraving. ` +
-    `Vignette: ${vig}, repeated as a seamless symmetric tile, every figure rendered in fine-line copperplate-engraving style with cross-hatched shading replaced by solid-flat figure tone. ` +
-    `Pierre-Frey / Manuel Canovas archival toile register. ` + SHARED_ARTDIR
-  );
-}
-
-const STRIPE_STYLES = [
-  'wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges',
-  'awning stripe — uniform 6cm vertical bands alternating ground and figure tones',
-  'beaded vertical lines with small ornamental dot-clusters between stripes',
-  'fine triple-pinstripe groups separated by wider blank channels',
-];
-function composeStripePrompt(cw) {
-  const style = STRIPE_STYLES[Math.floor(Math.random() * STRIPE_STYLES.length)];
-  return (
-    `Vertical stripe wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure stripes: bone-ivory (#F2EADB) single-ink. ` +
-    `Pattern: ${style}. ` +
-    `Robert Kime / Colefax & Fowler archival stripe register. ` + SHARED_ARTDIR
-  );
-}
-
-const QUATREFOIL_FIELDS = [
-  'classical quatrefoil tessellation, each quatrefoil oriented identically, edge-to-edge tight grid',
-  'gothic-window quatrefoil-plus-circle interlock, symmetric 4-fold',
-  'moorish-tile quatrefoil with negative-space cross channels',
-  'ogee-quatrefoil hybrid — pointed-arch ogee crossed with classical quatrefoil',
-];
-function composeQuatrefoilPrompt(cw) {
-  const field = QUATREFOIL_FIELDS[Math.floor(Math.random() * QUATREFOIL_FIELDS.length)];
-  return (
-    `Quatrefoil / ogee geometric wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Field: ${field}. ` +
-    `Owen Jones Grammar of Ornament register, strict mathematical tessellation. ` + SHARED_ARTDIR
-  );
-}
-
-const DIAMOND_TRELLIS_FIELDS = [
-  'narrow ogival diamond lattice with empty centers (just the lattice lines)',
-  'diamond trellis with a single tiny center-medallion in each diamond',
-  'crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections',
-  'fine-line diamond lattice with classical tassels at each lattice node',
-];
-function composeDiamondTrellisPrompt(cw) {
-  const field = DIAMOND_TRELLIS_FIELDS[Math.floor(Math.random() * DIAMOND_TRELLIS_FIELDS.length)];
-  return (
-    `Diamond trellis wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-trellis. ` +
-    `Field: ${field}. NOT a damask, NOT a heavy medallion field — light open trellis only. ` +
-    `Soane Britain / De Gournay archival trellis register. ` + SHARED_ARTDIR
-  );
-}
-
-// ── Track H: DRUNK ZOO 36" (Steve-approved 2026-05-20) ──────────────
-// Replaces the giraffe-only track with a broader symmetric-zoo track.
-// 36" repeat — wall-scale large pattern. Each design = one symmetric
-// paired-animal vignette in 19c-engraving aesthetic.
-const ZOO_VIGNETTES = [
-  'two reticulated giraffes facing each other in symmetric profile, necks crossing in a heart-shape, both sipping champagne coupes',
-  'pair of mirror-image giraffes flanking a central acacia tree, both holding cocktail glasses',
-  'mother-and-calf giraffe pair, mother arching over the calf, both with bottles',
-  'pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved',
-  'two sloths hanging in mirror-image from a central acacia branch, each with a cocktail glass',
-  'pair of red pandas in symmetric grooming pose, central bottle between them',
-  'two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern',
-  'pair of capybaras in symmetric resting pose, central mint julep glass between them',
-  'two African elephant calves in symmetric trunk-raised pose, central wine glass',
-  'pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them',
-  'two tipsy frogs in symmetric leaping pose mirrored, central decanter',
-];
-function composeDrunkZoo36Prompt(cw) {
-  const vig = ZOO_VIGNETTES[Math.floor(Math.random() * ZOO_VIGNETTES.length)];
-  return (
-    `Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading, no anatomical line-detail, no spots, no stripes, no fur texture. Spots/stripes appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track I: FACE-SKULL DAMASK (Steve directive 2026-05-20) ──────────
-// Damask is purged unless it has faces or skulls. This track generates new
-// damask designs where the medallion FILL is a hand-engraved face or skull
-// rendered in single-ink screen-print style.
-const FACE_SKULL_MOTIFS = [
-  'memento-mori skull at the center of each ogival damask medallion, crossed bones forming the lattice spines',
-  'classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders',
-  'death\'s-head moth skull pattern at each damask cell, wings forming the connecting flourishes',
-  'Venetian carnival mask face filling each damask medallion, ribbon-ties forming the lattice',
-  'Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds',
-  'Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion',
-  'anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink',
-  'phantom-of-the-opera half-mask face at each medallion, opera-cape ribbons forming the lattice',
-];
-function composeFaceSkullDamaskPrompt(cw) {
-  const motif = FACE_SKULL_MOTIFS[Math.floor(Math.random() * FACE_SKULL_MOTIFS.length)];
-  return (
-    `Face-Skull Damask — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Composition: classic ogival damask lattice, but the medallion FILL is ${motif}. ` +
-    `Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. ` +
-    `Each face/skull/lattice element is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior crosshatching, NO engraved line shading, NO 3D modeling of the skull/face. Pure cut-out silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Settlement check (inline — same logic as src/fliepaper-bugs.js) ──
-function settlementOK(prompt) {
-  let p = String(prompt || '').toLowerCase();
-  // Strip negatives so anti-prompts don't false-trigger
-  p = p
-    .replace(/\b(?:no|not|without|excluding|never|avoid)\b[^.,;]*(?:banana|bird|butterfl|grape|leaf|leaves|frond|palm|foliage|tropical|fluoresc|neon|rainbow)[^.,;]*[.,;]?/g, ' ')
-    .replace(/\bwithout\b[^.,;]*[.,;]/g, ' ')
-    .replace(/\bexcluding\b[^.,;]*[.,;]/g, ' ');
-  // Part B specific elements (affirmative only)
-  const partB = [
-    /\bbanana(?:s|\s+pods?)?\b/,
-    /\bplantains?\b/,
-    /\bgrapes?\b/,
-    /\bbirds?\b/,
-    /\bbutterfl(?:y|ies)\b/,
-  ];
-  for (const rx of partB) if (rx.test(p)) return false;
-  // Foliage = needs review (we still allow, the figure is single-ink)
-  return true;
-}
-
-// ── Track mix — Steve flagged "needs alot more variety in patterns, way
-// too much damasks" 2026-05-20. Six tracks, equal weight:
-//   cactus            — desert botanical (Sonoran luxury)
-//   chinoiserie       — British-archival Mandarin/Ming-style
-//   toile-de-jouy     — classic French pastoral one-color
-//   stripe-block      — vertical stripe / pinstripe / awning stripe
-//   quatrefoil-ogee   — geometric tessellation (non-damask)
-//   diamond-trellis   — narrow ogival lattice (lighter than damask)
-// drunk-animals-designer paused — see feedback_no_modern_bending_designs.
-// 2026-05-20 — top-selling interior-designer styles, ALL at 36" repeat:
-//   chinoiserie       — British-archival / hand-painted-silk perennial best-seller
-//   stripe            — Colefax & Fowler ticking stripe (designer classic)
-//   cactus            — Steve's house collection (Sonoran luxury)
-//   drunk-zoo-36      — symmetric paired animals (giraffes lead, sloths/lemurs/
-//                       panda/capybara/elephant added), 19c-engraving register
-// 2026-05-20 Steve directives:
-//  - Add designer-scenic (emulate luxury archival elegance, NOT copy)
-//  - Add throttled-plaster-roses ("throwled plaster look with roses")
-//  - luxury-archival is the high-end LLM crisp reference (NEVER heritage-paper/American-paper/German-paper)
-const DESIGNER_SCENES = [
-  'flowering magnolia branches reaching up the panel with peony blossoms at branch nodes',
-  'cascading wisteria vines hanging in symmetric clusters with stylized vine leaves',
-  'cherry-blossom branches arching across the panel with falling petal motifs',
-  'climbing rose vines on bamboo-trellis armature with full open rose heads at intersections',
-  'flowering plum branches in winter-Asian-painting register with sparse blossoms',
-];
-function composeDesignerScenicPrompt(cw) {
-  const scene = DESIGNER_SCENES[Math.floor(Math.random() * DESIGNER_SCENES.length)];
-  return (
-    `Hand-painted scenic chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) silk register. Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Scene: ${scene}, arranged as a seamless symmetric tile — each branch repeats with bilateral mirror symmetry. ` +
-    `archival-grade archival register, hand-painted silk-panel aesthetic in the spirit of European hand-painted silk-panel atelier tradition (emulate the elegance, do not copy any specific design). ` +
-    `Strictly no birds, no insects, no human figures. Branches and flowers only. ` + SHARED_ARTDIR
-  );
-}
-
-const PLASTER_ROSE_STYLES = [
-  'full-open garden roses in symmetric pairs with their stems crossing at the panel center',
-  'climbing-rose vine in vertical repeats with rose heads at every node and serrated leaves',
-  'damask-style rose medallions — single large rose head encircled by a wreath of smaller buds',
-  'rose-and-thorn-branch tessellation, each rose head identical, edge-to-edge tight grid',
-];
-function composeThrottledPlasterRosesPrompt(cw) {
-  const style = PLASTER_ROSE_STYLES[Math.floor(Math.random() * PLASTER_ROSE_STYLES.length)];
-  return (
-    `Trowelled-plaster-wall wallpaper with roses — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. ` +
-    `Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. ` +
-    `Rose style: ${style}. ` +
-    `Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / luxury-archival high-end archival baseline. ` + SHARED_ARTDIR
-  );
-}
-
-// ── MUYBRIDGE — sequential motion-study plates as wallpaper ──────────
-// Eadweard Muybridge "Animal Locomotion" (1887, public domain) — ~780 plates,
-// each one a tiled grid of frozen gait poses of a single subject. The plate
-// FORMAT is already a perfect wallpaper repeat: 12-24 small images tiled in
-// a grid showing the full motion cycle. Steve directive 2026-05-20.
-const MUYBRIDGE_SUBJECTS = [
-  'horse galloping — sequence of 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full gait cycle, tiled left-to-right in three rows of four',
-  'horse trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule tiled in a 4x4 grid',
-  'racehorse with jockey — 12 frozen pose sequence showing the full stride',
-  'leaping greyhound — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule, dog mid-leap, tiled side-by-side',
-  'galloping dog — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule in a 4x4 grid',
-  'mastiff trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in 3 rows',
-  'cat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule showing the saunter, tiled 4x4',
-  'cat leaping from rock to rock — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'lion pacing — 12 frozen pose sequence tiled in a strip',
-  'tiger walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'elephant walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the heavy gait, tiled 3x4',
-  'ostrich running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'kangaroo hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound, tiled 3x4',
-  'pig trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 4x3 grid',
-  'goat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'donkey trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'camel walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the long stride, tiled 3x4',
-  'rabbit hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound',
-  'deer leaping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a strip',
-  'bison charging — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'rooster strutting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'cockatoo flapping its wings — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'pigeon taking flight — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full wingbeat',
-  'human walking — Muybridge male figure in a loincloth, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human descending stairs — woman in a Victorian dress, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled left-to-right',
-  'human running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human jumping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full leap',
-  'human throwing a discus — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled 3x4',
-  'human ascending stairs — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule left-to-right',
-  'human carrying a heavy bucket — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the strained walk',
-  'human dancing a waltz — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human waving a flag — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human boxing — two figures in 12 frozen pose sequence, tiled 3x4',
-  'human wrestling — two figures locked together in 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'baby crawling — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a 3x4 grid',
-  'human hammering a nail — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full swing',
-];
-function composeMuybridgePlatePrompt(cw) {
-  const subj = MUYBRIDGE_SUBJECTS[Math.floor(Math.random() * MUYBRIDGE_SUBJECTS.length)];
-  return (
-    `Eadweard Muybridge motion-study plate as wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Subject: ${subj}. ` +
-    `Each pose is a flat stencil silhouette in single ink on the solid ground — no chiaroscuro, no shading, no anatomical detail inside. ` +
-    `Render as a SEAMLESS REPEAT — the full plate is one tile, edge-to-edge. ` +
-    `Reference: Muybridge "Animal Locomotion" 1887, plate-format, single-color blueprint-style serigraph treatment. NOT photographic, NOT engraved — flat stencil only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── LIPS — Steve directive 2026-05-20. 4-color max, sparse iconic lips on
-// generous ground. Inspired by Warhol Marilyn, Lichtenstein, Patrick Nagel,
-// matchbook covers.
-const LIPS_SUBJECTS = [
-  'one large parted red-velvet lips silhouette centered on the tile, no face, no teeth — just the lips. 80% empty cream ground around it',
-  'a single perfect lipstick-kiss imprint (blot mark) on the upper-third of the tile in scarlet, otherwise empty cream ground',
-  'two lips facing each other almost touching (mid-kiss silhouette), one in deep red and one in cream-outline, lots of empty ground around them',
-  'pursed pouting lips in profile silhouette, scarlet red, no other elements, vast clean ground',
-  'lips biting a single stem of a tiny rose — minimalist Patrick-Nagel silhouette, two-tone (red lips, black rose silhouette, cream ground)',
-  'classic Andy Warhol style pop-art lips — one giant pair of half-open red lips with a thin black outline, 4 colors max (red, black, cream, optional cream highlight on lower lip)',
-];
-
-function composeLipsPrompt(subject, cw) {
-  // For lips: ground=cream/ivory, figure=scarlet red. Override colorway.
-  return (
-    `Lips wallpaper — minimalist pop-art register. ` +
-    `Ground: solid cream-ivory (#F2EADB). Figure: deep scarlet red (#B0282A). ` +
-    `Subject: ${subject}. ` +
-    `Up to 4 colors maximum (cream + scarlet + optional black outline + optional cream highlight on lower lip). NO bleed. NO halftone. NO gradient. ` +
-    `Reference: Andy Warhol pop-art lips, Patrick Nagel, vintage matchbook cover, single-pass screen-print. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── MONTEREY WEST-COAST MURAL — Steve directive 2026-05-20.
-// 6 scene subjects × 4 seasonal colorways = 24 base designs. Each is a single
-// 36"-wide × 132"-tall (11ft) panel intended to be the FIRST in a multi-panel
-// mural set. Admin can later "+next panel" to extend the scene rightward
-// across the wall (via /api/design/:id/add-next-panel).
-const MONTEREY_SUBJECTS = [
-  'Lone Cypress on Pebble Beach 17-Mile Drive — a single windswept Monterey cypress silhouetted on a granite headland with the Pacific behind, hand-painted scenic register, hand-painted-silk aesthetic',
-  'Cannery Row at low tide — silhouettes of the historic canneries on pilings over kelp-strewn rocks, fog ribbons in the upper third',
-  'Point Pinos Lighthouse at dusk — the oldest continuously-operating lighthouse on the West Coast with cypress hedge and bluff in foreground',
-  'Carmel Mission Basilica — adobe bell tower silhouette with rose-garden hedges and ancient olive trees',
-  'Big Sur Bixby Creek Bridge silhouette — arched span over the canyon mouth with the Pacific opening to the west, redwoods on the inland slope',
-  'Monterey Bay kelp-forest above-and-below cross-section — sea-otters on the surface, kelp fronds rising from the seabed, single-layer silhouette only',
-];
-
-const SEASONAL_COLORWAYS = [
-  { season: 'spring', name: 'Spring Bloom',     ground: '#A8B79A', figure: '#F2EADB', tone: 'sage-mint' },
-  { season: 'summer', name: 'Summer Surf',      ground: '#3E6B82', figure: '#F2EADB', tone: 'bay-blue' },
-  { season: 'fall',   name: 'Autumn Saddle',    ground: '#7A4A28', figure: '#F2EADB', tone: 'cypress-burnt-umber' },
-  { season: 'winter', name: 'Winter Fog',       ground: '#9CA09B', figure: '#3A2A1C', tone: 'pacific-mist' },
-];
-
-function composeMontereyMuralPrompt(subject, cw) {
-  return (
-    `Monterey, California west-coast mural panel — ${subject}. ` +
-    `${cw.season.charAt(0).toUpperCase() + cw.season.slice(1)} colorway: ${cw.name}. Ground: solid ${cw.tone} (${cw.ground}). Figure: ${cw.figure} single-ink. ` +
-    `Composition: panoramic vertical panel, motif anchored center-bottom, sky/atmosphere extending up the panel (still in solid ground tone, NOT a sky gradient), generous breathing room. ` +
-    `This is a FULL-ROOM MURAL — width = full wall, height = full ceiling. Compose for a 12'-wide × 9'-tall wall. The customer will choose 24"/36"/54" print-panel widths at order time — generate at room scale, not panel scale. ` +
-    `4-COLOR PALETTE WITH HUE SYNC — all four colors share the same hue family (no warm/cool mixing). Ground + figure as the two main tones; one HIGHLIGHT color for accents (lips, eyes, fingernails, fruit, small jewels); one SHADOW color (under figures, in eye sockets, beneath leaves). Highlight + shadow stay in the same hue family as ground+figure. ` +
-    `Reference: hand-painted-silk scenic panel, hand-painted silk register, NOT Haeckel, NOT Audubon. Single-layer stencil silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// Tall panel kind — 36" wide × 132" tall (11ft). Different from the seamless_tile
-// kind which is square. Panel kind is meant to hang as-is, not tile-repeat.
-// 2026-05-20 Steve directive: murals are FULL ROOM dimensions, not panels.
-// Customer chooses 24"/36"/54" panel width at print time — generation is room-
-// scale. Default room: 144" wide (12 ft) × 108" tall (9 ft ceiling) @ 150 DPI.
-// SDXL renders at 1536×1152 (4:3 max) — at-print upscale to 21600×16200
-// happens at order time via Topaz/Real-ESRGAN.
-const PANEL_W = 144;        // inches (metadata)
-const PANEL_H = 108;        // inches (metadata)
-const PANEL_RENDER_W = 1536; // pixels (SDXL output)
-const PANEL_RENDER_H = 1152; // pixels (SDXL output)
-
-// 2026-05-20 Steve directive: "up to 4 colors using for highlights like
-// lips.. eyes.. fingernails.. accents in another color and shadows. always
-// make sure hue is in sync." Murals get 4 colors with hue-sync constraint.
-// Tile tracks (cactus/chinoiserie/etc.) stay 2-color.
-const HUE_SYNC_QUARTETS = [
-  // Each quartet is [ground, figure, highlight, shadow] within the same hue
-  // family. The hue-sync constraint = all 4 colors share the same dominant
-  // hue ± neutrals.
-  // WARM EARTH (cream/saddle/rose/cocoa)
-  { name: 'Saddle Rose Warmth', ground: '#F2EADB', figure: '#5C2329', highlight: '#B0282A', shadow: '#2A1010' },
-  // FOREST GREEN (Steve's 10492-loved family)
-  { name: 'Forest Loden Quartet', ground: '#3C4844', figure: '#E8E2D1', highlight: '#A8B79A', shadow: '#1A2520' },
-  // INK + GOLD (Steve's high-end luxury-archival energy)
-  { name: 'Ink Library', ground: '#1F2740', figure: '#E8DDB8', highlight: '#C9A14B', shadow: '#0B0F1C' },
-  // BORDEAUX
-  { name: 'Bordeaux Velvet', ground: '#5C2329', figure: '#F2EADB', highlight: '#C84A3A', shadow: '#2A0E10' },
-  // OXBLOOD
-  { name: 'Oxblood', ground: '#722F2F', figure: '#F2EADB', highlight: '#A85040', shadow: '#3A1010' },
-  // STONE PEWTER
-  { name: 'Stone Pewter', ground: '#8A857B', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#3A3530' },
-  // MOSS SLATE
-  { name: 'Moss Slate', ground: '#566554', figure: '#F2EADB', highlight: '#A8B79A', shadow: '#1A2520' },
-  // CHARCOAL + CREAM
-  { name: 'Charcoal Cream', ground: '#3D3833', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#0F0D0B' },
-];
-function pickHueSyncQuartet() {
-  return HUE_SYNC_QUARTETS[Math.floor(Math.random() * HUE_SYNC_QUARTETS.length)];
-}
-
-// ── DESIGNER-ZOO-CALM — Steve directive 2026-05-20 ("love this theme")
-// Inspired by accepted design 10492. Forest-green ground + bone-ivory animal
-// silhouettes, dignified standing/sitting poses (not staggering), sparse
-// fern/grass botanical fillers between animals, Cole-Son/Sister-Parish energy.
-const ZOO_CALM_ANIMALS = [
-  'pair of red pandas in symmetric grooming pose',
-  'bear seated upright in a meditative pose, head slightly tilted',
-  'a tall standing fox in profile, ears alert',
-  'pair of foxes mirrored facing each other, tails curving outward',
-  'family of three lemurs perched in a vertical stack',
-  'pair of standing wolves facing forward, dignified',
-  'mountain lion seated in regal posture, tail curled',
-  'pair of badgers facing each other in symmetric pose',
-  'standing pangolin in profile, scales catching highlight as cut-outs',
-  'pair of capybaras seated side-by-side',
-  'tall standing crane with neck curved (no flying — standing pose only)',
-  'pair of forest deer with antlers, mirrored facing inward',
-  'standing fennec fox with oversized ears, alert pose',
-  'pair of stoats standing upright, paws raised',
-  'standing armadillo in profile, shell silhouette clean',
-  'lounging snow leopard with paws extended',
-];
-const ZOO_CALM_FILLERS = [
-  'sparse fern fronds rising between the animals',
-  'tall slender grass clusters in negative space between motifs',
-  'small wildflower clusters between motifs',
-  'pinecone-and-needle sprigs',
-  'wild-rose stems with single open blossoms',
-  'tall cattails between figures',
-  'sparse oak-leaf clusters in negative space',
-  'olive branches between motifs',
-];
-const ZOO_CALM_PALETTE = [
-  { name: 'Forest Loden', tone: 'deep forest green', hex: '#3C4844' },
-  { name: 'Hunter Olive', tone: 'hunter olive', hex: '#48543A' },
-  { name: 'Bottle Green', tone: 'bottle-green', hex: '#2E4538' },
-  { name: 'Pine Charcoal', tone: 'pine charcoal', hex: '#3A4A45' },
-  { name: 'Moss Slate', tone: 'moss slate', hex: '#566554' },
-  { name: 'Ink Navy', tone: 'ink navy', hex: '#1F2B3A' },
-];
-function composeDesignerZooCalmPrompt() {
-  const animal = ZOO_CALM_ANIMALS[Math.floor(Math.random() * ZOO_CALM_ANIMALS.length)];
-  const filler = ZOO_CALM_FILLERS[Math.floor(Math.random() * ZOO_CALM_FILLERS.length)];
-  const cw = ZOO_CALM_PALETTE[Math.floor(Math.random() * ZOO_CALM_PALETTE.length)];
-  return (
-    `Designer Wallcoverings zoo wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${animal}. Renders at wall-scale 36-inch seamless tile. Animals are LARGE, anchored on the central vertical axis of symmetry, dignified standing or seated pose (NOT staggering, NOT falling, NOT mid-motion). ` +
-    `Between the animals: ${filler}, kept sparse — generous breathing room of clean ground tone visible throughout. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior shading, NO anatomical detail, NO fur texture. Spots/stripes/markings appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    `Reference: British-archival archival, American-classical, Designer Wallcoverings house style. Refined, restrained, designer-grade. ` +
-    SHARED_ARTDIR
-  );
-}
-
-const TRACKS = [
-  // Weighted — designer-zoo-calm appears MORE often per Steve "do more" of 10492
-  'designer-zoo-calm','designer-zoo-calm','designer-zoo-calm','designer-zoo-calm',
-  'chinoiserie','stripe','cactus','drunk-zoo-36','face-skull-damask',
-  'designer-scenic','throttled-plaster-roses','muybridge-plate','monterey-mural',
-];
-
-// Variable repeat size per Steve directive 2026-05-20: vary 24"/36"/54" so
-// designs render at different motif scales (small/medium/large wall coverage).
-const REPEAT_SIZES = [24, 36, 36, 36, 54];
-function pickRepeatSize() {
-  return REPEAT_SIZES[Math.floor(Math.random() * REPEAT_SIZES.length)];
-}
-const REPEAT_INCHES = 36;   // wall-scale repeat — Steve directive 2026-05-20
-function nextTrack() {
-  return TRACKS[Math.floor(Math.random() * TRACKS.length)];
-}
-
-// ── Stop conditions ──────────────────────────────────────────────────
-function shouldStop(state) {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('HALT — flag file present at ' + HALT_FLAG);
-    return true;
-  }
-  if (state.spentUsd >= BUDGET_CAP_USD) {
-    logLine(`HALT — budget cap reached ($${state.spentUsd.toFixed(2)} >= $${BUDGET_CAP_USD})`);
-    return true;
-  }
-  if (Date.now() >= STOP_DEADLINE.getTime()) {
-    logLine(`HALT — stop deadline reached (${new Date().toLocaleString()} past ${STOP_DEADLINE.toLocaleString()})`);
-    return true;
-  }
-  return false;
-}
-
-// ── Generate one design ──────────────────────────────────────────────
-function generateOne(track, cw) {
-  let prompt;
-  switch (track) {
-    case 'cactus':          prompt = composeCactusPrompt(cw); break;
-    case 'chinoiserie':     prompt = composeChinoiseriePrompt(cw); break;
-    case 'toile':           prompt = composeToilePrompt(cw); break;
-    case 'stripe':          prompt = composeStripePrompt(cw); break;
-    case 'quatrefoil':      prompt = composeQuatrefoilPrompt(cw); break;
-    case 'diamond-trellis': prompt = composeDiamondTrellisPrompt(cw); break;
-    case 'drunk-zoo-36':    prompt = composeDrunkZoo36Prompt(cw); break;
-    case 'face-skull-damask': prompt = composeFaceSkullDamaskPrompt(cw); break;
-    case 'designer-scenic':  prompt = composeDesignerScenicPrompt(cw); break;
-    case 'throttled-plaster-roses': prompt = composeThrottledPlasterRosesPrompt(cw); break;
-    case 'muybridge-plate':   prompt = composeMuybridgePlatePrompt(cw); break;
-    case 'designer-zoo-calm': prompt = composeDesignerZooCalmPrompt(); break;
-    case 'monterey-mural':    {
-      const subj = MONTEREY_SUBJECTS[Math.floor(Math.random() * MONTEREY_SUBJECTS.length)];
-      const season = SEASONAL_COLORWAYS[Math.floor(Math.random() * SEASONAL_COLORWAYS.length)];
-      // Override the colorway with the seasonal one for this track
-      cw = { name: season.name, hex: season.ground, tone: season.tone, slug: season.season };
-      prompt = composeMontereyMuralPrompt(subj, season);
-      break;
-    }
-    default:                prompt = composeCactusPrompt(cw);
-  }
-  if (!settlementOK(prompt)) {
-    logLine(`SKIP settlement — track=${track} cw=${cw.slug}`);
-    return { ok: false, settlementBlock: true };
-  }
-  // Each track inserts into its own category for sortability + variety.
-  const category = track === 'cactus'          ? 'cactus'
-                 : track === 'chinoiserie'     ? 'chinoiserie'
-                 : track === 'toile'           ? 'toile-de-jouy'
-                 : track === 'stripe'          ? 'stripe'
-                 : track === 'quatrefoil'      ? 'quatrefoil-ogee'
-                 : track === 'diamond-trellis' ? 'diamond-trellis'
-                 : track === 'drunk-zoo-36'    ? 'drunk-zoo-36'
-                 : track === 'face-skull-damask' ? 'face-skull-damask'
-                 : track === 'designer-scenic' ? 'designer-scenic'
-                 : track === 'throttled-plaster-roses' ? 'throttled-plaster-roses'
-                 : track === 'muybridge-plate' ? 'muybridge-plate'
-                 : track === 'monterey-mural' ? 'monterey-mural'
-                 : track === 'designer-zoo-calm' ? 'designer-zoo-calm'
-                 : 'drunk-animals-designer';
-  // Mural panels render at 36"×132" (11ft); everything else at square tiles
-  // with variable size per Steve 2026-05-20 (24/36/54).
-  const isPanel = (track === 'monterey-mural');
-  const w = isPanel ? PANEL_W : pickRepeatSize();
-  const h = isPanel ? PANEL_H : w;
-  const kind = isPanel ? 'mural_panel' : 'seamless_tile';
-  const r = spawnSync('node', [
-    path.join(__dirname, 'generate_designs.js'),
-    '--n', '1',
-    '--kind', kind,
-    '--width', String(w),
-    '--height', String(h),
-    '--category', category,
-    '--prompts', prompt,
-  ], { cwd: ROOT, encoding: 'utf8', env: process.env });
-
-  if (r.status === 0) {
-    // generate_designs.js prints "  ✓ #<id> ·" per-design and "IDs: <id>,..." summary
-    const summary = (r.stdout || '').match(/IDs:\s*(\d+(?:,\s*\d+)*)/);
-    const perLine = (r.stdout || '').match(/✓\s*#(\d+)/);
-    const idStr = summary ? summary[1].split(/,\s*/)[0] : (perLine ? perLine[1] : null);
-    const id = idStr ? parseInt(idStr, 10) : null;
-    // POST-PROCESS: force 2-tone quantize on the output PNG. Eliminates bleed
-    // between layers (Steve directive 2026-05-20 — looks cheap). The script
-    // backs up the original to <file>.original.png before overwriting.
-    if (id) {
-      try {
-        // psqlAt returns tuples-only, no header/footer — clean single-value reads
-        const lp = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=${id};"`, { encoding: 'utf8' }).trim();
-        if (lp && fs.existsSync(lp)) {
-          const q = spawnSync('python3', [path.join(__dirname, 'quantize-2tone.py'), lp], { encoding: 'utf8' });
-          if (q.status === 0) {
-            logLine(`  2tone OK id=${id}: ${(q.stdout || '').trim().split('->')[1] || '?'}`);
-            // COLORWAY-VARIANTS — for every 2-toned design, generate 3 free
-            // recolors. Steve directive 2026-05-20. Each variant becomes a
-            // child row in spoon_all_designs with parent_design_id=id.
-            // Skip for mural panels (they're meant to be a multi-panel set,
-            // colorway variation happens at the source-prompt level there).
-            if (!isPanel) {
-              try {
-                const cwOut = spawnSync('python3', [path.join(__dirname, 'colorway-variants.py'), lp], { encoding: 'utf8' });
-                if (cwOut.status === 0) {
-                  const parsed = JSON.parse((cwOut.stdout || '').trim().split('\n').pop() || '{}');
-                  const variants = parsed.variants || [];
-                  for (const v of variants) {
-                    if (!fs.existsSync(v.path)) continue;
-                    const cwTitle = `${category} · ${v.slug}`;
-                    // Two-step fix for the "PLACEHOLDER image_url" bug — see
-                    // git log + the bulk PG UPDATE on 2026-05-20. The earlier
-                    // single-statement CTE (INSERT + UPDATE in one) failed
-                    // because PG's data-modifying CTEs share a snapshot, so the
-                    // UPDATE didn't see the just-inserted row and the URL stayed
-                    // PLACEHOLDER. Two separate psql calls — INSERT...RETURNING
-                    // captures id, then a follow-up UPDATE writes the real URL.
-                    const insertSql = `
-                      INSERT INTO spoon_all_designs (brand, kind, width_in, height_in, panels, generator, prompt, image_url, local_path, dominant_hex, category, is_published, notes, parent_design_id, created_at)
-                      SELECT brand, kind, width_in, height_in, panels, generator, prompt || E'\\n[colorway-variant of #${id}]', '/designs/img/by-id/PLACEHOLDER', '${v.path.replace(/'/g, "''")}', '${v.ground.replace(/'/g, "''")}', '${cwTitle.replace(/'/g, "''")}', TRUE, E'colorway variant: ${v.slug} (ground=${v.ground}, figure=${v.figure}) of parent #${id}', ${id}, NOW()
-                      FROM spoon_all_designs WHERE id=${id}
-                      RETURNING id;`;
-                    try {
-                      const insertRaw = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' });
-                      // psql -At emits the RETURNING value on line 1, then the
-                      // command tag (e.g. "INSERT 0 1") on line 2. Earlier
-                      // version used `.split(/\s+/).pop()` which grabbed "1"
-                      // from the command tag. Take the FIRST line instead.
-                      const cwId = (insertRaw.trim().split('\n')[0] || '').trim();
-                      if (cwId && /^\d+$/.test(cwId)) {
-                        try {
-                          const updOut = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
-                          logLine(`    cw url-patch id=${cwId} → ${updOut || '(empty)'}`);
-                        } catch (uErr) {
-                          logLine(`    cw url-patch FAIL id=${cwId}: ${(uErr.message || '').slice(0, 200)}`);
-                        }
-                      } else {
-                        logLine(`    cw NO-ID raw=${JSON.stringify(insertRaw).slice(0, 300)}`);
-                      }
-                      logLine(`    cw child id=${cwId} (${v.slug}) of parent ${id}`);
-                    } catch (e2) {
-                      logLine(`    cw INSERT FAIL ${v.slug}: ${e2.message.slice(0, 200)}`);
-                    }
-                  }
-                } else {
-                  logLine(`  cw FAIL id=${id}: ${(cwOut.stderr || cwOut.stdout || '').slice(0, 200)}`);
-                }
-              } catch (e3) {
-                logLine(`  cw EXC id=${id}: ${e3.message.slice(0, 200)}`);
-              }
-            }
-          } else {
-            logLine(`  2tone FAIL id=${id}: ${(q.stderr || q.stdout || '').slice(0, 200)}`);
-          }
-        } else {
-          logLine(`  2tone SKIP id=${id}: local_path not found (${lp})`);
-        }
-      } catch (e) {
-        logLine(`  2tone EXC id=${id}: ${e.message.slice(0, 200)}`);
-      }
-    }
-    // Auto-publish so Steve sees it in /designs
-    if (id) {
-      try {
-        psql(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
-      } catch (e) {
-        logLine(`publish-flip FAIL id=${id}: ${e.message.slice(0, 120)}`);
-      }
-    }
-    return { ok: true, id, category, colorway: cw.slug };
-  } else {
-    return { ok: false, stderr: (r.stderr || r.stdout || '').slice(0, 200) };
-  }
-}
-
-// ── Main loop ────────────────────────────────────────────────────────
-(async () => {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('ABORT — halt flag already exists at startup. Remove it first.');
-    process.exit(1);
-  }
-  const state = { iterations: 0, ok: 0, fail: 0, settlementSkipped: 0, spentUsd: 0, startedAt: nowIso() };
-  logLine(`START night-builder · budget=$${BUDGET_CAP_USD} stop=${STOP_DEADLINE.toLocaleString()} cooldown=${COOLDOWN_MS}ms`);
-  logLine(`Track mix: 30% cactus / 70% drunk-animals-designer · ${MUTED_PALETTE.length} muted colorways`);
-
-  while (!shouldStop(state)) {
-    state.iterations++;
-    const track = nextTrack();
-    const cw = pickColorway();
-    const r = generateOne(track, cw);
-    state.spentUsd += PER_IMAGE_USD;
-    if (r.settlementBlock) {
-      state.settlementSkipped++;
-    } else if (r.ok) {
-      state.ok++;
-      logLine(`OK  #${state.iterations} ${track}/${cw.slug} id=${r.id} (cumul: ok=${state.ok} fail=${state.fail} spent=$${state.spentUsd.toFixed(2)})`);
-    } else {
-      state.fail++;
-      logLine(`FAIL #${state.iterations} ${track}/${cw.slug}: ${r.stderr || 'unknown'}`);
-      // brief backoff on failure (rate-limit or transient)
-      await new Promise(rs => setTimeout(rs, 15000));
-    }
-    await new Promise(rs => setTimeout(rs, COOLDOWN_MS));
-  }
-
-  logLine(`STOP iterations=${state.iterations} ok=${state.ok} fail=${state.fail} settlementSkip=${state.settlementSkipped} spent=$${state.spentUsd.toFixed(2)}`);
-  process.exit(0);
-})();
diff --git a/scripts/night-builder.js.bak-vendor-final b/scripts/night-builder.js.bak-vendor-final
deleted file mode 100755
index 5d6f6ac..0000000
--- a/scripts/night-builder.js.bak-vendor-final
+++ /dev/null
@@ -1,906 +0,0 @@
-#!/usr/bin/env node
-// All-night Replicate generator.
-//
-// Two tracks, interleaved:
-//   A) Muted Gucci-palette CACTUS variants (extends the existing 10 colorways)
-//   B) Drunk-animals → designer-elevated reinterpretation (Schumacher/Cole&Son
-//      register, muted Gucci palette, single-ink, archival gouache)
-//
-// Steve's brief: "continue creating patterns all night with the subdued hues
-// of grey, taupe, cream, ivory, gucci browns, recreate the drunk animals with
-// much more designer like designs."
-//
-// Hard rules:
-//   - Budget cap: $20 (≈2000 Replicate SDXL images at ~$0.01 ea)
-//   - Halt: touch /tmp/halt-night-builder.flag to stop after current image
-//   - Stop time: 07:30 local (Steve wakes ~8 AM)
-//   - Settlement gate inline (single-ink + no Part B elements = OK)
-//   - is_published=TRUE so Steve sees them in /designs immediately
-//   - Category: 'drunk-animals-designer' for the B-track (NOT 'drunk-animals')
-//     so Steve can sort + dedupe vs the kitsch originals
-//   - Cooldown: 5s between calls to be polite to Replicate
-//
-// Cost tracking: each Replicate image hits the cost-tracker via log.js
-// (already wired in generate_designs.js).
-
-const { spawnSync } = require('child_process');
-const fs = require('fs');
-const path = require('path');
-const { execSync } = require('child_process');
-
-const ROOT = path.join(__dirname, '..');
-const HALT_FLAG = '/tmp/halt-night-builder.flag';
-const LOG = '/tmp/night-builder.log';
-const STOP_HOUR = 7;   // local: stop at 07:30 next-morning
-const STOP_MIN  = 30;
-const BUDGET_CAP_USD = 20.0;
-const PER_IMAGE_USD = 0.011;   // Replicate SDXL ~$0.01 (incl. 10% safety margin)
-const COOLDOWN_MS = 5000;
-
-// Compute the actual next-7:30-AM deadline relative to script start.
-function computeStopDeadline() {
-  const now = new Date();
-  const d = new Date(now);
-  d.setHours(STOP_HOUR, STOP_MIN, 0, 0);
-  // If 7:30 today is already in the past, target 7:30 tomorrow.
-  if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
-  return d;
-}
-const STOP_DEADLINE = computeStopDeadline();
-
-const MUTED_PALETTE = [
-  { slug: 'smoke-ash',       name: 'Smoke Ash',       hex: '#6B6660', tone: 'warm-mid-grey' },
-  { slug: 'mushroom-taupe',  name: 'Mushroom Taupe',  hex: '#8A7E70', tone: 'designer-taupe' },
-  { slug: 'linen-cream',     name: 'Linen Cream',     hex: '#EDE4D3', tone: 'soft-cream' },
-  { slug: 'bone-ivory',      name: 'Bone Ivory',      hex: '#F2EADB', tone: 'warm-ivory' },
-  { slug: 'gucci-cocoa',     name: 'Gucci Cocoa',     hex: '#5C4033', tone: 'deep-cocoa' },
-  { slug: 'antique-tan',     name: 'Antique Tan',     hex: '#A78863', tone: 'warm-camel' },
-  { slug: 'stone-pewter',    name: 'Stone Pewter',    hex: '#7A736A', tone: 'cool-pewter' },
-  { slug: 'saddle-mocha',    name: 'Saddle Mocha',    hex: '#4A3528', tone: 'saddle-leather-brown' },
-  { slug: 'champagne-linen', name: 'Champagne Linen', hex: '#D7C9A8', tone: 'champagne-neutral' },
-  { slug: 'charcoal-smoke',  name: 'Charcoal Smoke',  hex: '#3D3833', tone: 'near-black-charcoal' },
-];
-
-const DB = 'dw_unified';
-const PSQL = `psql ${DB} -At -q`;
-function psql(sql) {
-  return execSync(PSQL, { input: sql, encoding: 'utf8' }).trim();
-}
-
-function nowIso() { return new Date().toISOString(); }
-function logLine(s) {
-  const line = `[${nowIso()}] ${s}`;
-  console.log(line);
-  fs.appendFileSync(LOG, line + '\n');
-}
-
-function pickColorway() {
-  return MUTED_PALETTE[Math.floor(Math.random() * MUTED_PALETTE.length)];
-}
-
-// ── Track A: cactus variants in muted palette ────────────────────────
-const CACTUS_PATTERNS = [
-  'tall saguaro forms with raised arms, scattered prickly-pear paddles',
-  'dense agave rosettes with whip-stem ocotillo verticals',
-  'barrel cactus rosettes and cholla branches in tight tessellation',
-  'sprawling prickly-pear paddle clusters as a vertical wallpaper repeat',
-  'ocotillo whip-stem fountain pattern with small barrel-cactus accents',
-];
-
-function composeCactusPrompt(cw) {
-  const pattern = CACTUS_PATTERNS[Math.floor(Math.random() * CACTUS_PATTERNS.length)];
-  return (
-    `Cactus Repeat — ${cw.name} colorway. ` +
-    `Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. ` +
-    `Sonoran desert cacti as FLAT STENCIL SILHOUETTES: ${pattern}, arranged in a tessellated seamless wallpaper repeat. ` +
-    `Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. ` +
-    `All cacti upright, oriented vertically, symmetric placement on the vertical axis. ` +
-    `Edge-to-edge tessellation, generous breathing room of clean ground between motifs. ` +
-    `Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track B: DEBAUCHED ANIMALS (rewritten 2026-05-20 per Steve directive) ──
-// References:
-//   1. Asterix drunk-Roman-legionnaire pose (Uderzo): staggering gait, leg
-//      kicking sideways, helmet askew, X-pupil eyes, wobble-line motion arc.
-//   2. Eadweard Muybridge "Animal Locomotion" sequential-motion plates: each
-//      animal captured at multiple frozen poses showing the full gait cycle,
-//      tiled side-by-side. The original "movement sneak" before cinema existed.
-// Tone: HEDONISTIC, DEBAUCHED, BACCHANALIAN. Cocktail party gone fully off
-// the rails. Animals are mid-stagger, mid-collapse, mid-tumble — never in
-// dignified repose.
-const ANIMAL_DESCRIPTORS = [
-  // STAGGERING / FALLING
-  'staggering bear mid-stumble with one paw airborne, Asterix-drunk-legionnaire pose',
-  'tumbling fox falling backward limbs splayed',
-  'reticulated giraffe slumped against an invisible wall, legs splayed Bambi-on-ice',
-  'red panda hanging upside down from a phantom branch, eyes spiraled',
-  'lemur mid-cartwheel, limbs flung in four directions, Uderzo motion arcs around it',
-  'sloth lying flat on its back, all four limbs in the air, classic dead-drunk pose',
-  'capybara mid-belly-laugh slumped sideways, hiccup bubble above',
-  'orangutan kicking one leg high in a Charleston-stagger, the other arm windmilling',
-  'raccoon in a top hat falling face-first toward the viewer',
-  'elephant calf doing a full pratfall on its back legs, trunk waving wildly',
-  'tree frog mid-leap and missing, arms grasping at empty air',
-  'Bornean orangutan blowing a kiss with one eye crossed, drink mid-spill',
-  'koala wrapped around a bottle larger than itself, swaying',
-  'wallaby boxer mid-haymaker against nothing, swinging at phantoms',
-  'tasmanian devil mid-spin, dust-cloud lines around it',
-  'fennec fox flopped on its side, tongue lolling out',
-  // DANCING / CABARET POSES
-  'flamingo in a high-kick cancan pose, feathers flying',
-  'okapi in a tango dip, partner is an invisible silhouette',
-  'pangolin doing a one-legged drunken pirouette, scales spread out',
-  'capybara in a top hat doing the Charleston',
-  'meerkat trio in a chorus-line kick',
-  'aardvark mid-Lindy-hop swing-out, partner missing',
-  'lemur ballroom-glide with one paw extended, the other holding a cocktail',
-  'red panda in disco pose, one arm up Saturday-Night-Fever style',
-  // PASSED OUT / OBLIVION
-  'walrus snoring with three Zs above it, X-pupils',
-  'turtle on its back legs in the air, hiccup bubble',
-  'manatee floating belly-up with bubbles around its mouth',
-  'wombat sprawled on a velvet ottoman, paws curled',
-  'lazy panther draped over a bar-stool silhouette',
-  'sleepy hedgehog curled inside an empty cocktail glass',
-  // MID-PRATFALL
-  'chimpanzee mid-banana-peel slip (peel falling, not held)',
-  'baboon doing a forward roll into a stack of empty glasses',
-  'mandrill mid-faceplant, legs in the air',
-  'gorilla doing a dignified slow-motion topple, one finger raised',
-  // INTOXICATED VIBE
-  'platypus floating in zero-gravity with a snifter, monocle askew',
-  'armadillo curled mid-tumble, motion-arc behind it',
-  'tapir wobbling on two legs holding a martini glass overhead',
-  'binturong (bearcat) dangling from a velvet rope by its tail, drink in mouth',
-  'ocelot lounging on a chaise with smoking pipe and one eye closed',
-  'civet stretched out on a Persian rug, paws to the ceiling',
-  // BACCHANALIAN HARVEST
-  'wild boar reclined against a barrel, snout sniffing the air',
-  'bison crowned with a wreath of curlicue smoke, head tipped back',
-  'ibex doing a goat-headstand on a stack of bottles',
-  'fallow deer mid-leap with hooves crossed, antlers tangled with ribbons',
-  // CLASSIC CARTOON-DRUNK
-  'cat with five-o\'clock-shadow holding a cocktail like Tom of Tom-and-Jerry',
-  'penguin in a bowtie tipping a top hat with one flipper',
-  'walrus playing a tiny piano with one tusk',
-  'badger in a smoking jacket striking a Sherlock Holmes pose',
-  'opossum hanging by its tail clutching a martini upside-down',
-  // SMOKE-CLOUD / VAPOR POSES
-  'fox blowing a perfect smoke-ring, eye squinted',
-  'rabbit reclining in a hookah-style pose, pipe in paw',
-  'mongoose with three smoke-curls rising from a pipe',
-  'lizard breathing out a single curling vapor-trail',
-  // CRYPTID DEBAUCHERY (real animals only, no fantasy)
-  'aye-aye gesturing wildly with a long finger raised',
-  'star-nosed mole sniffing a cocktail with thirteen tentacles',
-  'pangolin doing a slow Charleston in scaly tuxedo',
-  'kinkajou dangling from a chandelier with a coupe',
-  // MISC SURREAL POSES
-  'sloth doing a slow-motion crowd-surf on raised paws',
-  'porcupine quill-up flexing arms strongman pose',
-  'capybara DJ-ing on a turntable made of orange slices',
-  'badger conducting a phantom orchestra, baton in paw',
-];
-
-const DRINK_DESCRIPTORS = [
-  'half-empty long-stemmed coupe tilted at 45° with champagne sloshing out',
-  'cut-crystal whiskey tumbler frozen mid-toss, amber liquid arcing through the air',
-  'martini glass tipped over, two olives rolling away',
-  'bottle of pilsner held inverted, foam splashing onto the ground',
-  'red wine balloon mid-spill, dark liquid streaming sideways',
-  'frosted vodka decanter being chugged straight from the neck',
-  'tequila shot raised in toast, lime wedge between teeth',
-  'cognac snifter cradled like a baby',
-  'Aperol spritz in one hand, second drink in the other',
-  'magnum of champagne held aloft and shaken, cork popping with a comic burst',
-  'rolled joint trailing a wispy smoke-curl ribbon',
-  'oversize pipe with three smoke-curls forming a clover',
-  'cocktail with three umbrellas crammed into it',
-  'pint glass overflowing with foam pouring over the sides',
-];
-
-// Muybridge-style: sometimes show the animal in a sequence of 3 frozen motion
-// poses (instead of one), tiled side-by-side. Used 30% of the time.
-const MOTION_FORMATS = [
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  // 3 out of 10 → Muybridge sequence:
-  'Muybridge-style sequence of three frozen poses of the SAME animal mid-stagger, tiled side-by-side left-to-right showing the full stumble cycle',
-  'Muybridge-style sequence of three frozen poses of the SAME animal collapsing forward, tiled side-by-side left-to-right',
-  'Muybridge-style sequence of four frozen poses of the SAME animal mid-pratfall, tiled in a 2x2 grid',
-];
-
-const BOTANICAL_SETTINGS = [
-  'set among silhouetted Ghost Orchids',
-  'against a damask-trellis backdrop',
-  'framed by Botticelli-style arabesques',
-  'surrounded by hand-painted Magnolia branches',
-  'within a Schumacher-Audubon herbarium frame',
-  'set in a Cole&Son chinoiserie panel',
-  'flanked by ogival damask medallions',
-  'centered on a wunderkammer specimen plate',
-];
-
-function composeDrunkAnimalPrompt(cw) {
-  const animal    = ANIMAL_DESCRIPTORS[Math.floor(Math.random() * ANIMAL_DESCRIPTORS.length)];
-  const drink     = DRINK_DESCRIPTORS[Math.floor(Math.random() * DRINK_DESCRIPTORS.length)];
-  return (
-    `Cocktail-hour wallpaper — ${animal} cradling a ${drink}. ` +
-    `Colorway: ${cw.name} (${cw.hex}) ground with bone-ivory (#F2EADB) figure ink. ` +
-    `Each animal-and-drink vignette is one FLAT STENCIL SILHOUETTE in bone-ivory — no interior shading, no anatomical detail, no fur texture. The drink glass is part of the same silhouette, also flat ink. ` +
-    `Vignettes arranged in a symmetric tessellated repeat, generous breathing room of clean ground between them. ` +
-    `Strictly no botanical background, no foliage filler, no decorative arabesques behind the figures — the ground is one uniform flat color with nothing on it except the silhouette vignettes. ` +
-    `No bananas, no grapes, no birds, no butterflies. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── SHARED art direction — rewritten 2026-05-20 to ELIMINATE the bleed-through
-// layered-background problem. Anchor refs swapped from natural-history plates
-// (Haeckel/Merian — those DO have atmospheric depth + layered washes) to pure
-// 2-tone serigraph references (Warhol/Lichtenstein/Toulouse-Lautrec/WPA poster
-// /Saul Bass/pochoir stencil — those are GUARANTEED flat 2-tone with NO depth).
-// Rewritten 2026-05-20 — Steve directive: "way too busy for most designs..
-// keep it simple". Old SHARED_ARTDIR encouraged tessellated edge-to-edge
-// filling. New version forces SPARSE motifs + GENEROUS NEGATIVE SPACE.
-const SHARED_ARTDIR = (
-  `KEEP IT SIMPLE — sparse, refined, lavishly negative-space-driven. At LEAST 75-85% of the tile area is solid clean ground tone with NOTHING in it (often 85%+, like Zuber and Maya Romanoff archives). Motifs are FEW (2-4 per tile maximum), well-spaced, never edge-to-edge, never tessellated, never cluttered. The empty ground IS the design — motifs are punctuation, not pattern-fill. ` +
-  `Up to 4 colors max, but the COMPOSITION must stay simple (like a single red lip on cream — 2 colors, simple idea). Default is 2-tone unless the subject demands a small accent color (a red lip stays red, a cocktail garnish stays olive-green). Never more than 4 distinct ink tones. ` +
-  `ABSOLUTELY NO bleed between layers — bleed looks cheap and is FORBIDDEN. Single-layer screen-print only. ` +
-  `NO ghost layer, NO faded background, NO atmospheric depth, NO secondary motifs behind the main figures. The ground is one completely uniform flat solid color from edge to edge. ` +
-  `Reference aesthetic: high-end designer wallcovering register, anchored to these brand archives — Zuber & Cie (hand-blocked scenic), Designers Guild (refined modern romantic), Thibaut (archival classic), Arte International (natural-fiber luxe), Romo (painterly British), Maya Romanoff (handcrafted luxe surface). Subtle. Sparse. Refined. Negative-space-driven. Single-pass silkscreen with two ink screens. ` +
-  `Hard clean razor-sharp edges. No half-tones, no gradient, no watercolor, no crosshatch shading. Every figure region is one flat color. ` +
-  `Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar. ` +
-  `Square aspect ratio, seamless tile repeat (edges meet cleanly), flat-paper rendering, no watermark, no signature, no text, no logos.`
-);
-
-// Universal negative prompt — passed to Replicate as --negative-prompt. Catches
-// the model's instinct to add depth/layers even when text anti-prompts fail.
-const SHARED_NEGATIVE = (
-  `gradient, ombre, fade, faded, ghosted, ghost layer, atmospheric perspective, depth, layered background, ` +
-  `secondary motifs behind, lighter background copies, halftone, half-tone, chromatic gradient, watercolor wash, ` +
-  `airbrush, soft edges, blurry edges, anti-aliased blur, crosshatching, engraving lines, stippling, ` +
-  `three colors, four colors, multi-color, textured ground, mottled background, shadow, drop shadow, ` +
-  `Haeckel, Merian, Audubon, natural-history plate, botanical plate, engraving, cartoon, Pixar, 3D render`
-);
-
-const CHINOISERIE_MOTIFS = [
-  'pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches',
-  'kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above',
-  'lotus blossom roundels in vertical stacks separated by bamboo verticals',
-  'mountain-and-temple silhouette repeats with stylized scholar-cloud forms',
-];
-function composeChinoiseriePrompt(cw) {
-  const motif = CHINOISERIE_MOTIFS[Math.floor(Math.random() * CHINOISERIE_MOTIFS.length)];
-  return (
-    `Chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Motif: ${motif}, arranged in a tessellated seamless repeat. ` +
-    `Cole & Son Mandarin / Ming-dynasty woodblock register. ` + SHARED_ARTDIR
-  );
-}
-
-const TOILE_VIGNETTES = [
-  'a pair of mirror-image country-cottage scenes flanking a central fountain',
-  'symmetric pastoral courtship scene under arching elm-tree silhouettes',
-  'paired equestrian-rider vignettes facing each other across a ribbon-tied bouquet',
-  'symmetric harvest-basket scene with sheaves of wheat and hand-tools',
-];
-function composeToilePrompt(cw) {
-  const vig = TOILE_VIGNETTES[Math.floor(Math.random() * TOILE_VIGNETTES.length)];
-  return (
-    `Toile de Jouy wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-engraving. ` +
-    `Vignette: ${vig}, repeated as a seamless symmetric tile, every figure rendered in fine-line copperplate-engraving style with cross-hatched shading replaced by solid-flat figure tone. ` +
-    `Pierre-Frey / Manuel Canovas archival toile register. ` + SHARED_ARTDIR
-  );
-}
-
-const STRIPE_STYLES = [
-  'wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges',
-  'awning stripe — uniform 6cm vertical bands alternating ground and figure tones',
-  'beaded vertical lines with small ornamental dot-clusters between stripes',
-  'fine triple-pinstripe groups separated by wider blank channels',
-];
-function composeStripePrompt(cw) {
-  const style = STRIPE_STYLES[Math.floor(Math.random() * STRIPE_STYLES.length)];
-  return (
-    `Vertical stripe wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure stripes: bone-ivory (#F2EADB) single-ink. ` +
-    `Pattern: ${style}. ` +
-    `Robert Kime / Colefax & Fowler archival stripe register. ` + SHARED_ARTDIR
-  );
-}
-
-const QUATREFOIL_FIELDS = [
-  'classical quatrefoil tessellation, each quatrefoil oriented identically, edge-to-edge tight grid',
-  'gothic-window quatrefoil-plus-circle interlock, symmetric 4-fold',
-  'moorish-tile quatrefoil with negative-space cross channels',
-  'ogee-quatrefoil hybrid — pointed-arch ogee crossed with classical quatrefoil',
-];
-function composeQuatrefoilPrompt(cw) {
-  const field = QUATREFOIL_FIELDS[Math.floor(Math.random() * QUATREFOIL_FIELDS.length)];
-  return (
-    `Quatrefoil / ogee geometric wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Field: ${field}. ` +
-    `Owen Jones Grammar of Ornament register, strict mathematical tessellation. ` + SHARED_ARTDIR
-  );
-}
-
-const DIAMOND_TRELLIS_FIELDS = [
-  'narrow ogival diamond lattice with empty centers (just the lattice lines)',
-  'diamond trellis with a single tiny center-medallion in each diamond',
-  'crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections',
-  'fine-line diamond lattice with classical tassels at each lattice node',
-];
-function composeDiamondTrellisPrompt(cw) {
-  const field = DIAMOND_TRELLIS_FIELDS[Math.floor(Math.random() * DIAMOND_TRELLIS_FIELDS.length)];
-  return (
-    `Diamond trellis wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-trellis. ` +
-    `Field: ${field}. NOT a damask, NOT a heavy medallion field — light open trellis only. ` +
-    `Soane Britain / De Gournay archival trellis register. ` + SHARED_ARTDIR
-  );
-}
-
-// ── Track H: DRUNK ZOO 36" (Steve-approved 2026-05-20) ──────────────
-// Replaces the giraffe-only track with a broader symmetric-zoo track.
-// 36" repeat — wall-scale large pattern. Each design = one symmetric
-// paired-animal vignette in 19c-engraving aesthetic.
-const ZOO_VIGNETTES = [
-  'two reticulated giraffes facing each other in symmetric profile, necks crossing in a heart-shape, both sipping champagne coupes',
-  'pair of mirror-image giraffes flanking a central acacia tree, both holding cocktail glasses',
-  'mother-and-calf giraffe pair, mother arching over the calf, both with bottles',
-  'pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved',
-  'two sloths hanging in mirror-image from a central acacia branch, each with a cocktail glass',
-  'pair of red pandas in symmetric grooming pose, central bottle between them',
-  'two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern',
-  'pair of capybaras in symmetric resting pose, central mint julep glass between them',
-  'two African elephant calves in symmetric trunk-raised pose, central wine glass',
-  'pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them',
-  'two tipsy frogs in symmetric leaping pose mirrored, central decanter',
-];
-function composeDrunkZoo36Prompt(cw) {
-  const vig = ZOO_VIGNETTES[Math.floor(Math.random() * ZOO_VIGNETTES.length)];
-  return (
-    `Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading, no anatomical line-detail, no spots, no stripes, no fur texture. Spots/stripes appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track I: FACE-SKULL DAMASK (Steve directive 2026-05-20) ──────────
-// Damask is purged unless it has faces or skulls. This track generates new
-// damask designs where the medallion FILL is a hand-engraved face or skull
-// rendered in single-ink screen-print style.
-const FACE_SKULL_MOTIFS = [
-  'memento-mori skull at the center of each ogival damask medallion, crossed bones forming the lattice spines',
-  'classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders',
-  'death\'s-head moth skull pattern at each damask cell, wings forming the connecting flourishes',
-  'Venetian carnival mask face filling each damask medallion, ribbon-ties forming the lattice',
-  'Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds',
-  'Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion',
-  'anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink',
-  'phantom-of-the-opera half-mask face at each medallion, opera-cape ribbons forming the lattice',
-];
-function composeFaceSkullDamaskPrompt(cw) {
-  const motif = FACE_SKULL_MOTIFS[Math.floor(Math.random() * FACE_SKULL_MOTIFS.length)];
-  return (
-    `Face-Skull Damask — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Composition: classic ogival damask lattice, but the medallion FILL is ${motif}. ` +
-    `Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. ` +
-    `Each face/skull/lattice element is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior crosshatching, NO engraved line shading, NO 3D modeling of the skull/face. Pure cut-out silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Settlement check (inline — same logic as src/fliepaper-bugs.js) ──
-function settlementOK(prompt) {
-  let p = String(prompt || '').toLowerCase();
-  // Strip negatives so anti-prompts don't false-trigger
-  p = p
-    .replace(/\b(?:no|not|without|excluding|never|avoid)\b[^.,;]*(?:banana|bird|butterfl|grape|leaf|leaves|frond|palm|foliage|tropical|fluoresc|neon|rainbow)[^.,;]*[.,;]?/g, ' ')
-    .replace(/\bwithout\b[^.,;]*[.,;]/g, ' ')
-    .replace(/\bexcluding\b[^.,;]*[.,;]/g, ' ');
-  // Part B specific elements (affirmative only)
-  const partB = [
-    /\bbanana(?:s|\s+pods?)?\b/,
-    /\bplantains?\b/,
-    /\bgrapes?\b/,
-    /\bbirds?\b/,
-    /\bbutterfl(?:y|ies)\b/,
-  ];
-  for (const rx of partB) if (rx.test(p)) return false;
-  // Foliage = needs review (we still allow, the figure is single-ink)
-  return true;
-}
-
-// ── Track mix — Steve flagged "needs alot more variety in patterns, way
-// too much damasks" 2026-05-20. Six tracks, equal weight:
-//   cactus            — desert botanical (Sonoran luxury)
-//   chinoiserie       — Cole&Son Mandarin/Ming-style
-//   toile-de-jouy     — classic French pastoral one-color
-//   stripe-block      — vertical stripe / pinstripe / awning stripe
-//   quatrefoil-ogee   — geometric tessellation (non-damask)
-//   diamond-trellis   — narrow ogival lattice (lighter than damask)
-// drunk-animals-designer paused — see feedback_no_modern_bending_designs.
-// 2026-05-20 — top-selling interior-designer styles, ALL at 36" repeat:
-//   chinoiserie       — Cole & Son / de Gournay perennial best-seller
-//   stripe            — Colefax & Fowler ticking stripe (designer classic)
-//   cactus            — Steve's house collection (Sonoran luxury)
-//   drunk-zoo-36      — symmetric paired animals (giraffes lead, sloths/lemurs/
-//                       panda/capybara/elephant added), 19c-engraving register
-// 2026-05-20 Steve directives:
-//  - Add designer-scenic (emulate luxury archival elegance, NOT copy)
-//  - Add throttled-plaster-roses ("throwled plaster look with roses")
-//  - Thibaut is the high-end LLM crisp reference (NEVER York/Brewster/Marburg)
-const DESIGNER_SCENES = [
-  'flowering magnolia branches reaching up the panel with peony blossoms at branch nodes',
-  'cascading wisteria vines hanging in symmetric clusters with stylized vine leaves',
-  'cherry-blossom branches arching across the panel with falling petal motifs',
-  'climbing rose vines on bamboo-trellis armature with full open rose heads at intersections',
-  'flowering plum branches in winter-Asian-painting register with sparse blossoms',
-];
-function composeDesignerScenicPrompt(cw) {
-  const scene = DESIGNER_SCENES[Math.floor(Math.random() * DESIGNER_SCENES.length)];
-  return (
-    `Hand-painted scenic chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) silk register. Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Scene: ${scene}, arranged as a seamless symmetric tile — each branch repeats with bilateral mirror symmetry. ` +
-    `Thibaut-grade archival register, hand-painted silk-panel aesthetic in the spirit of European hand-painted silk-panel atelier tradition (emulate the elegance, do not copy any specific design). ` +
-    `Strictly no birds, no insects, no human figures. Branches and flowers only. ` + SHARED_ARTDIR
-  );
-}
-
-const PLASTER_ROSE_STYLES = [
-  'full-open garden roses in symmetric pairs with their stems crossing at the panel center',
-  'climbing-rose vine in vertical repeats with rose heads at every node and serrated leaves',
-  'damask-style rose medallions — single large rose head encircled by a wreath of smaller buds',
-  'rose-and-thorn-branch tessellation, each rose head identical, edge-to-edge tight grid',
-];
-function composeThrottledPlasterRosesPrompt(cw) {
-  const style = PLASTER_ROSE_STYLES[Math.floor(Math.random() * PLASTER_ROSE_STYLES.length)];
-  return (
-    `Trowelled-plaster-wall wallpaper with roses — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. ` +
-    `Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. ` +
-    `Rose style: ${style}. ` +
-    `Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. ` + SHARED_ARTDIR
-  );
-}
-
-// ── MUYBRIDGE — sequential motion-study plates as wallpaper ──────────
-// Eadweard Muybridge "Animal Locomotion" (1887, public domain) — ~780 plates,
-// each one a tiled grid of frozen gait poses of a single subject. The plate
-// FORMAT is already a perfect wallpaper repeat: 12-24 small images tiled in
-// a grid showing the full motion cycle. Steve directive 2026-05-20.
-const MUYBRIDGE_SUBJECTS = [
-  'horse galloping — sequence of 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full gait cycle, tiled left-to-right in three rows of four',
-  'horse trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule tiled in a 4x4 grid',
-  'racehorse with jockey — 12 frozen pose sequence showing the full stride',
-  'leaping greyhound — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule, dog mid-leap, tiled side-by-side',
-  'galloping dog — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule in a 4x4 grid',
-  'mastiff trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in 3 rows',
-  'cat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule showing the saunter, tiled 4x4',
-  'cat leaping from rock to rock — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'lion pacing — 12 frozen pose sequence tiled in a strip',
-  'tiger walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'elephant walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the heavy gait, tiled 3x4',
-  'ostrich running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'kangaroo hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound, tiled 3x4',
-  'pig trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 4x3 grid',
-  'goat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'donkey trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'camel walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the long stride, tiled 3x4',
-  'rabbit hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound',
-  'deer leaping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a strip',
-  'bison charging — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'rooster strutting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'cockatoo flapping its wings — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'pigeon taking flight — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full wingbeat',
-  'human walking — Muybridge male figure in a loincloth, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human descending stairs — woman in a Victorian dress, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled left-to-right',
-  'human running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human jumping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full leap',
-  'human throwing a discus — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled 3x4',
-  'human ascending stairs — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule left-to-right',
-  'human carrying a heavy bucket — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the strained walk',
-  'human dancing a waltz — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human waving a flag — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human boxing — two figures in 12 frozen pose sequence, tiled 3x4',
-  'human wrestling — two figures locked together in 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'baby crawling — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a 3x4 grid',
-  'human hammering a nail — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full swing',
-];
-function composeMuybridgePlatePrompt(cw) {
-  const subj = MUYBRIDGE_SUBJECTS[Math.floor(Math.random() * MUYBRIDGE_SUBJECTS.length)];
-  return (
-    `Eadweard Muybridge motion-study plate as wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Subject: ${subj}. ` +
-    `Each pose is a flat stencil silhouette in single ink on the solid ground — no chiaroscuro, no shading, no anatomical detail inside. ` +
-    `Render as a SEAMLESS REPEAT — the full plate is one tile, edge-to-edge. ` +
-    `Reference: Muybridge "Animal Locomotion" 1887, plate-format, single-color blueprint-style serigraph treatment. NOT photographic, NOT engraved — flat stencil only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── LIPS — Steve directive 2026-05-20. 4-color max, sparse iconic lips on
-// generous ground. Inspired by Warhol Marilyn, Lichtenstein, Patrick Nagel,
-// matchbook covers.
-const LIPS_SUBJECTS = [
-  'one large parted red-velvet lips silhouette centered on the tile, no face, no teeth — just the lips. 80% empty cream ground around it',
-  'a single perfect lipstick-kiss imprint (blot mark) on the upper-third of the tile in scarlet, otherwise empty cream ground',
-  'two lips facing each other almost touching (mid-kiss silhouette), one in deep red and one in cream-outline, lots of empty ground around them',
-  'pursed pouting lips in profile silhouette, scarlet red, no other elements, vast clean ground',
-  'lips biting a single stem of a tiny rose — minimalist Patrick-Nagel silhouette, two-tone (red lips, black rose silhouette, cream ground)',
-  'classic Andy Warhol style pop-art lips — one giant pair of half-open red lips with a thin black outline, 4 colors max (red, black, cream, optional cream highlight on lower lip)',
-];
-
-function composeLipsPrompt(subject, cw) {
-  // For lips: ground=cream/ivory, figure=scarlet red. Override colorway.
-  return (
-    `Lips wallpaper — minimalist pop-art register. ` +
-    `Ground: solid cream-ivory (#F2EADB). Figure: deep scarlet red (#B0282A). ` +
-    `Subject: ${subject}. ` +
-    `Up to 4 colors maximum (cream + scarlet + optional black outline + optional cream highlight on lower lip). NO bleed. NO halftone. NO gradient. ` +
-    `Reference: Andy Warhol pop-art lips, Patrick Nagel, vintage matchbook cover, single-pass screen-print. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── MONTEREY WEST-COAST MURAL — Steve directive 2026-05-20.
-// 6 scene subjects × 4 seasonal colorways = 24 base designs. Each is a single
-// 36"-wide × 132"-tall (11ft) panel intended to be the FIRST in a multi-panel
-// mural set. Admin can later "+next panel" to extend the scene rightward
-// across the wall (via /api/design/:id/add-next-panel).
-const MONTEREY_SUBJECTS = [
-  'Lone Cypress on Pebble Beach 17-Mile Drive — a single windswept Monterey cypress silhouetted on a granite headland with the Pacific behind, hand-painted scenic register, de Gournay aesthetic',
-  'Cannery Row at low tide — silhouettes of the historic canneries on pilings over kelp-strewn rocks, fog ribbons in the upper third',
-  'Point Pinos Lighthouse at dusk — the oldest continuously-operating lighthouse on the West Coast with cypress hedge and bluff in foreground',
-  'Carmel Mission Basilica — adobe bell tower silhouette with rose-garden hedges and ancient olive trees',
-  'Big Sur Bixby Creek Bridge silhouette — arched span over the canyon mouth with the Pacific opening to the west, redwoods on the inland slope',
-  'Monterey Bay kelp-forest above-and-below cross-section — sea-otters on the surface, kelp fronds rising from the seabed, single-layer silhouette only',
-];
-
-const SEASONAL_COLORWAYS = [
-  { season: 'spring', name: 'Spring Bloom',     ground: '#A8B79A', figure: '#F2EADB', tone: 'sage-mint' },
-  { season: 'summer', name: 'Summer Surf',      ground: '#3E6B82', figure: '#F2EADB', tone: 'bay-blue' },
-  { season: 'fall',   name: 'Autumn Saddle',    ground: '#7A4A28', figure: '#F2EADB', tone: 'cypress-burnt-umber' },
-  { season: 'winter', name: 'Winter Fog',       ground: '#9CA09B', figure: '#3A2A1C', tone: 'pacific-mist' },
-];
-
-function composeMontereyMuralPrompt(subject, cw) {
-  return (
-    `Monterey, California west-coast mural panel — ${subject}. ` +
-    `${cw.season.charAt(0).toUpperCase() + cw.season.slice(1)} colorway: ${cw.name}. Ground: solid ${cw.tone} (${cw.ground}). Figure: ${cw.figure} single-ink. ` +
-    `Composition: panoramic vertical panel, motif anchored center-bottom, sky/atmosphere extending up the panel (still in solid ground tone, NOT a sky gradient), generous breathing room. ` +
-    `This is a FULL-ROOM MURAL — width = full wall, height = full ceiling. Compose for a 12'-wide × 9'-tall wall. The customer will choose 24"/36"/54" print-panel widths at order time — generate at room scale, not panel scale. ` +
-    `4-COLOR PALETTE WITH HUE SYNC — all four colors share the same hue family (no warm/cool mixing). Ground + figure as the two main tones; one HIGHLIGHT color for accents (lips, eyes, fingernails, fruit, small jewels); one SHADOW color (under figures, in eye sockets, beneath leaves). Highlight + shadow stay in the same hue family as ground+figure. ` +
-    `Reference: de Gournay scenic panel, hand-painted silk register, NOT Haeckel, NOT Audubon. Single-layer stencil silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// Tall panel kind — 36" wide × 132" tall (11ft). Different from the seamless_tile
-// kind which is square. Panel kind is meant to hang as-is, not tile-repeat.
-// 2026-05-20 Steve directive: murals are FULL ROOM dimensions, not panels.
-// Customer chooses 24"/36"/54" panel width at print time — generation is room-
-// scale. Default room: 144" wide (12 ft) × 108" tall (9 ft ceiling) @ 150 DPI.
-// SDXL renders at 1536×1152 (4:3 max) — at-print upscale to 21600×16200
-// happens at order time via Topaz/Real-ESRGAN.
-const PANEL_W = 144;        // inches (metadata)
-const PANEL_H = 108;        // inches (metadata)
-const PANEL_RENDER_W = 1536; // pixels (SDXL output)
-const PANEL_RENDER_H = 1152; // pixels (SDXL output)
-
-// 2026-05-20 Steve directive: "up to 4 colors using for highlights like
-// lips.. eyes.. fingernails.. accents in another color and shadows. always
-// make sure hue is in sync." Murals get 4 colors with hue-sync constraint.
-// Tile tracks (cactus/chinoiserie/etc.) stay 2-color.
-const HUE_SYNC_QUARTETS = [
-  // Each quartet is [ground, figure, highlight, shadow] within the same hue
-  // family. The hue-sync constraint = all 4 colors share the same dominant
-  // hue ± neutrals.
-  // WARM EARTH (cream/saddle/rose/cocoa)
-  { name: 'Saddle Rose Warmth', ground: '#F2EADB', figure: '#5C2329', highlight: '#B0282A', shadow: '#2A1010' },
-  // FOREST GREEN (Steve's 10492-loved family)
-  { name: 'Forest Loden Quartet', ground: '#3C4844', figure: '#E8E2D1', highlight: '#A8B79A', shadow: '#1A2520' },
-  // INK + GOLD (Steve's high-end Thibaut energy)
-  { name: 'Ink Library', ground: '#1F2740', figure: '#E8DDB8', highlight: '#C9A14B', shadow: '#0B0F1C' },
-  // BORDEAUX
-  { name: 'Bordeaux Velvet', ground: '#5C2329', figure: '#F2EADB', highlight: '#C84A3A', shadow: '#2A0E10' },
-  // OXBLOOD
-  { name: 'Oxblood', ground: '#722F2F', figure: '#F2EADB', highlight: '#A85040', shadow: '#3A1010' },
-  // STONE PEWTER
-  { name: 'Stone Pewter', ground: '#8A857B', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#3A3530' },
-  // MOSS SLATE
-  { name: 'Moss Slate', ground: '#566554', figure: '#F2EADB', highlight: '#A8B79A', shadow: '#1A2520' },
-  // CHARCOAL + CREAM
-  { name: 'Charcoal Cream', ground: '#3D3833', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#0F0D0B' },
-];
-function pickHueSyncQuartet() {
-  return HUE_SYNC_QUARTETS[Math.floor(Math.random() * HUE_SYNC_QUARTETS.length)];
-}
-
-// ── DESIGNER-ZOO-CALM — Steve directive 2026-05-20 ("love this theme")
-// Inspired by accepted design 10492. Forest-green ground + bone-ivory animal
-// silhouettes, dignified standing/sitting poses (not staggering), sparse
-// fern/grass botanical fillers between animals, Cole-Son/Sister-Parish energy.
-const ZOO_CALM_ANIMALS = [
-  'pair of red pandas in symmetric grooming pose',
-  'bear seated upright in a meditative pose, head slightly tilted',
-  'a tall standing fox in profile, ears alert',
-  'pair of foxes mirrored facing each other, tails curving outward',
-  'family of three lemurs perched in a vertical stack',
-  'pair of standing wolves facing forward, dignified',
-  'mountain lion seated in regal posture, tail curled',
-  'pair of badgers facing each other in symmetric pose',
-  'standing pangolin in profile, scales catching highlight as cut-outs',
-  'pair of capybaras seated side-by-side',
-  'tall standing crane with neck curved (no flying — standing pose only)',
-  'pair of forest deer with antlers, mirrored facing inward',
-  'standing fennec fox with oversized ears, alert pose',
-  'pair of stoats standing upright, paws raised',
-  'standing armadillo in profile, shell silhouette clean',
-  'lounging snow leopard with paws extended',
-];
-const ZOO_CALM_FILLERS = [
-  'sparse fern fronds rising between the animals',
-  'tall slender grass clusters in negative space between motifs',
-  'small wildflower clusters between motifs',
-  'pinecone-and-needle sprigs',
-  'wild-rose stems with single open blossoms',
-  'tall cattails between figures',
-  'sparse oak-leaf clusters in negative space',
-  'olive branches between motifs',
-];
-const ZOO_CALM_PALETTE = [
-  { name: 'Forest Loden', tone: 'deep forest green', hex: '#3C4844' },
-  { name: 'Hunter Olive', tone: 'hunter olive', hex: '#48543A' },
-  { name: 'Bottle Green', tone: 'bottle-green', hex: '#2E4538' },
-  { name: 'Pine Charcoal', tone: 'pine charcoal', hex: '#3A4A45' },
-  { name: 'Moss Slate', tone: 'moss slate', hex: '#566554' },
-  { name: 'Ink Navy', tone: 'ink navy', hex: '#1F2B3A' },
-];
-function composeDesignerZooCalmPrompt() {
-  const animal = ZOO_CALM_ANIMALS[Math.floor(Math.random() * ZOO_CALM_ANIMALS.length)];
-  const filler = ZOO_CALM_FILLERS[Math.floor(Math.random() * ZOO_CALM_FILLERS.length)];
-  const cw = ZOO_CALM_PALETTE[Math.floor(Math.random() * ZOO_CALM_PALETTE.length)];
-  return (
-    `Designer Wallcoverings zoo wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${animal}. Renders at wall-scale 36-inch seamless tile. Animals are LARGE, anchored on the central vertical axis of symmetry, dignified standing or seated pose (NOT staggering, NOT falling, NOT mid-motion). ` +
-    `Between the animals: ${filler}, kept sparse — generous breathing room of clean ground tone visible throughout. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior shading, NO anatomical detail, NO fur texture. Spots/stripes/markings appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    `Reference: Cole & Son archival, Sister Parish, Designer Wallcoverings house style. Refined, restrained, designer-grade. ` +
-    SHARED_ARTDIR
-  );
-}
-
-const TRACKS = [
-  // Weighted — designer-zoo-calm appears MORE often per Steve "do more" of 10492
-  'designer-zoo-calm','designer-zoo-calm','designer-zoo-calm','designer-zoo-calm',
-  'chinoiserie','stripe','cactus','drunk-zoo-36','face-skull-damask',
-  'designer-scenic','throttled-plaster-roses','muybridge-plate','monterey-mural',
-];
-
-// Variable repeat size per Steve directive 2026-05-20: vary 24"/36"/54" so
-// designs render at different motif scales (small/medium/large wall coverage).
-const REPEAT_SIZES = [24, 36, 36, 36, 54];
-function pickRepeatSize() {
-  return REPEAT_SIZES[Math.floor(Math.random() * REPEAT_SIZES.length)];
-}
-const REPEAT_INCHES = 36;   // wall-scale repeat — Steve directive 2026-05-20
-function nextTrack() {
-  return TRACKS[Math.floor(Math.random() * TRACKS.length)];
-}
-
-// ── Stop conditions ──────────────────────────────────────────────────
-function shouldStop(state) {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('HALT — flag file present at ' + HALT_FLAG);
-    return true;
-  }
-  if (state.spentUsd >= BUDGET_CAP_USD) {
-    logLine(`HALT — budget cap reached ($${state.spentUsd.toFixed(2)} >= $${BUDGET_CAP_USD})`);
-    return true;
-  }
-  if (Date.now() >= STOP_DEADLINE.getTime()) {
-    logLine(`HALT — stop deadline reached (${new Date().toLocaleString()} past ${STOP_DEADLINE.toLocaleString()})`);
-    return true;
-  }
-  return false;
-}
-
-// ── Generate one design ──────────────────────────────────────────────
-function generateOne(track, cw) {
-  let prompt;
-  switch (track) {
-    case 'cactus':          prompt = composeCactusPrompt(cw); break;
-    case 'chinoiserie':     prompt = composeChinoiseriePrompt(cw); break;
-    case 'toile':           prompt = composeToilePrompt(cw); break;
-    case 'stripe':          prompt = composeStripePrompt(cw); break;
-    case 'quatrefoil':      prompt = composeQuatrefoilPrompt(cw); break;
-    case 'diamond-trellis': prompt = composeDiamondTrellisPrompt(cw); break;
-    case 'drunk-zoo-36':    prompt = composeDrunkZoo36Prompt(cw); break;
-    case 'face-skull-damask': prompt = composeFaceSkullDamaskPrompt(cw); break;
-    case 'designer-scenic':  prompt = composeDesignerScenicPrompt(cw); break;
-    case 'throttled-plaster-roses': prompt = composeThrottledPlasterRosesPrompt(cw); break;
-    case 'muybridge-plate':   prompt = composeMuybridgePlatePrompt(cw); break;
-    case 'designer-zoo-calm': prompt = composeDesignerZooCalmPrompt(); break;
-    case 'monterey-mural':    {
-      const subj = MONTEREY_SUBJECTS[Math.floor(Math.random() * MONTEREY_SUBJECTS.length)];
-      const season = SEASONAL_COLORWAYS[Math.floor(Math.random() * SEASONAL_COLORWAYS.length)];
-      // Override the colorway with the seasonal one for this track
-      cw = { name: season.name, hex: season.ground, tone: season.tone, slug: season.season };
-      prompt = composeMontereyMuralPrompt(subj, season);
-      break;
-    }
-    default:                prompt = composeCactusPrompt(cw);
-  }
-  if (!settlementOK(prompt)) {
-    logLine(`SKIP settlement — track=${track} cw=${cw.slug}`);
-    return { ok: false, settlementBlock: true };
-  }
-  // Each track inserts into its own category for sortability + variety.
-  const category = track === 'cactus'          ? 'cactus'
-                 : track === 'chinoiserie'     ? 'chinoiserie'
-                 : track === 'toile'           ? 'toile-de-jouy'
-                 : track === 'stripe'          ? 'stripe'
-                 : track === 'quatrefoil'      ? 'quatrefoil-ogee'
-                 : track === 'diamond-trellis' ? 'diamond-trellis'
-                 : track === 'drunk-zoo-36'    ? 'drunk-zoo-36'
-                 : track === 'face-skull-damask' ? 'face-skull-damask'
-                 : track === 'designer-scenic' ? 'designer-scenic'
-                 : track === 'throttled-plaster-roses' ? 'throttled-plaster-roses'
-                 : track === 'muybridge-plate' ? 'muybridge-plate'
-                 : track === 'monterey-mural' ? 'monterey-mural'
-                 : track === 'designer-zoo-calm' ? 'designer-zoo-calm'
-                 : 'drunk-animals-designer';
-  // Mural panels render at 36"×132" (11ft); everything else at square tiles
-  // with variable size per Steve 2026-05-20 (24/36/54).
-  const isPanel = (track === 'monterey-mural');
-  const w = isPanel ? PANEL_W : pickRepeatSize();
-  const h = isPanel ? PANEL_H : w;
-  const kind = isPanel ? 'mural_panel' : 'seamless_tile';
-  const r = spawnSync('node', [
-    path.join(__dirname, 'generate_designs.js'),
-    '--n', '1',
-    '--kind', kind,
-    '--width', String(w),
-    '--height', String(h),
-    '--category', category,
-    '--prompts', prompt,
-  ], { cwd: ROOT, encoding: 'utf8', env: process.env });
-
-  if (r.status === 0) {
-    // generate_designs.js prints "  ✓ #<id> ·" per-design and "IDs: <id>,..." summary
-    const summary = (r.stdout || '').match(/IDs:\s*(\d+(?:,\s*\d+)*)/);
-    const perLine = (r.stdout || '').match(/✓\s*#(\d+)/);
-    const idStr = summary ? summary[1].split(/,\s*/)[0] : (perLine ? perLine[1] : null);
-    const id = idStr ? parseInt(idStr, 10) : null;
-    // POST-PROCESS: force 2-tone quantize on the output PNG. Eliminates bleed
-    // between layers (Steve directive 2026-05-20 — looks cheap). The script
-    // backs up the original to <file>.original.png before overwriting.
-    if (id) {
-      try {
-        // psqlAt returns tuples-only, no header/footer — clean single-value reads
-        const lp = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=${id};"`, { encoding: 'utf8' }).trim();
-        if (lp && fs.existsSync(lp)) {
-          const q = spawnSync('python3', [path.join(__dirname, 'quantize-2tone.py'), lp], { encoding: 'utf8' });
-          if (q.status === 0) {
-            logLine(`  2tone OK id=${id}: ${(q.stdout || '').trim().split('->')[1] || '?'}`);
-            // COLORWAY-VARIANTS — for every 2-toned design, generate 3 free
-            // recolors. Steve directive 2026-05-20. Each variant becomes a
-            // child row in spoon_all_designs with parent_design_id=id.
-            // Skip for mural panels (they're meant to be a multi-panel set,
-            // colorway variation happens at the source-prompt level there).
-            if (!isPanel) {
-              try {
-                const cwOut = spawnSync('python3', [path.join(__dirname, 'colorway-variants.py'), lp], { encoding: 'utf8' });
-                if (cwOut.status === 0) {
-                  const parsed = JSON.parse((cwOut.stdout || '').trim().split('\n').pop() || '{}');
-                  const variants = parsed.variants || [];
-                  for (const v of variants) {
-                    if (!fs.existsSync(v.path)) continue;
-                    const cwTitle = `${category} · ${v.slug}`;
-                    // Two-step fix for the "PLACEHOLDER image_url" bug — see
-                    // git log + the bulk PG UPDATE on 2026-05-20. The earlier
-                    // single-statement CTE (INSERT + UPDATE in one) failed
-                    // because PG's data-modifying CTEs share a snapshot, so the
-                    // UPDATE didn't see the just-inserted row and the URL stayed
-                    // PLACEHOLDER. Two separate psql calls — INSERT...RETURNING
-                    // captures id, then a follow-up UPDATE writes the real URL.
-                    const insertSql = `
-                      INSERT INTO spoon_all_designs (brand, kind, width_in, height_in, panels, generator, prompt, image_url, local_path, dominant_hex, category, is_published, notes, parent_design_id, created_at)
-                      SELECT brand, kind, width_in, height_in, panels, generator, prompt || E'\\n[colorway-variant of #${id}]', '/designs/img/by-id/PLACEHOLDER', '${v.path.replace(/'/g, "''")}', '${v.ground.replace(/'/g, "''")}', '${cwTitle.replace(/'/g, "''")}', TRUE, E'colorway variant: ${v.slug} (ground=${v.ground}, figure=${v.figure}) of parent #${id}', ${id}, NOW()
-                      FROM spoon_all_designs WHERE id=${id}
-                      RETURNING id;`;
-                    try {
-                      const insertRaw = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' });
-                      // psql -At emits the RETURNING value on line 1, then the
-                      // command tag (e.g. "INSERT 0 1") on line 2. Earlier
-                      // version used `.split(/\s+/).pop()` which grabbed "1"
-                      // from the command tag. Take the FIRST line instead.
-                      const cwId = (insertRaw.trim().split('\n')[0] || '').trim();
-                      if (cwId && /^\d+$/.test(cwId)) {
-                        try {
-                          const updOut = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
-                          logLine(`    cw url-patch id=${cwId} → ${updOut || '(empty)'}`);
-                        } catch (uErr) {
-                          logLine(`    cw url-patch FAIL id=${cwId}: ${(uErr.message || '').slice(0, 200)}`);
-                        }
-                      } else {
-                        logLine(`    cw NO-ID raw=${JSON.stringify(insertRaw).slice(0, 300)}`);
-                      }
-                      logLine(`    cw child id=${cwId} (${v.slug}) of parent ${id}`);
-                    } catch (e2) {
-                      logLine(`    cw INSERT FAIL ${v.slug}: ${e2.message.slice(0, 200)}`);
-                    }
-                  }
-                } else {
-                  logLine(`  cw FAIL id=${id}: ${(cwOut.stderr || cwOut.stdout || '').slice(0, 200)}`);
-                }
-              } catch (e3) {
-                logLine(`  cw EXC id=${id}: ${e3.message.slice(0, 200)}`);
-              }
-            }
-          } else {
-            logLine(`  2tone FAIL id=${id}: ${(q.stderr || q.stdout || '').slice(0, 200)}`);
-          }
-        } else {
-          logLine(`  2tone SKIP id=${id}: local_path not found (${lp})`);
-        }
-      } catch (e) {
-        logLine(`  2tone EXC id=${id}: ${e.message.slice(0, 200)}`);
-      }
-    }
-    // Auto-publish so Steve sees it in /designs
-    if (id) {
-      try {
-        psql(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
-      } catch (e) {
-        logLine(`publish-flip FAIL id=${id}: ${e.message.slice(0, 120)}`);
-      }
-    }
-    return { ok: true, id, category, colorway: cw.slug };
-  } else {
-    return { ok: false, stderr: (r.stderr || r.stdout || '').slice(0, 200) };
-  }
-}
-
-// ── Main loop ────────────────────────────────────────────────────────
-(async () => {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('ABORT — halt flag already exists at startup. Remove it first.');
-    process.exit(1);
-  }
-  const state = { iterations: 0, ok: 0, fail: 0, settlementSkipped: 0, spentUsd: 0, startedAt: nowIso() };
-  logLine(`START night-builder · budget=$${BUDGET_CAP_USD} stop=${STOP_DEADLINE.toLocaleString()} cooldown=${COOLDOWN_MS}ms`);
-  logLine(`Track mix: 30% cactus / 70% drunk-animals-designer · ${MUTED_PALETTE.length} muted colorways`);
-
-  while (!shouldStop(state)) {
-    state.iterations++;
-    const track = nextTrack();
-    const cw = pickColorway();
-    const r = generateOne(track, cw);
-    state.spentUsd += PER_IMAGE_USD;
-    if (r.settlementBlock) {
-      state.settlementSkipped++;
-    } else if (r.ok) {
-      state.ok++;
-      logLine(`OK  #${state.iterations} ${track}/${cw.slug} id=${r.id} (cumul: ok=${state.ok} fail=${state.fail} spent=$${state.spentUsd.toFixed(2)})`);
-    } else {
-      state.fail++;
-      logLine(`FAIL #${state.iterations} ${track}/${cw.slug}: ${r.stderr || 'unknown'}`);
-      // brief backoff on failure (rate-limit or transient)
-      await new Promise(rs => setTimeout(rs, 15000));
-    }
-    await new Promise(rs => setTimeout(rs, COOLDOWN_MS));
-  }
-
-  logLine(`STOP iterations=${state.iterations} ok=${state.ok} fail=${state.fail} settlementSkip=${state.settlementSkipped} spent=$${state.spentUsd.toFixed(2)}`);
-  process.exit(0);
-})();
diff --git a/scripts/night-builder.js.bak-vendor-scrub b/scripts/night-builder.js.bak-vendor-scrub
deleted file mode 100755
index 5a136a9..0000000
--- a/scripts/night-builder.js.bak-vendor-scrub
+++ /dev/null
@@ -1,906 +0,0 @@
-#!/usr/bin/env node
-// All-night Replicate generator.
-//
-// Two tracks, interleaved:
-//   A) Muted Gucci-palette CACTUS variants (extends the existing 10 colorways)
-//   B) Drunk-animals → designer-elevated reinterpretation (Schumacher/Cole&Son
-//      register, muted Gucci palette, single-ink, archival gouache)
-//
-// Steve's brief: "continue creating patterns all night with the subdued hues
-// of grey, taupe, cream, ivory, gucci browns, recreate the drunk animals with
-// much more designer like designs."
-//
-// Hard rules:
-//   - Budget cap: $20 (≈2000 Replicate SDXL images at ~$0.01 ea)
-//   - Halt: touch /tmp/halt-night-builder.flag to stop after current image
-//   - Stop time: 07:30 local (Steve wakes ~8 AM)
-//   - Settlement gate inline (single-ink + no Part B elements = OK)
-//   - is_published=TRUE so Steve sees them in /designs immediately
-//   - Category: 'drunk-animals-designer' for the B-track (NOT 'drunk-animals')
-//     so Steve can sort + dedupe vs the kitsch originals
-//   - Cooldown: 5s between calls to be polite to Replicate
-//
-// Cost tracking: each Replicate image hits the cost-tracker via log.js
-// (already wired in generate_designs.js).
-
-const { spawnSync } = require('child_process');
-const fs = require('fs');
-const path = require('path');
-const { execSync } = require('child_process');
-
-const ROOT = path.join(__dirname, '..');
-const HALT_FLAG = '/tmp/halt-night-builder.flag';
-const LOG = '/tmp/night-builder.log';
-const STOP_HOUR = 7;   // local: stop at 07:30 next-morning
-const STOP_MIN  = 30;
-const BUDGET_CAP_USD = 20.0;
-const PER_IMAGE_USD = 0.011;   // Replicate SDXL ~$0.01 (incl. 10% safety margin)
-const COOLDOWN_MS = 5000;
-
-// Compute the actual next-7:30-AM deadline relative to script start.
-function computeStopDeadline() {
-  const now = new Date();
-  const d = new Date(now);
-  d.setHours(STOP_HOUR, STOP_MIN, 0, 0);
-  // If 7:30 today is already in the past, target 7:30 tomorrow.
-  if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
-  return d;
-}
-const STOP_DEADLINE = computeStopDeadline();
-
-const MUTED_PALETTE = [
-  { slug: 'smoke-ash',       name: 'Smoke Ash',       hex: '#6B6660', tone: 'warm-mid-grey' },
-  { slug: 'mushroom-taupe',  name: 'Mushroom Taupe',  hex: '#8A7E70', tone: 'designer-taupe' },
-  { slug: 'linen-cream',     name: 'Linen Cream',     hex: '#EDE4D3', tone: 'soft-cream' },
-  { slug: 'bone-ivory',      name: 'Bone Ivory',      hex: '#F2EADB', tone: 'warm-ivory' },
-  { slug: 'gucci-cocoa',     name: 'Gucci Cocoa',     hex: '#5C4033', tone: 'deep-cocoa' },
-  { slug: 'antique-tan',     name: 'Antique Tan',     hex: '#A78863', tone: 'warm-camel' },
-  { slug: 'stone-pewter',    name: 'Stone Pewter',    hex: '#7A736A', tone: 'cool-pewter' },
-  { slug: 'saddle-mocha',    name: 'Saddle Mocha',    hex: '#4A3528', tone: 'saddle-leather-brown' },
-  { slug: 'champagne-linen', name: 'Champagne Linen', hex: '#D7C9A8', tone: 'champagne-neutral' },
-  { slug: 'charcoal-smoke',  name: 'Charcoal Smoke',  hex: '#3D3833', tone: 'near-black-charcoal' },
-];
-
-const DB = 'dw_unified';
-const PSQL = `psql ${DB} -At -q`;
-function psql(sql) {
-  return execSync(PSQL, { input: sql, encoding: 'utf8' }).trim();
-}
-
-function nowIso() { return new Date().toISOString(); }
-function logLine(s) {
-  const line = `[${nowIso()}] ${s}`;
-  console.log(line);
-  fs.appendFileSync(LOG, line + '\n');
-}
-
-function pickColorway() {
-  return MUTED_PALETTE[Math.floor(Math.random() * MUTED_PALETTE.length)];
-}
-
-// ── Track A: cactus variants in muted palette ────────────────────────
-const CACTUS_PATTERNS = [
-  'tall saguaro forms with raised arms, scattered prickly-pear paddles',
-  'dense agave rosettes with whip-stem ocotillo verticals',
-  'barrel cactus rosettes and cholla branches in tight tessellation',
-  'sprawling prickly-pear paddle clusters as a vertical wallpaper repeat',
-  'ocotillo whip-stem fountain pattern with small barrel-cactus accents',
-];
-
-function composeCactusPrompt(cw) {
-  const pattern = CACTUS_PATTERNS[Math.floor(Math.random() * CACTUS_PATTERNS.length)];
-  return (
-    `Cactus Repeat — ${cw.name} colorway. ` +
-    `Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. ` +
-    `Sonoran desert cacti as FLAT STENCIL SILHOUETTES: ${pattern}, arranged in a tessellated seamless wallpaper repeat. ` +
-    `Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. ` +
-    `All cacti upright, oriented vertically, symmetric placement on the vertical axis. ` +
-    `Edge-to-edge tessellation, generous breathing room of clean ground between motifs. ` +
-    `Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track B: DEBAUCHED ANIMALS (rewritten 2026-05-20 per Steve directive) ──
-// References:
-//   1. Asterix drunk-Roman-legionnaire pose (Uderzo): staggering gait, leg
-//      kicking sideways, helmet askew, X-pupil eyes, wobble-line motion arc.
-//   2. Eadweard Muybridge "Animal Locomotion" sequential-motion plates: each
-//      animal captured at multiple frozen poses showing the full gait cycle,
-//      tiled side-by-side. The original "movement sneak" before cinema existed.
-// Tone: HEDONISTIC, DEBAUCHED, BACCHANALIAN. Cocktail party gone fully off
-// the rails. Animals are mid-stagger, mid-collapse, mid-tumble — never in
-// dignified repose.
-const ANIMAL_DESCRIPTORS = [
-  // STAGGERING / FALLING
-  'staggering bear mid-stumble with one paw airborne, Asterix-drunk-legionnaire pose',
-  'tumbling fox falling backward limbs splayed',
-  'reticulated giraffe slumped against an invisible wall, legs splayed Bambi-on-ice',
-  'red panda hanging upside down from a phantom branch, eyes spiraled',
-  'lemur mid-cartwheel, limbs flung in four directions, Uderzo motion arcs around it',
-  'sloth lying flat on its back, all four limbs in the air, classic dead-drunk pose',
-  'capybara mid-belly-laugh slumped sideways, hiccup bubble above',
-  'orangutan kicking one leg high in a Charleston-stagger, the other arm windmilling',
-  'raccoon in a top hat falling face-first toward the viewer',
-  'elephant calf doing a full pratfall on its back legs, trunk waving wildly',
-  'tree frog mid-leap and missing, arms grasping at empty air',
-  'Bornean orangutan blowing a kiss with one eye crossed, drink mid-spill',
-  'koala wrapped around a bottle larger than itself, swaying',
-  'wallaby boxer mid-haymaker against nothing, swinging at phantoms',
-  'tasmanian devil mid-spin, dust-cloud lines around it',
-  'fennec fox flopped on its side, tongue lolling out',
-  // DANCING / CABARET POSES
-  'flamingo in a high-kick cancan pose, feathers flying',
-  'okapi in a tango dip, partner is an invisible silhouette',
-  'pangolin doing a one-legged drunken pirouette, scales spread out',
-  'capybara in a top hat doing the Charleston',
-  'meerkat trio in a chorus-line kick',
-  'aardvark mid-Lindy-hop swing-out, partner missing',
-  'lemur ballroom-glide with one paw extended, the other holding a cocktail',
-  'red panda in disco pose, one arm up Saturday-Night-Fever style',
-  // PASSED OUT / OBLIVION
-  'walrus snoring with three Zs above it, X-pupils',
-  'turtle on its back legs in the air, hiccup bubble',
-  'manatee floating belly-up with bubbles around its mouth',
-  'wombat sprawled on a velvet ottoman, paws curled',
-  'lazy panther draped over a bar-stool silhouette',
-  'sleepy hedgehog curled inside an empty cocktail glass',
-  // MID-PRATFALL
-  'chimpanzee mid-banana-peel slip (peel falling, not held)',
-  'baboon doing a forward roll into a stack of empty glasses',
-  'mandrill mid-faceplant, legs in the air',
-  'gorilla doing a dignified slow-motion topple, one finger raised',
-  // INTOXICATED VIBE
-  'platypus floating in zero-gravity with a snifter, monocle askew',
-  'armadillo curled mid-tumble, motion-arc behind it',
-  'tapir wobbling on two legs holding a martini glass overhead',
-  'binturong (bearcat) dangling from a velvet rope by its tail, drink in mouth',
-  'ocelot lounging on a chaise with smoking pipe and one eye closed',
-  'civet stretched out on a Persian rug, paws to the ceiling',
-  // BACCHANALIAN HARVEST
-  'wild boar reclined against a barrel, snout sniffing the air',
-  'bison crowned with a wreath of curlicue smoke, head tipped back',
-  'ibex doing a goat-headstand on a stack of bottles',
-  'fallow deer mid-leap with hooves crossed, antlers tangled with ribbons',
-  // CLASSIC CARTOON-DRUNK
-  'cat with five-o\'clock-shadow holding a cocktail like Tom of Tom-and-Jerry',
-  'penguin in a bowtie tipping a top hat with one flipper',
-  'walrus playing a tiny piano with one tusk',
-  'badger in a smoking jacket striking a Sherlock Holmes pose',
-  'opossum hanging by its tail clutching a martini upside-down',
-  // SMOKE-CLOUD / VAPOR POSES
-  'fox blowing a perfect smoke-ring, eye squinted',
-  'rabbit reclining in a hookah-style pose, pipe in paw',
-  'mongoose with three smoke-curls rising from a pipe',
-  'lizard breathing out a single curling vapor-trail',
-  // CRYPTID DEBAUCHERY (real animals only, no fantasy)
-  'aye-aye gesturing wildly with a long finger raised',
-  'star-nosed mole sniffing a cocktail with thirteen tentacles',
-  'pangolin doing a slow Charleston in scaly tuxedo',
-  'kinkajou dangling from a chandelier with a coupe',
-  // MISC SURREAL POSES
-  'sloth doing a slow-motion crowd-surf on raised paws',
-  'porcupine quill-up flexing arms strongman pose',
-  'capybara DJ-ing on a turntable made of orange slices',
-  'badger conducting a phantom orchestra, baton in paw',
-];
-
-const DRINK_DESCRIPTORS = [
-  'half-empty long-stemmed coupe tilted at 45° with champagne sloshing out',
-  'cut-crystal whiskey tumbler frozen mid-toss, amber liquid arcing through the air',
-  'martini glass tipped over, two olives rolling away',
-  'bottle of pilsner held inverted, foam splashing onto the ground',
-  'red wine balloon mid-spill, dark liquid streaming sideways',
-  'frosted vodka decanter being chugged straight from the neck',
-  'tequila shot raised in toast, lime wedge between teeth',
-  'cognac snifter cradled like a baby',
-  'Aperol spritz in one hand, second drink in the other',
-  'magnum of champagne held aloft and shaken, cork popping with a comic burst',
-  'rolled joint trailing a wispy smoke-curl ribbon',
-  'oversize pipe with three smoke-curls forming a clover',
-  'cocktail with three umbrellas crammed into it',
-  'pint glass overflowing with foam pouring over the sides',
-];
-
-// Muybridge-style: sometimes show the animal in a sequence of 3 frozen motion
-// poses (instead of one), tiled side-by-side. Used 30% of the time.
-const MOTION_FORMATS = [
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  // 3 out of 10 → Muybridge sequence:
-  'Muybridge-style sequence of three frozen poses of the SAME animal mid-stagger, tiled side-by-side left-to-right showing the full stumble cycle',
-  'Muybridge-style sequence of three frozen poses of the SAME animal collapsing forward, tiled side-by-side left-to-right',
-  'Muybridge-style sequence of four frozen poses of the SAME animal mid-pratfall, tiled in a 2x2 grid',
-];
-
-const BOTANICAL_SETTINGS = [
-  'set among silhouetted Ghost Orchids',
-  'against a damask-trellis backdrop',
-  'framed by Botticelli-style arabesques',
-  'surrounded by hand-painted Magnolia branches',
-  'within a Schumacher-Audubon herbarium frame',
-  'set in a Cole&Son chinoiserie panel',
-  'flanked by ogival damask medallions',
-  'centered on a wunderkammer specimen plate',
-];
-
-function composeDrunkAnimalPrompt(cw) {
-  const animal    = ANIMAL_DESCRIPTORS[Math.floor(Math.random() * ANIMAL_DESCRIPTORS.length)];
-  const drink     = DRINK_DESCRIPTORS[Math.floor(Math.random() * DRINK_DESCRIPTORS.length)];
-  return (
-    `Cocktail-hour wallpaper — ${animal} cradling a ${drink}. ` +
-    `Colorway: ${cw.name} (${cw.hex}) ground with bone-ivory (#F2EADB) figure ink. ` +
-    `Each animal-and-drink vignette is one FLAT STENCIL SILHOUETTE in bone-ivory — no interior shading, no anatomical detail, no fur texture. The drink glass is part of the same silhouette, also flat ink. ` +
-    `Vignettes arranged in a symmetric tessellated repeat, generous breathing room of clean ground between them. ` +
-    `Strictly no botanical background, no foliage filler, no decorative arabesques behind the figures — the ground is one uniform flat color with nothing on it except the silhouette vignettes. ` +
-    `No bananas, no grapes, no birds, no butterflies. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── SHARED art direction — rewritten 2026-05-20 to ELIMINATE the bleed-through
-// layered-background problem. Anchor refs swapped from natural-history plates
-// (Haeckel/Merian — those DO have atmospheric depth + layered washes) to pure
-// 2-tone serigraph references (Warhol/Lichtenstein/Toulouse-Lautrec/WPA poster
-// /Saul Bass/pochoir stencil — those are GUARANTEED flat 2-tone with NO depth).
-// Rewritten 2026-05-20 — Steve directive: "way too busy for most designs..
-// keep it simple". Old SHARED_ARTDIR encouraged tessellated edge-to-edge
-// filling. New version forces SPARSE motifs + GENEROUS NEGATIVE SPACE.
-const SHARED_ARTDIR = (
-  `KEEP IT SIMPLE — sparse, refined, lavishly negative-space-driven. At LEAST 75-85% of the tile area is solid clean ground tone with NOTHING in it (often 85%+, like Zuber and Maya Romanoff archives). Motifs are FEW (2-4 per tile maximum), well-spaced, never edge-to-edge, never tessellated, never cluttered. The empty ground IS the design — motifs are punctuation, not pattern-fill. ` +
-  `Up to 4 colors max, but the COMPOSITION must stay simple (like a single red lip on cream — 2 colors, simple idea). Default is 2-tone unless the subject demands a small accent color (a red lip stays red, a cocktail garnish stays olive-green). Never more than 4 distinct ink tones. ` +
-  `ABSOLUTELY NO bleed between layers — bleed looks cheap and is FORBIDDEN. Single-layer screen-print only. ` +
-  `NO ghost layer, NO faded background, NO atmospheric depth, NO secondary motifs behind the main figures. The ground is one completely uniform flat solid color from edge to edge. ` +
-  `Reference aesthetic: high-end designer wallcovering register, anchored to these brand archives — Zuber & Cie (hand-blocked scenic), Designers Guild (refined modern romantic), Thibaut (archival classic), Arte International (natural-fiber luxe), Romo (painterly British), Maya Romanoff (handcrafted luxe surface). Subtle. Sparse. Refined. Negative-space-driven. Single-pass silkscreen with two ink screens. ` +
-  `Hard clean razor-sharp edges. No half-tones, no gradient, no watercolor, no crosshatch shading. Every figure region is one flat color. ` +
-  `Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar. ` +
-  `Square aspect ratio, seamless tile repeat (edges meet cleanly), flat-paper rendering, no watermark, no signature, no text, no logos.`
-);
-
-// Universal negative prompt — passed to Replicate as --negative-prompt. Catches
-// the model's instinct to add depth/layers even when text anti-prompts fail.
-const SHARED_NEGATIVE = (
-  `gradient, ombre, fade, faded, ghosted, ghost layer, atmospheric perspective, depth, layered background, ` +
-  `secondary motifs behind, lighter background copies, halftone, half-tone, chromatic gradient, watercolor wash, ` +
-  `airbrush, soft edges, blurry edges, anti-aliased blur, crosshatching, engraving lines, stippling, ` +
-  `three colors, four colors, multi-color, textured ground, mottled background, shadow, drop shadow, ` +
-  `Haeckel, Merian, Audubon, natural-history plate, botanical plate, engraving, cartoon, Pixar, 3D render`
-);
-
-const CHINOISERIE_MOTIFS = [
-  'pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches',
-  'kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above',
-  'lotus blossom roundels in vertical stacks separated by bamboo verticals',
-  'mountain-and-temple silhouette repeats with stylized scholar-cloud forms',
-];
-function composeChinoiseriePrompt(cw) {
-  const motif = CHINOISERIE_MOTIFS[Math.floor(Math.random() * CHINOISERIE_MOTIFS.length)];
-  return (
-    `Chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Motif: ${motif}, arranged in a tessellated seamless repeat. ` +
-    `Cole & Son Mandarin / Ming-dynasty woodblock register. ` + SHARED_ARTDIR
-  );
-}
-
-const TOILE_VIGNETTES = [
-  'a pair of mirror-image country-cottage scenes flanking a central fountain',
-  'symmetric pastoral courtship scene under arching elm-tree silhouettes',
-  'paired equestrian-rider vignettes facing each other across a ribbon-tied bouquet',
-  'symmetric harvest-basket scene with sheaves of wheat and hand-tools',
-];
-function composeToilePrompt(cw) {
-  const vig = TOILE_VIGNETTES[Math.floor(Math.random() * TOILE_VIGNETTES.length)];
-  return (
-    `Toile de Jouy wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-engraving. ` +
-    `Vignette: ${vig}, repeated as a seamless symmetric tile, every figure rendered in fine-line copperplate-engraving style with cross-hatched shading replaced by solid-flat figure tone. ` +
-    `Pierre-Frey / Manuel Canovas archival toile register. ` + SHARED_ARTDIR
-  );
-}
-
-const STRIPE_STYLES = [
-  'wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges',
-  'awning stripe — uniform 6cm vertical bands alternating ground and figure tones',
-  'beaded vertical lines with small ornamental dot-clusters between stripes',
-  'fine triple-pinstripe groups separated by wider blank channels',
-];
-function composeStripePrompt(cw) {
-  const style = STRIPE_STYLES[Math.floor(Math.random() * STRIPE_STYLES.length)];
-  return (
-    `Vertical stripe wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure stripes: bone-ivory (#F2EADB) single-ink. ` +
-    `Pattern: ${style}. ` +
-    `Robert Kime / Colefax & Fowler archival stripe register. ` + SHARED_ARTDIR
-  );
-}
-
-const QUATREFOIL_FIELDS = [
-  'classical quatrefoil tessellation, each quatrefoil oriented identically, edge-to-edge tight grid',
-  'gothic-window quatrefoil-plus-circle interlock, symmetric 4-fold',
-  'moorish-tile quatrefoil with negative-space cross channels',
-  'ogee-quatrefoil hybrid — pointed-arch ogee crossed with classical quatrefoil',
-];
-function composeQuatrefoilPrompt(cw) {
-  const field = QUATREFOIL_FIELDS[Math.floor(Math.random() * QUATREFOIL_FIELDS.length)];
-  return (
-    `Quatrefoil / ogee geometric wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Field: ${field}. ` +
-    `Owen Jones Grammar of Ornament register, strict mathematical tessellation. ` + SHARED_ARTDIR
-  );
-}
-
-const DIAMOND_TRELLIS_FIELDS = [
-  'narrow ogival diamond lattice with empty centers (just the lattice lines)',
-  'diamond trellis with a single tiny center-medallion in each diamond',
-  'crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections',
-  'fine-line diamond lattice with classical tassels at each lattice node',
-];
-function composeDiamondTrellisPrompt(cw) {
-  const field = DIAMOND_TRELLIS_FIELDS[Math.floor(Math.random() * DIAMOND_TRELLIS_FIELDS.length)];
-  return (
-    `Diamond trellis wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-trellis. ` +
-    `Field: ${field}. NOT a damask, NOT a heavy medallion field — light open trellis only. ` +
-    `Soane Britain / De Gournay archival trellis register. ` + SHARED_ARTDIR
-  );
-}
-
-// ── Track H: DRUNK ZOO 36" (Steve-approved 2026-05-20) ──────────────
-// Replaces the giraffe-only track with a broader symmetric-zoo track.
-// 36" repeat — wall-scale large pattern. Each design = one symmetric
-// paired-animal vignette in 19c-engraving aesthetic.
-const ZOO_VIGNETTES = [
-  'two reticulated giraffes facing each other in symmetric profile, necks crossing in a heart-shape, both sipping champagne coupes',
-  'pair of mirror-image giraffes flanking a central acacia tree, both holding cocktail glasses',
-  'mother-and-calf giraffe pair, mother arching over the calf, both with bottles',
-  'pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved',
-  'two sloths hanging in mirror-image from a central acacia branch, each with a cocktail glass',
-  'pair of red pandas in symmetric grooming pose, central bottle between them',
-  'two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern',
-  'pair of capybaras in symmetric resting pose, central mint julep glass between them',
-  'two African elephant calves in symmetric trunk-raised pose, central wine glass',
-  'pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them',
-  'two tipsy frogs in symmetric leaping pose mirrored, central decanter',
-];
-function composeDrunkZoo36Prompt(cw) {
-  const vig = ZOO_VIGNETTES[Math.floor(Math.random() * ZOO_VIGNETTES.length)];
-  return (
-    `Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading, no anatomical line-detail, no spots, no stripes, no fur texture. Spots/stripes appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track I: FACE-SKULL DAMASK (Steve directive 2026-05-20) ──────────
-// Damask is purged unless it has faces or skulls. This track generates new
-// damask designs where the medallion FILL is a hand-engraved face or skull
-// rendered in single-ink screen-print style.
-const FACE_SKULL_MOTIFS = [
-  'memento-mori skull at the center of each ogival damask medallion, crossed bones forming the lattice spines',
-  'classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders',
-  'death\'s-head moth skull pattern at each damask cell, wings forming the connecting flourishes',
-  'Venetian carnival mask face filling each damask medallion, ribbon-ties forming the lattice',
-  'Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds',
-  'Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion',
-  'anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink',
-  'phantom-of-the-opera half-mask face at each medallion, opera-cape ribbons forming the lattice',
-];
-function composeFaceSkullDamaskPrompt(cw) {
-  const motif = FACE_SKULL_MOTIFS[Math.floor(Math.random() * FACE_SKULL_MOTIFS.length)];
-  return (
-    `Face-Skull Damask — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Composition: classic ogival damask lattice, but the medallion FILL is ${motif}. ` +
-    `Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. ` +
-    `Each face/skull/lattice element is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior crosshatching, NO engraved line shading, NO 3D modeling of the skull/face. Pure cut-out silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Settlement check (inline — same logic as src/fliepaper-bugs.js) ──
-function settlementOK(prompt) {
-  let p = String(prompt || '').toLowerCase();
-  // Strip negatives so anti-prompts don't false-trigger
-  p = p
-    .replace(/\b(?:no|not|without|excluding|never|avoid)\b[^.,;]*(?:banana|bird|butterfl|grape|leaf|leaves|frond|palm|foliage|tropical|fluoresc|neon|rainbow)[^.,;]*[.,;]?/g, ' ')
-    .replace(/\bwithout\b[^.,;]*[.,;]/g, ' ')
-    .replace(/\bexcluding\b[^.,;]*[.,;]/g, ' ');
-  // Part B specific elements (affirmative only)
-  const partB = [
-    /\bbanana(?:s|\s+pods?)?\b/,
-    /\bplantains?\b/,
-    /\bgrapes?\b/,
-    /\bbirds?\b/,
-    /\bbutterfl(?:y|ies)\b/,
-  ];
-  for (const rx of partB) if (rx.test(p)) return false;
-  // Foliage = needs review (we still allow, the figure is single-ink)
-  return true;
-}
-
-// ── Track mix — Steve flagged "needs alot more variety in patterns, way
-// too much damasks" 2026-05-20. Six tracks, equal weight:
-//   cactus            — desert botanical (Sonoran luxury)
-//   chinoiserie       — Cole&Son Mandarin/Ming-style
-//   toile-de-jouy     — classic French pastoral one-color
-//   stripe-block      — vertical stripe / pinstripe / awning stripe
-//   quatrefoil-ogee   — geometric tessellation (non-damask)
-//   diamond-trellis   — narrow ogival lattice (lighter than damask)
-// drunk-animals-designer paused — see feedback_no_modern_bending_designs.
-// 2026-05-20 — top-selling interior-designer styles, ALL at 36" repeat:
-//   chinoiserie       — Cole & Son / de Gournay perennial best-seller
-//   stripe            — Colefax & Fowler ticking stripe (designer classic)
-//   cactus            — Steve's house collection (Sonoran luxury)
-//   drunk-zoo-36      — symmetric paired animals (giraffes lead, sloths/lemurs/
-//                       panda/capybara/elephant added), 19c-engraving register
-// 2026-05-20 Steve directives:
-//  - Add fromental-scenic (emulate Fromental.com elegance, NOT copy)
-//  - Add throttled-plaster-roses ("throwled plaster look with roses")
-//  - Thibaut is the high-end LLM crisp reference (NEVER York/Brewster/Marburg)
-const FROMENTAL_SCENES = [
-  'flowering magnolia branches reaching up the panel with peony blossoms at branch nodes',
-  'cascading wisteria vines hanging in symmetric clusters with stylized vine leaves',
-  'cherry-blossom branches arching across the panel with falling petal motifs',
-  'climbing rose vines on bamboo-trellis armature with full open rose heads at intersections',
-  'flowering plum branches in winter-Asian-painting register with sparse blossoms',
-];
-function composeFromentalScenicPrompt(cw) {
-  const scene = FROMENTAL_SCENES[Math.floor(Math.random() * FROMENTAL_SCENES.length)];
-  return (
-    `Hand-painted scenic chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) silk register. Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Scene: ${scene}, arranged as a seamless symmetric tile — each branch repeats with bilateral mirror symmetry. ` +
-    `Thibaut-grade archival register, hand-painted silk-panel aesthetic in the spirit of de Gournay or Fromental atelier work (emulate the elegance, do not copy any specific design). ` +
-    `Strictly no birds, no insects, no human figures. Branches and flowers only. ` + SHARED_ARTDIR
-  );
-}
-
-const PLASTER_ROSE_STYLES = [
-  'full-open garden roses in symmetric pairs with their stems crossing at the panel center',
-  'climbing-rose vine in vertical repeats with rose heads at every node and serrated leaves',
-  'damask-style rose medallions — single large rose head encircled by a wreath of smaller buds',
-  'rose-and-thorn-branch tessellation, each rose head identical, edge-to-edge tight grid',
-];
-function composeThrottledPlasterRosesPrompt(cw) {
-  const style = PLASTER_ROSE_STYLES[Math.floor(Math.random() * PLASTER_ROSE_STYLES.length)];
-  return (
-    `Trowelled-plaster-wall wallpaper with roses — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. ` +
-    `Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. ` +
-    `Rose style: ${style}. ` +
-    `Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. ` + SHARED_ARTDIR
-  );
-}
-
-// ── MUYBRIDGE — sequential motion-study plates as wallpaper ──────────
-// Eadweard Muybridge "Animal Locomotion" (1887, public domain) — ~780 plates,
-// each one a tiled grid of frozen gait poses of a single subject. The plate
-// FORMAT is already a perfect wallpaper repeat: 12-24 small images tiled in
-// a grid showing the full motion cycle. Steve directive 2026-05-20.
-const MUYBRIDGE_SUBJECTS = [
-  'horse galloping — sequence of 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full gait cycle, tiled left-to-right in three rows of four',
-  'horse trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule tiled in a 4x4 grid',
-  'racehorse with jockey — 12 frozen pose sequence showing the full stride',
-  'leaping greyhound — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule, dog mid-leap, tiled side-by-side',
-  'galloping dog — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule in a 4x4 grid',
-  'mastiff trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in 3 rows',
-  'cat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule showing the saunter, tiled 4x4',
-  'cat leaping from rock to rock — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'lion pacing — 12 frozen pose sequence tiled in a strip',
-  'tiger walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'elephant walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the heavy gait, tiled 3x4',
-  'ostrich running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'kangaroo hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound, tiled 3x4',
-  'pig trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 4x3 grid',
-  'goat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'donkey trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'camel walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the long stride, tiled 3x4',
-  'rabbit hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound',
-  'deer leaping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a strip',
-  'bison charging — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'rooster strutting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'cockatoo flapping its wings — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'pigeon taking flight — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full wingbeat',
-  'human walking — Muybridge male figure in a loincloth, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human descending stairs — woman in a Victorian dress, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled left-to-right',
-  'human running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human jumping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full leap',
-  'human throwing a discus — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled 3x4',
-  'human ascending stairs — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule left-to-right',
-  'human carrying a heavy bucket — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the strained walk',
-  'human dancing a waltz — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human waving a flag — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human boxing — two figures in 12 frozen pose sequence, tiled 3x4',
-  'human wrestling — two figures locked together in 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'baby crawling — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a 3x4 grid',
-  'human hammering a nail — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full swing',
-];
-function composeMuybridgePlatePrompt(cw) {
-  const subj = MUYBRIDGE_SUBJECTS[Math.floor(Math.random() * MUYBRIDGE_SUBJECTS.length)];
-  return (
-    `Eadweard Muybridge motion-study plate as wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Subject: ${subj}. ` +
-    `Each pose is a flat stencil silhouette in single ink on the solid ground — no chiaroscuro, no shading, no anatomical detail inside. ` +
-    `Render as a SEAMLESS REPEAT — the full plate is one tile, edge-to-edge. ` +
-    `Reference: Muybridge "Animal Locomotion" 1887, plate-format, single-color blueprint-style serigraph treatment. NOT photographic, NOT engraved — flat stencil only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── LIPS — Steve directive 2026-05-20. 4-color max, sparse iconic lips on
-// generous ground. Inspired by Warhol Marilyn, Lichtenstein, Patrick Nagel,
-// matchbook covers.
-const LIPS_SUBJECTS = [
-  'one large parted red-velvet lips silhouette centered on the tile, no face, no teeth — just the lips. 80% empty cream ground around it',
-  'a single perfect lipstick-kiss imprint (blot mark) on the upper-third of the tile in scarlet, otherwise empty cream ground',
-  'two lips facing each other almost touching (mid-kiss silhouette), one in deep red and one in cream-outline, lots of empty ground around them',
-  'pursed pouting lips in profile silhouette, scarlet red, no other elements, vast clean ground',
-  'lips biting a single stem of a tiny rose — minimalist Patrick-Nagel silhouette, two-tone (red lips, black rose silhouette, cream ground)',
-  'classic Andy Warhol style pop-art lips — one giant pair of half-open red lips with a thin black outline, 4 colors max (red, black, cream, optional cream highlight on lower lip)',
-];
-
-function composeLipsPrompt(subject, cw) {
-  // For lips: ground=cream/ivory, figure=scarlet red. Override colorway.
-  return (
-    `Lips wallpaper — minimalist pop-art register. ` +
-    `Ground: solid cream-ivory (#F2EADB). Figure: deep scarlet red (#B0282A). ` +
-    `Subject: ${subject}. ` +
-    `Up to 4 colors maximum (cream + scarlet + optional black outline + optional cream highlight on lower lip). NO bleed. NO halftone. NO gradient. ` +
-    `Reference: Andy Warhol pop-art lips, Patrick Nagel, vintage matchbook cover, single-pass screen-print. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── MONTEREY WEST-COAST MURAL — Steve directive 2026-05-20.
-// 6 scene subjects × 4 seasonal colorways = 24 base designs. Each is a single
-// 36"-wide × 132"-tall (11ft) panel intended to be the FIRST in a multi-panel
-// mural set. Admin can later "+next panel" to extend the scene rightward
-// across the wall (via /api/design/:id/add-next-panel).
-const MONTEREY_SUBJECTS = [
-  'Lone Cypress on Pebble Beach 17-Mile Drive — a single windswept Monterey cypress silhouetted on a granite headland with the Pacific behind, hand-painted scenic register, de Gournay aesthetic',
-  'Cannery Row at low tide — silhouettes of the historic canneries on pilings over kelp-strewn rocks, fog ribbons in the upper third',
-  'Point Pinos Lighthouse at dusk — the oldest continuously-operating lighthouse on the West Coast with cypress hedge and bluff in foreground',
-  'Carmel Mission Basilica — adobe bell tower silhouette with rose-garden hedges and ancient olive trees',
-  'Big Sur Bixby Creek Bridge silhouette — arched span over the canyon mouth with the Pacific opening to the west, redwoods on the inland slope',
-  'Monterey Bay kelp-forest above-and-below cross-section — sea-otters on the surface, kelp fronds rising from the seabed, single-layer silhouette only',
-];
-
-const SEASONAL_COLORWAYS = [
-  { season: 'spring', name: 'Spring Bloom',     ground: '#A8B79A', figure: '#F2EADB', tone: 'sage-mint' },
-  { season: 'summer', name: 'Summer Surf',      ground: '#3E6B82', figure: '#F2EADB', tone: 'bay-blue' },
-  { season: 'fall',   name: 'Autumn Saddle',    ground: '#7A4A28', figure: '#F2EADB', tone: 'cypress-burnt-umber' },
-  { season: 'winter', name: 'Winter Fog',       ground: '#9CA09B', figure: '#3A2A1C', tone: 'pacific-mist' },
-];
-
-function composeMontereyMuralPrompt(subject, cw) {
-  return (
-    `Monterey, California west-coast mural panel — ${subject}. ` +
-    `${cw.season.charAt(0).toUpperCase() + cw.season.slice(1)} colorway: ${cw.name}. Ground: solid ${cw.tone} (${cw.ground}). Figure: ${cw.figure} single-ink. ` +
-    `Composition: panoramic vertical panel, motif anchored center-bottom, sky/atmosphere extending up the panel (still in solid ground tone, NOT a sky gradient), generous breathing room. ` +
-    `This is a FULL-ROOM MURAL — width = full wall, height = full ceiling. Compose for a 12'-wide × 9'-tall wall. The customer will choose 24"/36"/54" print-panel widths at order time — generate at room scale, not panel scale. ` +
-    `4-COLOR PALETTE WITH HUE SYNC — all four colors share the same hue family (no warm/cool mixing). Ground + figure as the two main tones; one HIGHLIGHT color for accents (lips, eyes, fingernails, fruit, small jewels); one SHADOW color (under figures, in eye sockets, beneath leaves). Highlight + shadow stay in the same hue family as ground+figure. ` +
-    `Reference: de Gournay scenic panel, hand-painted silk register, NOT Haeckel, NOT Audubon. Single-layer stencil silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// Tall panel kind — 36" wide × 132" tall (11ft). Different from the seamless_tile
-// kind which is square. Panel kind is meant to hang as-is, not tile-repeat.
-// 2026-05-20 Steve directive: murals are FULL ROOM dimensions, not panels.
-// Customer chooses 24"/36"/54" panel width at print time — generation is room-
-// scale. Default room: 144" wide (12 ft) × 108" tall (9 ft ceiling) @ 150 DPI.
-// SDXL renders at 1536×1152 (4:3 max) — at-print upscale to 21600×16200
-// happens at order time via Topaz/Real-ESRGAN.
-const PANEL_W = 144;        // inches (metadata)
-const PANEL_H = 108;        // inches (metadata)
-const PANEL_RENDER_W = 1536; // pixels (SDXL output)
-const PANEL_RENDER_H = 1152; // pixels (SDXL output)
-
-// 2026-05-20 Steve directive: "up to 4 colors using for highlights like
-// lips.. eyes.. fingernails.. accents in another color and shadows. always
-// make sure hue is in sync." Murals get 4 colors with hue-sync constraint.
-// Tile tracks (cactus/chinoiserie/etc.) stay 2-color.
-const HUE_SYNC_QUARTETS = [
-  // Each quartet is [ground, figure, highlight, shadow] within the same hue
-  // family. The hue-sync constraint = all 4 colors share the same dominant
-  // hue ± neutrals.
-  // WARM EARTH (cream/saddle/rose/cocoa)
-  { name: 'Saddle Rose Warmth', ground: '#F2EADB', figure: '#5C2329', highlight: '#B0282A', shadow: '#2A1010' },
-  // FOREST GREEN (Steve's 10492-loved family)
-  { name: 'Forest Loden Quartet', ground: '#3C4844', figure: '#E8E2D1', highlight: '#A8B79A', shadow: '#1A2520' },
-  // INK + GOLD (Steve's high-end Thibaut energy)
-  { name: 'Ink Library', ground: '#1F2740', figure: '#E8DDB8', highlight: '#C9A14B', shadow: '#0B0F1C' },
-  // BORDEAUX
-  { name: 'Bordeaux Velvet', ground: '#5C2329', figure: '#F2EADB', highlight: '#C84A3A', shadow: '#2A0E10' },
-  // OXBLOOD
-  { name: 'Oxblood', ground: '#722F2F', figure: '#F2EADB', highlight: '#A85040', shadow: '#3A1010' },
-  // STONE PEWTER
-  { name: 'Stone Pewter', ground: '#8A857B', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#3A3530' },
-  // MOSS SLATE
-  { name: 'Moss Slate', ground: '#566554', figure: '#F2EADB', highlight: '#A8B79A', shadow: '#1A2520' },
-  // CHARCOAL + CREAM
-  { name: 'Charcoal Cream', ground: '#3D3833', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#0F0D0B' },
-];
-function pickHueSyncQuartet() {
-  return HUE_SYNC_QUARTETS[Math.floor(Math.random() * HUE_SYNC_QUARTETS.length)];
-}
-
-// ── DESIGNER-ZOO-CALM — Steve directive 2026-05-20 ("love this theme")
-// Inspired by accepted design 10492. Forest-green ground + bone-ivory animal
-// silhouettes, dignified standing/sitting poses (not staggering), sparse
-// fern/grass botanical fillers between animals, Cole-Son/Sister-Parish energy.
-const ZOO_CALM_ANIMALS = [
-  'pair of red pandas in symmetric grooming pose',
-  'bear seated upright in a meditative pose, head slightly tilted',
-  'a tall standing fox in profile, ears alert',
-  'pair of foxes mirrored facing each other, tails curving outward',
-  'family of three lemurs perched in a vertical stack',
-  'pair of standing wolves facing forward, dignified',
-  'mountain lion seated in regal posture, tail curled',
-  'pair of badgers facing each other in symmetric pose',
-  'standing pangolin in profile, scales catching highlight as cut-outs',
-  'pair of capybaras seated side-by-side',
-  'tall standing crane with neck curved (no flying — standing pose only)',
-  'pair of forest deer with antlers, mirrored facing inward',
-  'standing fennec fox with oversized ears, alert pose',
-  'pair of stoats standing upright, paws raised',
-  'standing armadillo in profile, shell silhouette clean',
-  'lounging snow leopard with paws extended',
-];
-const ZOO_CALM_FILLERS = [
-  'sparse fern fronds rising between the animals',
-  'tall slender grass clusters in negative space between motifs',
-  'small wildflower clusters between motifs',
-  'pinecone-and-needle sprigs',
-  'wild-rose stems with single open blossoms',
-  'tall cattails between figures',
-  'sparse oak-leaf clusters in negative space',
-  'olive branches between motifs',
-];
-const ZOO_CALM_PALETTE = [
-  { name: 'Forest Loden', tone: 'deep forest green', hex: '#3C4844' },
-  { name: 'Hunter Olive', tone: 'hunter olive', hex: '#48543A' },
-  { name: 'Bottle Green', tone: 'bottle-green', hex: '#2E4538' },
-  { name: 'Pine Charcoal', tone: 'pine charcoal', hex: '#3A4A45' },
-  { name: 'Moss Slate', tone: 'moss slate', hex: '#566554' },
-  { name: 'Ink Navy', tone: 'ink navy', hex: '#1F2B3A' },
-];
-function composeDesignerZooCalmPrompt() {
-  const animal = ZOO_CALM_ANIMALS[Math.floor(Math.random() * ZOO_CALM_ANIMALS.length)];
-  const filler = ZOO_CALM_FILLERS[Math.floor(Math.random() * ZOO_CALM_FILLERS.length)];
-  const cw = ZOO_CALM_PALETTE[Math.floor(Math.random() * ZOO_CALM_PALETTE.length)];
-  return (
-    `Designer Wallcoverings zoo wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${animal}. Renders at wall-scale 36-inch seamless tile. Animals are LARGE, anchored on the central vertical axis of symmetry, dignified standing or seated pose (NOT staggering, NOT falling, NOT mid-motion). ` +
-    `Between the animals: ${filler}, kept sparse — generous breathing room of clean ground tone visible throughout. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior shading, NO anatomical detail, NO fur texture. Spots/stripes/markings appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    `Reference: Cole & Son archival, Sister Parish, Designer Wallcoverings house style. Refined, restrained, designer-grade. ` +
-    SHARED_ARTDIR
-  );
-}
-
-const TRACKS = [
-  // Weighted — designer-zoo-calm appears MORE often per Steve "do more" of 10492
-  'designer-zoo-calm','designer-zoo-calm','designer-zoo-calm','designer-zoo-calm',
-  'chinoiserie','stripe','cactus','drunk-zoo-36','face-skull-damask',
-  'fromental-scenic','throttled-plaster-roses','muybridge-plate','monterey-mural',
-];
-
-// Variable repeat size per Steve directive 2026-05-20: vary 24"/36"/54" so
-// designs render at different motif scales (small/medium/large wall coverage).
-const REPEAT_SIZES = [24, 36, 36, 36, 54];
-function pickRepeatSize() {
-  return REPEAT_SIZES[Math.floor(Math.random() * REPEAT_SIZES.length)];
-}
-const REPEAT_INCHES = 36;   // wall-scale repeat — Steve directive 2026-05-20
-function nextTrack() {
-  return TRACKS[Math.floor(Math.random() * TRACKS.length)];
-}
-
-// ── Stop conditions ──────────────────────────────────────────────────
-function shouldStop(state) {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('HALT — flag file present at ' + HALT_FLAG);
-    return true;
-  }
-  if (state.spentUsd >= BUDGET_CAP_USD) {
-    logLine(`HALT — budget cap reached ($${state.spentUsd.toFixed(2)} >= $${BUDGET_CAP_USD})`);
-    return true;
-  }
-  if (Date.now() >= STOP_DEADLINE.getTime()) {
-    logLine(`HALT — stop deadline reached (${new Date().toLocaleString()} past ${STOP_DEADLINE.toLocaleString()})`);
-    return true;
-  }
-  return false;
-}
-
-// ── Generate one design ──────────────────────────────────────────────
-function generateOne(track, cw) {
-  let prompt;
-  switch (track) {
-    case 'cactus':          prompt = composeCactusPrompt(cw); break;
-    case 'chinoiserie':     prompt = composeChinoiseriePrompt(cw); break;
-    case 'toile':           prompt = composeToilePrompt(cw); break;
-    case 'stripe':          prompt = composeStripePrompt(cw); break;
-    case 'quatrefoil':      prompt = composeQuatrefoilPrompt(cw); break;
-    case 'diamond-trellis': prompt = composeDiamondTrellisPrompt(cw); break;
-    case 'drunk-zoo-36':    prompt = composeDrunkZoo36Prompt(cw); break;
-    case 'face-skull-damask': prompt = composeFaceSkullDamaskPrompt(cw); break;
-    case 'fromental-scenic':  prompt = composeFromentalScenicPrompt(cw); break;
-    case 'throttled-plaster-roses': prompt = composeThrottledPlasterRosesPrompt(cw); break;
-    case 'muybridge-plate':   prompt = composeMuybridgePlatePrompt(cw); break;
-    case 'designer-zoo-calm': prompt = composeDesignerZooCalmPrompt(); break;
-    case 'monterey-mural':    {
-      const subj = MONTEREY_SUBJECTS[Math.floor(Math.random() * MONTEREY_SUBJECTS.length)];
-      const season = SEASONAL_COLORWAYS[Math.floor(Math.random() * SEASONAL_COLORWAYS.length)];
-      // Override the colorway with the seasonal one for this track
-      cw = { name: season.name, hex: season.ground, tone: season.tone, slug: season.season };
-      prompt = composeMontereyMuralPrompt(subj, season);
-      break;
-    }
-    default:                prompt = composeCactusPrompt(cw);
-  }
-  if (!settlementOK(prompt)) {
-    logLine(`SKIP settlement — track=${track} cw=${cw.slug}`);
-    return { ok: false, settlementBlock: true };
-  }
-  // Each track inserts into its own category for sortability + variety.
-  const category = track === 'cactus'          ? 'cactus'
-                 : track === 'chinoiserie'     ? 'chinoiserie'
-                 : track === 'toile'           ? 'toile-de-jouy'
-                 : track === 'stripe'          ? 'stripe'
-                 : track === 'quatrefoil'      ? 'quatrefoil-ogee'
-                 : track === 'diamond-trellis' ? 'diamond-trellis'
-                 : track === 'drunk-zoo-36'    ? 'drunk-zoo-36'
-                 : track === 'face-skull-damask' ? 'face-skull-damask'
-                 : track === 'fromental-scenic' ? 'fromental-scenic'
-                 : track === 'throttled-plaster-roses' ? 'throttled-plaster-roses'
-                 : track === 'muybridge-plate' ? 'muybridge-plate'
-                 : track === 'monterey-mural' ? 'monterey-mural'
-                 : track === 'designer-zoo-calm' ? 'designer-zoo-calm'
-                 : 'drunk-animals-designer';
-  // Mural panels render at 36"×132" (11ft); everything else at square tiles
-  // with variable size per Steve 2026-05-20 (24/36/54).
-  const isPanel = (track === 'monterey-mural');
-  const w = isPanel ? PANEL_W : pickRepeatSize();
-  const h = isPanel ? PANEL_H : w;
-  const kind = isPanel ? 'mural_panel' : 'seamless_tile';
-  const r = spawnSync('node', [
-    path.join(__dirname, 'generate_designs.js'),
-    '--n', '1',
-    '--kind', kind,
-    '--width', String(w),
-    '--height', String(h),
-    '--category', category,
-    '--prompts', prompt,
-  ], { cwd: ROOT, encoding: 'utf8', env: process.env });
-
-  if (r.status === 0) {
-    // generate_designs.js prints "  ✓ #<id> ·" per-design and "IDs: <id>,..." summary
-    const summary = (r.stdout || '').match(/IDs:\s*(\d+(?:,\s*\d+)*)/);
-    const perLine = (r.stdout || '').match(/✓\s*#(\d+)/);
-    const idStr = summary ? summary[1].split(/,\s*/)[0] : (perLine ? perLine[1] : null);
-    const id = idStr ? parseInt(idStr, 10) : null;
-    // POST-PROCESS: force 2-tone quantize on the output PNG. Eliminates bleed
-    // between layers (Steve directive 2026-05-20 — looks cheap). The script
-    // backs up the original to <file>.original.png before overwriting.
-    if (id) {
-      try {
-        // psqlAt returns tuples-only, no header/footer — clean single-value reads
-        const lp = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=${id};"`, { encoding: 'utf8' }).trim();
-        if (lp && fs.existsSync(lp)) {
-          const q = spawnSync('python3', [path.join(__dirname, 'quantize-2tone.py'), lp], { encoding: 'utf8' });
-          if (q.status === 0) {
-            logLine(`  2tone OK id=${id}: ${(q.stdout || '').trim().split('->')[1] || '?'}`);
-            // COLORWAY-VARIANTS — for every 2-toned design, generate 3 free
-            // recolors. Steve directive 2026-05-20. Each variant becomes a
-            // child row in spoon_all_designs with parent_design_id=id.
-            // Skip for mural panels (they're meant to be a multi-panel set,
-            // colorway variation happens at the source-prompt level there).
-            if (!isPanel) {
-              try {
-                const cwOut = spawnSync('python3', [path.join(__dirname, 'colorway-variants.py'), lp], { encoding: 'utf8' });
-                if (cwOut.status === 0) {
-                  const parsed = JSON.parse((cwOut.stdout || '').trim().split('\n').pop() || '{}');
-                  const variants = parsed.variants || [];
-                  for (const v of variants) {
-                    if (!fs.existsSync(v.path)) continue;
-                    const cwTitle = `${category} · ${v.slug}`;
-                    // Two-step fix for the "PLACEHOLDER image_url" bug — see
-                    // git log + the bulk PG UPDATE on 2026-05-20. The earlier
-                    // single-statement CTE (INSERT + UPDATE in one) failed
-                    // because PG's data-modifying CTEs share a snapshot, so the
-                    // UPDATE didn't see the just-inserted row and the URL stayed
-                    // PLACEHOLDER. Two separate psql calls — INSERT...RETURNING
-                    // captures id, then a follow-up UPDATE writes the real URL.
-                    const insertSql = `
-                      INSERT INTO spoon_all_designs (brand, kind, width_in, height_in, panels, generator, prompt, image_url, local_path, dominant_hex, category, is_published, notes, parent_design_id, created_at)
-                      SELECT brand, kind, width_in, height_in, panels, generator, prompt || E'\\n[colorway-variant of #${id}]', '/designs/img/by-id/PLACEHOLDER', '${v.path.replace(/'/g, "''")}', '${v.ground.replace(/'/g, "''")}', '${cwTitle.replace(/'/g, "''")}', TRUE, E'colorway variant: ${v.slug} (ground=${v.ground}, figure=${v.figure}) of parent #${id}', ${id}, NOW()
-                      FROM spoon_all_designs WHERE id=${id}
-                      RETURNING id;`;
-                    try {
-                      const insertRaw = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' });
-                      // psql -At emits the RETURNING value on line 1, then the
-                      // command tag (e.g. "INSERT 0 1") on line 2. Earlier
-                      // version used `.split(/\s+/).pop()` which grabbed "1"
-                      // from the command tag. Take the FIRST line instead.
-                      const cwId = (insertRaw.trim().split('\n')[0] || '').trim();
-                      if (cwId && /^\d+$/.test(cwId)) {
-                        try {
-                          const updOut = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
-                          logLine(`    cw url-patch id=${cwId} → ${updOut || '(empty)'}`);
-                        } catch (uErr) {
-                          logLine(`    cw url-patch FAIL id=${cwId}: ${(uErr.message || '').slice(0, 200)}`);
-                        }
-                      } else {
-                        logLine(`    cw NO-ID raw=${JSON.stringify(insertRaw).slice(0, 300)}`);
-                      }
-                      logLine(`    cw child id=${cwId} (${v.slug}) of parent ${id}`);
-                    } catch (e2) {
-                      logLine(`    cw INSERT FAIL ${v.slug}: ${e2.message.slice(0, 200)}`);
-                    }
-                  }
-                } else {
-                  logLine(`  cw FAIL id=${id}: ${(cwOut.stderr || cwOut.stdout || '').slice(0, 200)}`);
-                }
-              } catch (e3) {
-                logLine(`  cw EXC id=${id}: ${e3.message.slice(0, 200)}`);
-              }
-            }
-          } else {
-            logLine(`  2tone FAIL id=${id}: ${(q.stderr || q.stdout || '').slice(0, 200)}`);
-          }
-        } else {
-          logLine(`  2tone SKIP id=${id}: local_path not found (${lp})`);
-        }
-      } catch (e) {
-        logLine(`  2tone EXC id=${id}: ${e.message.slice(0, 200)}`);
-      }
-    }
-    // Auto-publish so Steve sees it in /designs
-    if (id) {
-      try {
-        psql(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
-      } catch (e) {
-        logLine(`publish-flip FAIL id=${id}: ${e.message.slice(0, 120)}`);
-      }
-    }
-    return { ok: true, id, category, colorway: cw.slug };
-  } else {
-    return { ok: false, stderr: (r.stderr || r.stdout || '').slice(0, 200) };
-  }
-}
-
-// ── Main loop ────────────────────────────────────────────────────────
-(async () => {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('ABORT — halt flag already exists at startup. Remove it first.');
-    process.exit(1);
-  }
-  const state = { iterations: 0, ok: 0, fail: 0, settlementSkipped: 0, spentUsd: 0, startedAt: nowIso() };
-  logLine(`START night-builder · budget=$${BUDGET_CAP_USD} stop=${STOP_DEADLINE.toLocaleString()} cooldown=${COOLDOWN_MS}ms`);
-  logLine(`Track mix: 30% cactus / 70% drunk-animals-designer · ${MUTED_PALETTE.length} muted colorways`);
-
-  while (!shouldStop(state)) {
-    state.iterations++;
-    const track = nextTrack();
-    const cw = pickColorway();
-    const r = generateOne(track, cw);
-    state.spentUsd += PER_IMAGE_USD;
-    if (r.settlementBlock) {
-      state.settlementSkipped++;
-    } else if (r.ok) {
-      state.ok++;
-      logLine(`OK  #${state.iterations} ${track}/${cw.slug} id=${r.id} (cumul: ok=${state.ok} fail=${state.fail} spent=$${state.spentUsd.toFixed(2)})`);
-    } else {
-      state.fail++;
-      logLine(`FAIL #${state.iterations} ${track}/${cw.slug}: ${r.stderr || 'unknown'}`);
-      // brief backoff on failure (rate-limit or transient)
-      await new Promise(rs => setTimeout(rs, 15000));
-    }
-    await new Promise(rs => setTimeout(rs, COOLDOWN_MS));
-  }
-
-  logLine(`STOP iterations=${state.iterations} ok=${state.ok} fail=${state.fail} settlementSkip=${state.settlementSkipped} spent=$${state.spentUsd.toFixed(2)}`);
-  process.exit(0);
-})();
diff --git a/scripts/night-builder.js.bak-vendor-scrub2 b/scripts/night-builder.js.bak-vendor-scrub2
deleted file mode 100755
index 1d73b21..0000000
--- a/scripts/night-builder.js.bak-vendor-scrub2
+++ /dev/null
@@ -1,906 +0,0 @@
-#!/usr/bin/env node
-// All-night Replicate generator.
-//
-// Two tracks, interleaved:
-//   A) Muted Gucci-palette CACTUS variants (extends the existing 10 colorways)
-//   B) Drunk-animals → designer-elevated reinterpretation (Schumacher/Cole&Son
-//      register, muted Gucci palette, single-ink, archival gouache)
-//
-// Steve's brief: "continue creating patterns all night with the subdued hues
-// of grey, taupe, cream, ivory, gucci browns, recreate the drunk animals with
-// much more designer like designs."
-//
-// Hard rules:
-//   - Budget cap: $20 (≈2000 Replicate SDXL images at ~$0.01 ea)
-//   - Halt: touch /tmp/halt-night-builder.flag to stop after current image
-//   - Stop time: 07:30 local (Steve wakes ~8 AM)
-//   - Settlement gate inline (single-ink + no Part B elements = OK)
-//   - is_published=TRUE so Steve sees them in /designs immediately
-//   - Category: 'drunk-animals-designer' for the B-track (NOT 'drunk-animals')
-//     so Steve can sort + dedupe vs the kitsch originals
-//   - Cooldown: 5s between calls to be polite to Replicate
-//
-// Cost tracking: each Replicate image hits the cost-tracker via log.js
-// (already wired in generate_designs.js).
-
-const { spawnSync } = require('child_process');
-const fs = require('fs');
-const path = require('path');
-const { execSync } = require('child_process');
-
-const ROOT = path.join(__dirname, '..');
-const HALT_FLAG = '/tmp/halt-night-builder.flag';
-const LOG = '/tmp/night-builder.log';
-const STOP_HOUR = 7;   // local: stop at 07:30 next-morning
-const STOP_MIN  = 30;
-const BUDGET_CAP_USD = 20.0;
-const PER_IMAGE_USD = 0.011;   // Replicate SDXL ~$0.01 (incl. 10% safety margin)
-const COOLDOWN_MS = 5000;
-
-// Compute the actual next-7:30-AM deadline relative to script start.
-function computeStopDeadline() {
-  const now = new Date();
-  const d = new Date(now);
-  d.setHours(STOP_HOUR, STOP_MIN, 0, 0);
-  // If 7:30 today is already in the past, target 7:30 tomorrow.
-  if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
-  return d;
-}
-const STOP_DEADLINE = computeStopDeadline();
-
-const MUTED_PALETTE = [
-  { slug: 'smoke-ash',       name: 'Smoke Ash',       hex: '#6B6660', tone: 'warm-mid-grey' },
-  { slug: 'mushroom-taupe',  name: 'Mushroom Taupe',  hex: '#8A7E70', tone: 'designer-taupe' },
-  { slug: 'linen-cream',     name: 'Linen Cream',     hex: '#EDE4D3', tone: 'soft-cream' },
-  { slug: 'bone-ivory',      name: 'Bone Ivory',      hex: '#F2EADB', tone: 'warm-ivory' },
-  { slug: 'gucci-cocoa',     name: 'Gucci Cocoa',     hex: '#5C4033', tone: 'deep-cocoa' },
-  { slug: 'antique-tan',     name: 'Antique Tan',     hex: '#A78863', tone: 'warm-camel' },
-  { slug: 'stone-pewter',    name: 'Stone Pewter',    hex: '#7A736A', tone: 'cool-pewter' },
-  { slug: 'saddle-mocha',    name: 'Saddle Mocha',    hex: '#4A3528', tone: 'saddle-leather-brown' },
-  { slug: 'champagne-linen', name: 'Champagne Linen', hex: '#D7C9A8', tone: 'champagne-neutral' },
-  { slug: 'charcoal-smoke',  name: 'Charcoal Smoke',  hex: '#3D3833', tone: 'near-black-charcoal' },
-];
-
-const DB = 'dw_unified';
-const PSQL = `psql ${DB} -At -q`;
-function psql(sql) {
-  return execSync(PSQL, { input: sql, encoding: 'utf8' }).trim();
-}
-
-function nowIso() { return new Date().toISOString(); }
-function logLine(s) {
-  const line = `[${nowIso()}] ${s}`;
-  console.log(line);
-  fs.appendFileSync(LOG, line + '\n');
-}
-
-function pickColorway() {
-  return MUTED_PALETTE[Math.floor(Math.random() * MUTED_PALETTE.length)];
-}
-
-// ── Track A: cactus variants in muted palette ────────────────────────
-const CACTUS_PATTERNS = [
-  'tall saguaro forms with raised arms, scattered prickly-pear paddles',
-  'dense agave rosettes with whip-stem ocotillo verticals',
-  'barrel cactus rosettes and cholla branches in tight tessellation',
-  'sprawling prickly-pear paddle clusters as a vertical wallpaper repeat',
-  'ocotillo whip-stem fountain pattern with small barrel-cactus accents',
-];
-
-function composeCactusPrompt(cw) {
-  const pattern = CACTUS_PATTERNS[Math.floor(Math.random() * CACTUS_PATTERNS.length)];
-  return (
-    `Cactus Repeat — ${cw.name} colorway. ` +
-    `Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. ` +
-    `Sonoran desert cacti as FLAT STENCIL SILHOUETTES: ${pattern}, arranged in a tessellated seamless wallpaper repeat. ` +
-    `Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. ` +
-    `All cacti upright, oriented vertically, symmetric placement on the vertical axis. ` +
-    `Edge-to-edge tessellation, generous breathing room of clean ground between motifs. ` +
-    `Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track B: DEBAUCHED ANIMALS (rewritten 2026-05-20 per Steve directive) ──
-// References:
-//   1. Asterix drunk-Roman-legionnaire pose (Uderzo): staggering gait, leg
-//      kicking sideways, helmet askew, X-pupil eyes, wobble-line motion arc.
-//   2. Eadweard Muybridge "Animal Locomotion" sequential-motion plates: each
-//      animal captured at multiple frozen poses showing the full gait cycle,
-//      tiled side-by-side. The original "movement sneak" before cinema existed.
-// Tone: HEDONISTIC, DEBAUCHED, BACCHANALIAN. Cocktail party gone fully off
-// the rails. Animals are mid-stagger, mid-collapse, mid-tumble — never in
-// dignified repose.
-const ANIMAL_DESCRIPTORS = [
-  // STAGGERING / FALLING
-  'staggering bear mid-stumble with one paw airborne, Asterix-drunk-legionnaire pose',
-  'tumbling fox falling backward limbs splayed',
-  'reticulated giraffe slumped against an invisible wall, legs splayed Bambi-on-ice',
-  'red panda hanging upside down from a phantom branch, eyes spiraled',
-  'lemur mid-cartwheel, limbs flung in four directions, Uderzo motion arcs around it',
-  'sloth lying flat on its back, all four limbs in the air, classic dead-drunk pose',
-  'capybara mid-belly-laugh slumped sideways, hiccup bubble above',
-  'orangutan kicking one leg high in a Charleston-stagger, the other arm windmilling',
-  'raccoon in a top hat falling face-first toward the viewer',
-  'elephant calf doing a full pratfall on its back legs, trunk waving wildly',
-  'tree frog mid-leap and missing, arms grasping at empty air',
-  'Bornean orangutan blowing a kiss with one eye crossed, drink mid-spill',
-  'koala wrapped around a bottle larger than itself, swaying',
-  'wallaby boxer mid-haymaker against nothing, swinging at phantoms',
-  'tasmanian devil mid-spin, dust-cloud lines around it',
-  'fennec fox flopped on its side, tongue lolling out',
-  // DANCING / CABARET POSES
-  'flamingo in a high-kick cancan pose, feathers flying',
-  'okapi in a tango dip, partner is an invisible silhouette',
-  'pangolin doing a one-legged drunken pirouette, scales spread out',
-  'capybara in a top hat doing the Charleston',
-  'meerkat trio in a chorus-line kick',
-  'aardvark mid-Lindy-hop swing-out, partner missing',
-  'lemur ballroom-glide with one paw extended, the other holding a cocktail',
-  'red panda in disco pose, one arm up Saturday-Night-Fever style',
-  // PASSED OUT / OBLIVION
-  'walrus snoring with three Zs above it, X-pupils',
-  'turtle on its back legs in the air, hiccup bubble',
-  'manatee floating belly-up with bubbles around its mouth',
-  'wombat sprawled on a velvet ottoman, paws curled',
-  'lazy panther draped over a bar-stool silhouette',
-  'sleepy hedgehog curled inside an empty cocktail glass',
-  // MID-PRATFALL
-  'chimpanzee mid-banana-peel slip (peel falling, not held)',
-  'baboon doing a forward roll into a stack of empty glasses',
-  'mandrill mid-faceplant, legs in the air',
-  'gorilla doing a dignified slow-motion topple, one finger raised',
-  // INTOXICATED VIBE
-  'platypus floating in zero-gravity with a snifter, monocle askew',
-  'armadillo curled mid-tumble, motion-arc behind it',
-  'tapir wobbling on two legs holding a martini glass overhead',
-  'binturong (bearcat) dangling from a velvet rope by its tail, drink in mouth',
-  'ocelot lounging on a chaise with smoking pipe and one eye closed',
-  'civet stretched out on a Persian rug, paws to the ceiling',
-  // BACCHANALIAN HARVEST
-  'wild boar reclined against a barrel, snout sniffing the air',
-  'bison crowned with a wreath of curlicue smoke, head tipped back',
-  'ibex doing a goat-headstand on a stack of bottles',
-  'fallow deer mid-leap with hooves crossed, antlers tangled with ribbons',
-  // CLASSIC CARTOON-DRUNK
-  'cat with five-o\'clock-shadow holding a cocktail like Tom of Tom-and-Jerry',
-  'penguin in a bowtie tipping a top hat with one flipper',
-  'walrus playing a tiny piano with one tusk',
-  'badger in a smoking jacket striking a Sherlock Holmes pose',
-  'opossum hanging by its tail clutching a martini upside-down',
-  // SMOKE-CLOUD / VAPOR POSES
-  'fox blowing a perfect smoke-ring, eye squinted',
-  'rabbit reclining in a hookah-style pose, pipe in paw',
-  'mongoose with three smoke-curls rising from a pipe',
-  'lizard breathing out a single curling vapor-trail',
-  // CRYPTID DEBAUCHERY (real animals only, no fantasy)
-  'aye-aye gesturing wildly with a long finger raised',
-  'star-nosed mole sniffing a cocktail with thirteen tentacles',
-  'pangolin doing a slow Charleston in scaly tuxedo',
-  'kinkajou dangling from a chandelier with a coupe',
-  // MISC SURREAL POSES
-  'sloth doing a slow-motion crowd-surf on raised paws',
-  'porcupine quill-up flexing arms strongman pose',
-  'capybara DJ-ing on a turntable made of orange slices',
-  'badger conducting a phantom orchestra, baton in paw',
-];
-
-const DRINK_DESCRIPTORS = [
-  'half-empty long-stemmed coupe tilted at 45° with champagne sloshing out',
-  'cut-crystal whiskey tumbler frozen mid-toss, amber liquid arcing through the air',
-  'martini glass tipped over, two olives rolling away',
-  'bottle of pilsner held inverted, foam splashing onto the ground',
-  'red wine balloon mid-spill, dark liquid streaming sideways',
-  'frosted vodka decanter being chugged straight from the neck',
-  'tequila shot raised in toast, lime wedge between teeth',
-  'cognac snifter cradled like a baby',
-  'Aperol spritz in one hand, second drink in the other',
-  'magnum of champagne held aloft and shaken, cork popping with a comic burst',
-  'rolled joint trailing a wispy smoke-curl ribbon',
-  'oversize pipe with three smoke-curls forming a clover',
-  'cocktail with three umbrellas crammed into it',
-  'pint glass overflowing with foam pouring over the sides',
-];
-
-// Muybridge-style: sometimes show the animal in a sequence of 3 frozen motion
-// poses (instead of one), tiled side-by-side. Used 30% of the time.
-const MOTION_FORMATS = [
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
-  // 3 out of 10 → Muybridge sequence:
-  'Muybridge-style sequence of three frozen poses of the SAME animal mid-stagger, tiled side-by-side left-to-right showing the full stumble cycle',
-  'Muybridge-style sequence of three frozen poses of the SAME animal collapsing forward, tiled side-by-side left-to-right',
-  'Muybridge-style sequence of four frozen poses of the SAME animal mid-pratfall, tiled in a 2x2 grid',
-];
-
-const BOTANICAL_SETTINGS = [
-  'set among silhouetted Ghost Orchids',
-  'against a damask-trellis backdrop',
-  'framed by Botticelli-style arabesques',
-  'surrounded by hand-painted Magnolia branches',
-  'within a Schumacher-Audubon herbarium frame',
-  'set in a Cole&Son chinoiserie panel',
-  'flanked by ogival damask medallions',
-  'centered on a wunderkammer specimen plate',
-];
-
-function composeDrunkAnimalPrompt(cw) {
-  const animal    = ANIMAL_DESCRIPTORS[Math.floor(Math.random() * ANIMAL_DESCRIPTORS.length)];
-  const drink     = DRINK_DESCRIPTORS[Math.floor(Math.random() * DRINK_DESCRIPTORS.length)];
-  return (
-    `Cocktail-hour wallpaper — ${animal} cradling a ${drink}. ` +
-    `Colorway: ${cw.name} (${cw.hex}) ground with bone-ivory (#F2EADB) figure ink. ` +
-    `Each animal-and-drink vignette is one FLAT STENCIL SILHOUETTE in bone-ivory — no interior shading, no anatomical detail, no fur texture. The drink glass is part of the same silhouette, also flat ink. ` +
-    `Vignettes arranged in a symmetric tessellated repeat, generous breathing room of clean ground between them. ` +
-    `Strictly no botanical background, no foliage filler, no decorative arabesques behind the figures — the ground is one uniform flat color with nothing on it except the silhouette vignettes. ` +
-    `No bananas, no grapes, no birds, no butterflies. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── SHARED art direction — rewritten 2026-05-20 to ELIMINATE the bleed-through
-// layered-background problem. Anchor refs swapped from natural-history plates
-// (Haeckel/Merian — those DO have atmospheric depth + layered washes) to pure
-// 2-tone serigraph references (Warhol/Lichtenstein/Toulouse-Lautrec/WPA poster
-// /Saul Bass/pochoir stencil — those are GUARANTEED flat 2-tone with NO depth).
-// Rewritten 2026-05-20 — Steve directive: "way too busy for most designs..
-// keep it simple". Old SHARED_ARTDIR encouraged tessellated edge-to-edge
-// filling. New version forces SPARSE motifs + GENEROUS NEGATIVE SPACE.
-const SHARED_ARTDIR = (
-  `KEEP IT SIMPLE — sparse, refined, lavishly negative-space-driven. At LEAST 75-85% of the tile area is solid clean ground tone with NOTHING in it (often 85%+, like Zuber and Maya Romanoff archives). Motifs are FEW (2-4 per tile maximum), well-spaced, never edge-to-edge, never tessellated, never cluttered. The empty ground IS the design — motifs are punctuation, not pattern-fill. ` +
-  `Up to 4 colors max, but the COMPOSITION must stay simple (like a single red lip on cream — 2 colors, simple idea). Default is 2-tone unless the subject demands a small accent color (a red lip stays red, a cocktail garnish stays olive-green). Never more than 4 distinct ink tones. ` +
-  `ABSOLUTELY NO bleed between layers — bleed looks cheap and is FORBIDDEN. Single-layer screen-print only. ` +
-  `NO ghost layer, NO faded background, NO atmospheric depth, NO secondary motifs behind the main figures. The ground is one completely uniform flat solid color from edge to edge. ` +
-  `Reference aesthetic: high-end designer wallcovering register, anchored to these brand archives — Zuber & Cie (hand-blocked scenic), Designers Guild (refined modern romantic), Thibaut (archival classic), Arte International (natural-fiber luxe), Romo (painterly British), Maya Romanoff (handcrafted luxe surface). Subtle. Sparse. Refined. Negative-space-driven. Single-pass silkscreen with two ink screens. ` +
-  `Hard clean razor-sharp edges. No half-tones, no gradient, no watercolor, no crosshatch shading. Every figure region is one flat color. ` +
-  `Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar. ` +
-  `Square aspect ratio, seamless tile repeat (edges meet cleanly), flat-paper rendering, no watermark, no signature, no text, no logos.`
-);
-
-// Universal negative prompt — passed to Replicate as --negative-prompt. Catches
-// the model's instinct to add depth/layers even when text anti-prompts fail.
-const SHARED_NEGATIVE = (
-  `gradient, ombre, fade, faded, ghosted, ghost layer, atmospheric perspective, depth, layered background, ` +
-  `secondary motifs behind, lighter background copies, halftone, half-tone, chromatic gradient, watercolor wash, ` +
-  `airbrush, soft edges, blurry edges, anti-aliased blur, crosshatching, engraving lines, stippling, ` +
-  `three colors, four colors, multi-color, textured ground, mottled background, shadow, drop shadow, ` +
-  `Haeckel, Merian, Audubon, natural-history plate, botanical plate, engraving, cartoon, Pixar, 3D render`
-);
-
-const CHINOISERIE_MOTIFS = [
-  'pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches',
-  'kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above',
-  'lotus blossom roundels in vertical stacks separated by bamboo verticals',
-  'mountain-and-temple silhouette repeats with stylized scholar-cloud forms',
-];
-function composeChinoiseriePrompt(cw) {
-  const motif = CHINOISERIE_MOTIFS[Math.floor(Math.random() * CHINOISERIE_MOTIFS.length)];
-  return (
-    `Chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Motif: ${motif}, arranged in a tessellated seamless repeat. ` +
-    `Cole & Son Mandarin / Ming-dynasty woodblock register. ` + SHARED_ARTDIR
-  );
-}
-
-const TOILE_VIGNETTES = [
-  'a pair of mirror-image country-cottage scenes flanking a central fountain',
-  'symmetric pastoral courtship scene under arching elm-tree silhouettes',
-  'paired equestrian-rider vignettes facing each other across a ribbon-tied bouquet',
-  'symmetric harvest-basket scene with sheaves of wheat and hand-tools',
-];
-function composeToilePrompt(cw) {
-  const vig = TOILE_VIGNETTES[Math.floor(Math.random() * TOILE_VIGNETTES.length)];
-  return (
-    `Toile de Jouy wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-engraving. ` +
-    `Vignette: ${vig}, repeated as a seamless symmetric tile, every figure rendered in fine-line copperplate-engraving style with cross-hatched shading replaced by solid-flat figure tone. ` +
-    `Pierre-Frey / Manuel Canovas archival toile register. ` + SHARED_ARTDIR
-  );
-}
-
-const STRIPE_STYLES = [
-  'wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges',
-  'awning stripe — uniform 6cm vertical bands alternating ground and figure tones',
-  'beaded vertical lines with small ornamental dot-clusters between stripes',
-  'fine triple-pinstripe groups separated by wider blank channels',
-];
-function composeStripePrompt(cw) {
-  const style = STRIPE_STYLES[Math.floor(Math.random() * STRIPE_STYLES.length)];
-  return (
-    `Vertical stripe wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure stripes: bone-ivory (#F2EADB) single-ink. ` +
-    `Pattern: ${style}. ` +
-    `Robert Kime / Colefax & Fowler archival stripe register. ` + SHARED_ARTDIR
-  );
-}
-
-const QUATREFOIL_FIELDS = [
-  'classical quatrefoil tessellation, each quatrefoil oriented identically, edge-to-edge tight grid',
-  'gothic-window quatrefoil-plus-circle interlock, symmetric 4-fold',
-  'moorish-tile quatrefoil with negative-space cross channels',
-  'ogee-quatrefoil hybrid — pointed-arch ogee crossed with classical quatrefoil',
-];
-function composeQuatrefoilPrompt(cw) {
-  const field = QUATREFOIL_FIELDS[Math.floor(Math.random() * QUATREFOIL_FIELDS.length)];
-  return (
-    `Quatrefoil / ogee geometric wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Field: ${field}. ` +
-    `Owen Jones Grammar of Ornament register, strict mathematical tessellation. ` + SHARED_ARTDIR
-  );
-}
-
-const DIAMOND_TRELLIS_FIELDS = [
-  'narrow ogival diamond lattice with empty centers (just the lattice lines)',
-  'diamond trellis with a single tiny center-medallion in each diamond',
-  'crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections',
-  'fine-line diamond lattice with classical tassels at each lattice node',
-];
-function composeDiamondTrellisPrompt(cw) {
-  const field = DIAMOND_TRELLIS_FIELDS[Math.floor(Math.random() * DIAMOND_TRELLIS_FIELDS.length)];
-  return (
-    `Diamond trellis wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-trellis. ` +
-    `Field: ${field}. NOT a damask, NOT a heavy medallion field — light open trellis only. ` +
-    `Soane Britain / De Gournay archival trellis register. ` + SHARED_ARTDIR
-  );
-}
-
-// ── Track H: DRUNK ZOO 36" (Steve-approved 2026-05-20) ──────────────
-// Replaces the giraffe-only track with a broader symmetric-zoo track.
-// 36" repeat — wall-scale large pattern. Each design = one symmetric
-// paired-animal vignette in 19c-engraving aesthetic.
-const ZOO_VIGNETTES = [
-  'two reticulated giraffes facing each other in symmetric profile, necks crossing in a heart-shape, both sipping champagne coupes',
-  'pair of mirror-image giraffes flanking a central acacia tree, both holding cocktail glasses',
-  'mother-and-calf giraffe pair, mother arching over the calf, both with bottles',
-  'pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved',
-  'two sloths hanging in mirror-image from a central acacia branch, each with a cocktail glass',
-  'pair of red pandas in symmetric grooming pose, central bottle between them',
-  'two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern',
-  'pair of capybaras in symmetric resting pose, central mint julep glass between them',
-  'two African elephant calves in symmetric trunk-raised pose, central wine glass',
-  'pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them',
-  'two tipsy frogs in symmetric leaping pose mirrored, central decanter',
-];
-function composeDrunkZoo36Prompt(cw) {
-  const vig = ZOO_VIGNETTES[Math.floor(Math.random() * ZOO_VIGNETTES.length)];
-  return (
-    `Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading, no anatomical line-detail, no spots, no stripes, no fur texture. Spots/stripes appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Track I: FACE-SKULL DAMASK (Steve directive 2026-05-20) ──────────
-// Damask is purged unless it has faces or skulls. This track generates new
-// damask designs where the medallion FILL is a hand-engraved face or skull
-// rendered in single-ink screen-print style.
-const FACE_SKULL_MOTIFS = [
-  'memento-mori skull at the center of each ogival damask medallion, crossed bones forming the lattice spines',
-  'classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders',
-  'death\'s-head moth skull pattern at each damask cell, wings forming the connecting flourishes',
-  'Venetian carnival mask face filling each damask medallion, ribbon-ties forming the lattice',
-  'Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds',
-  'Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion',
-  'anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink',
-  'phantom-of-the-opera half-mask face at each medallion, opera-cape ribbons forming the lattice',
-];
-function composeFaceSkullDamaskPrompt(cw) {
-  const motif = FACE_SKULL_MOTIFS[Math.floor(Math.random() * FACE_SKULL_MOTIFS.length)];
-  return (
-    `Face-Skull Damask — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Composition: classic ogival damask lattice, but the medallion FILL is ${motif}. ` +
-    `Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. ` +
-    `Each face/skull/lattice element is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior crosshatching, NO engraved line shading, NO 3D modeling of the skull/face. Pure cut-out silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── Settlement check (inline — same logic as src/fliepaper-bugs.js) ──
-function settlementOK(prompt) {
-  let p = String(prompt || '').toLowerCase();
-  // Strip negatives so anti-prompts don't false-trigger
-  p = p
-    .replace(/\b(?:no|not|without|excluding|never|avoid)\b[^.,;]*(?:banana|bird|butterfl|grape|leaf|leaves|frond|palm|foliage|tropical|fluoresc|neon|rainbow)[^.,;]*[.,;]?/g, ' ')
-    .replace(/\bwithout\b[^.,;]*[.,;]/g, ' ')
-    .replace(/\bexcluding\b[^.,;]*[.,;]/g, ' ');
-  // Part B specific elements (affirmative only)
-  const partB = [
-    /\bbanana(?:s|\s+pods?)?\b/,
-    /\bplantains?\b/,
-    /\bgrapes?\b/,
-    /\bbirds?\b/,
-    /\bbutterfl(?:y|ies)\b/,
-  ];
-  for (const rx of partB) if (rx.test(p)) return false;
-  // Foliage = needs review (we still allow, the figure is single-ink)
-  return true;
-}
-
-// ── Track mix — Steve flagged "needs alot more variety in patterns, way
-// too much damasks" 2026-05-20. Six tracks, equal weight:
-//   cactus            — desert botanical (Sonoran luxury)
-//   chinoiserie       — Cole&Son Mandarin/Ming-style
-//   toile-de-jouy     — classic French pastoral one-color
-//   stripe-block      — vertical stripe / pinstripe / awning stripe
-//   quatrefoil-ogee   — geometric tessellation (non-damask)
-//   diamond-trellis   — narrow ogival lattice (lighter than damask)
-// drunk-animals-designer paused — see feedback_no_modern_bending_designs.
-// 2026-05-20 — top-selling interior-designer styles, ALL at 36" repeat:
-//   chinoiserie       — Cole & Son / de Gournay perennial best-seller
-//   stripe            — Colefax & Fowler ticking stripe (designer classic)
-//   cactus            — Steve's house collection (Sonoran luxury)
-//   drunk-zoo-36      — symmetric paired animals (giraffes lead, sloths/lemurs/
-//                       panda/capybara/elephant added), 19c-engraving register
-// 2026-05-20 Steve directives:
-//  - Add designer-scenic (emulate Fromental.com elegance, NOT copy)
-//  - Add throttled-plaster-roses ("throwled plaster look with roses")
-//  - Thibaut is the high-end LLM crisp reference (NEVER York/Brewster/Marburg)
-const DESIGNER_SCENES = [
-  'flowering magnolia branches reaching up the panel with peony blossoms at branch nodes',
-  'cascading wisteria vines hanging in symmetric clusters with stylized vine leaves',
-  'cherry-blossom branches arching across the panel with falling petal motifs',
-  'climbing rose vines on bamboo-trellis armature with full open rose heads at intersections',
-  'flowering plum branches in winter-Asian-painting register with sparse blossoms',
-];
-function composeDesignerScenicPrompt(cw) {
-  const scene = DESIGNER_SCENES[Math.floor(Math.random() * DESIGNER_SCENES.length)];
-  return (
-    `Hand-painted scenic chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) silk register. Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Scene: ${scene}, arranged as a seamless symmetric tile — each branch repeats with bilateral mirror symmetry. ` +
-    `Thibaut-grade archival register, hand-painted silk-panel aesthetic in the spirit of de Gournay or Fromental atelier work (emulate the elegance, do not copy any specific design). ` +
-    `Strictly no birds, no insects, no human figures. Branches and flowers only. ` + SHARED_ARTDIR
-  );
-}
-
-const PLASTER_ROSE_STYLES = [
-  'full-open garden roses in symmetric pairs with their stems crossing at the panel center',
-  'climbing-rose vine in vertical repeats with rose heads at every node and serrated leaves',
-  'damask-style rose medallions — single large rose head encircled by a wreath of smaller buds',
-  'rose-and-thorn-branch tessellation, each rose head identical, edge-to-edge tight grid',
-];
-function composeThrottledPlasterRosesPrompt(cw) {
-  const style = PLASTER_ROSE_STYLES[Math.floor(Math.random() * PLASTER_ROSE_STYLES.length)];
-  return (
-    `Trowelled-plaster-wall wallpaper with roses — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. ` +
-    `Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. ` +
-    `Rose style: ${style}. ` +
-    `Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. ` + SHARED_ARTDIR
-  );
-}
-
-// ── MUYBRIDGE — sequential motion-study plates as wallpaper ──────────
-// Eadweard Muybridge "Animal Locomotion" (1887, public domain) — ~780 plates,
-// each one a tiled grid of frozen gait poses of a single subject. The plate
-// FORMAT is already a perfect wallpaper repeat: 12-24 small images tiled in
-// a grid showing the full motion cycle. Steve directive 2026-05-20.
-const MUYBRIDGE_SUBJECTS = [
-  'horse galloping — sequence of 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full gait cycle, tiled left-to-right in three rows of four',
-  'horse trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule tiled in a 4x4 grid',
-  'racehorse with jockey — 12 frozen pose sequence showing the full stride',
-  'leaping greyhound — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule, dog mid-leap, tiled side-by-side',
-  'galloping dog — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule in a 4x4 grid',
-  'mastiff trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in 3 rows',
-  'cat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule showing the saunter, tiled 4x4',
-  'cat leaping from rock to rock — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'lion pacing — 12 frozen pose sequence tiled in a strip',
-  'tiger walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'elephant walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the heavy gait, tiled 3x4',
-  'ostrich running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'kangaroo hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound, tiled 3x4',
-  'pig trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 4x3 grid',
-  'goat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'donkey trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'camel walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the long stride, tiled 3x4',
-  'rabbit hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound',
-  'deer leaping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a strip',
-  'bison charging — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'rooster strutting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'cockatoo flapping its wings — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'pigeon taking flight — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full wingbeat',
-  'human walking — Muybridge male figure in a loincloth, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human descending stairs — woman in a Victorian dress, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled left-to-right',
-  'human running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human jumping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full leap',
-  'human throwing a discus — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled 3x4',
-  'human ascending stairs — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule left-to-right',
-  'human carrying a heavy bucket — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the strained walk',
-  'human dancing a waltz — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
-  'human waving a flag — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
-  'human boxing — two figures in 12 frozen pose sequence, tiled 3x4',
-  'human wrestling — two figures locked together in 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
-  'baby crawling — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a 3x4 grid',
-  'human hammering a nail — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full swing',
-];
-function composeMuybridgePlatePrompt(cw) {
-  const subj = MUYBRIDGE_SUBJECTS[Math.floor(Math.random() * MUYBRIDGE_SUBJECTS.length)];
-  return (
-    `Eadweard Muybridge motion-study plate as wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Subject: ${subj}. ` +
-    `Each pose is a flat stencil silhouette in single ink on the solid ground — no chiaroscuro, no shading, no anatomical detail inside. ` +
-    `Render as a SEAMLESS REPEAT — the full plate is one tile, edge-to-edge. ` +
-    `Reference: Muybridge "Animal Locomotion" 1887, plate-format, single-color blueprint-style serigraph treatment. NOT photographic, NOT engraved — flat stencil only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── LIPS — Steve directive 2026-05-20. 4-color max, sparse iconic lips on
-// generous ground. Inspired by Warhol Marilyn, Lichtenstein, Patrick Nagel,
-// matchbook covers.
-const LIPS_SUBJECTS = [
-  'one large parted red-velvet lips silhouette centered on the tile, no face, no teeth — just the lips. 80% empty cream ground around it',
-  'a single perfect lipstick-kiss imprint (blot mark) on the upper-third of the tile in scarlet, otherwise empty cream ground',
-  'two lips facing each other almost touching (mid-kiss silhouette), one in deep red and one in cream-outline, lots of empty ground around them',
-  'pursed pouting lips in profile silhouette, scarlet red, no other elements, vast clean ground',
-  'lips biting a single stem of a tiny rose — minimalist Patrick-Nagel silhouette, two-tone (red lips, black rose silhouette, cream ground)',
-  'classic Andy Warhol style pop-art lips — one giant pair of half-open red lips with a thin black outline, 4 colors max (red, black, cream, optional cream highlight on lower lip)',
-];
-
-function composeLipsPrompt(subject, cw) {
-  // For lips: ground=cream/ivory, figure=scarlet red. Override colorway.
-  return (
-    `Lips wallpaper — minimalist pop-art register. ` +
-    `Ground: solid cream-ivory (#F2EADB). Figure: deep scarlet red (#B0282A). ` +
-    `Subject: ${subject}. ` +
-    `Up to 4 colors maximum (cream + scarlet + optional black outline + optional cream highlight on lower lip). NO bleed. NO halftone. NO gradient. ` +
-    `Reference: Andy Warhol pop-art lips, Patrick Nagel, vintage matchbook cover, single-pass screen-print. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// ── MONTEREY WEST-COAST MURAL — Steve directive 2026-05-20.
-// 6 scene subjects × 4 seasonal colorways = 24 base designs. Each is a single
-// 36"-wide × 132"-tall (11ft) panel intended to be the FIRST in a multi-panel
-// mural set. Admin can later "+next panel" to extend the scene rightward
-// across the wall (via /api/design/:id/add-next-panel).
-const MONTEREY_SUBJECTS = [
-  'Lone Cypress on Pebble Beach 17-Mile Drive — a single windswept Monterey cypress silhouetted on a granite headland with the Pacific behind, hand-painted scenic register, de Gournay aesthetic',
-  'Cannery Row at low tide — silhouettes of the historic canneries on pilings over kelp-strewn rocks, fog ribbons in the upper third',
-  'Point Pinos Lighthouse at dusk — the oldest continuously-operating lighthouse on the West Coast with cypress hedge and bluff in foreground',
-  'Carmel Mission Basilica — adobe bell tower silhouette with rose-garden hedges and ancient olive trees',
-  'Big Sur Bixby Creek Bridge silhouette — arched span over the canyon mouth with the Pacific opening to the west, redwoods on the inland slope',
-  'Monterey Bay kelp-forest above-and-below cross-section — sea-otters on the surface, kelp fronds rising from the seabed, single-layer silhouette only',
-];
-
-const SEASONAL_COLORWAYS = [
-  { season: 'spring', name: 'Spring Bloom',     ground: '#A8B79A', figure: '#F2EADB', tone: 'sage-mint' },
-  { season: 'summer', name: 'Summer Surf',      ground: '#3E6B82', figure: '#F2EADB', tone: 'bay-blue' },
-  { season: 'fall',   name: 'Autumn Saddle',    ground: '#7A4A28', figure: '#F2EADB', tone: 'cypress-burnt-umber' },
-  { season: 'winter', name: 'Winter Fog',       ground: '#9CA09B', figure: '#3A2A1C', tone: 'pacific-mist' },
-];
-
-function composeMontereyMuralPrompt(subject, cw) {
-  return (
-    `Monterey, California west-coast mural panel — ${subject}. ` +
-    `${cw.season.charAt(0).toUpperCase() + cw.season.slice(1)} colorway: ${cw.name}. Ground: solid ${cw.tone} (${cw.ground}). Figure: ${cw.figure} single-ink. ` +
-    `Composition: panoramic vertical panel, motif anchored center-bottom, sky/atmosphere extending up the panel (still in solid ground tone, NOT a sky gradient), generous breathing room. ` +
-    `This is a FULL-ROOM MURAL — width = full wall, height = full ceiling. Compose for a 12'-wide × 9'-tall wall. The customer will choose 24"/36"/54" print-panel widths at order time — generate at room scale, not panel scale. ` +
-    `4-COLOR PALETTE WITH HUE SYNC — all four colors share the same hue family (no warm/cool mixing). Ground + figure as the two main tones; one HIGHLIGHT color for accents (lips, eyes, fingernails, fruit, small jewels); one SHADOW color (under figures, in eye sockets, beneath leaves). Highlight + shadow stay in the same hue family as ground+figure. ` +
-    `Reference: de Gournay scenic panel, hand-painted silk register, NOT Haeckel, NOT Audubon. Single-layer stencil silhouette only. ` +
-    SHARED_ARTDIR
-  );
-}
-
-// Tall panel kind — 36" wide × 132" tall (11ft). Different from the seamless_tile
-// kind which is square. Panel kind is meant to hang as-is, not tile-repeat.
-// 2026-05-20 Steve directive: murals are FULL ROOM dimensions, not panels.
-// Customer chooses 24"/36"/54" panel width at print time — generation is room-
-// scale. Default room: 144" wide (12 ft) × 108" tall (9 ft ceiling) @ 150 DPI.
-// SDXL renders at 1536×1152 (4:3 max) — at-print upscale to 21600×16200
-// happens at order time via Topaz/Real-ESRGAN.
-const PANEL_W = 144;        // inches (metadata)
-const PANEL_H = 108;        // inches (metadata)
-const PANEL_RENDER_W = 1536; // pixels (SDXL output)
-const PANEL_RENDER_H = 1152; // pixels (SDXL output)
-
-// 2026-05-20 Steve directive: "up to 4 colors using for highlights like
-// lips.. eyes.. fingernails.. accents in another color and shadows. always
-// make sure hue is in sync." Murals get 4 colors with hue-sync constraint.
-// Tile tracks (cactus/chinoiserie/etc.) stay 2-color.
-const HUE_SYNC_QUARTETS = [
-  // Each quartet is [ground, figure, highlight, shadow] within the same hue
-  // family. The hue-sync constraint = all 4 colors share the same dominant
-  // hue ± neutrals.
-  // WARM EARTH (cream/saddle/rose/cocoa)
-  { name: 'Saddle Rose Warmth', ground: '#F2EADB', figure: '#5C2329', highlight: '#B0282A', shadow: '#2A1010' },
-  // FOREST GREEN (Steve's 10492-loved family)
-  { name: 'Forest Loden Quartet', ground: '#3C4844', figure: '#E8E2D1', highlight: '#A8B79A', shadow: '#1A2520' },
-  // INK + GOLD (Steve's high-end Thibaut energy)
-  { name: 'Ink Library', ground: '#1F2740', figure: '#E8DDB8', highlight: '#C9A14B', shadow: '#0B0F1C' },
-  // BORDEAUX
-  { name: 'Bordeaux Velvet', ground: '#5C2329', figure: '#F2EADB', highlight: '#C84A3A', shadow: '#2A0E10' },
-  // OXBLOOD
-  { name: 'Oxblood', ground: '#722F2F', figure: '#F2EADB', highlight: '#A85040', shadow: '#3A1010' },
-  // STONE PEWTER
-  { name: 'Stone Pewter', ground: '#8A857B', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#3A3530' },
-  // MOSS SLATE
-  { name: 'Moss Slate', ground: '#566554', figure: '#F2EADB', highlight: '#A8B79A', shadow: '#1A2520' },
-  // CHARCOAL + CREAM
-  { name: 'Charcoal Cream', ground: '#3D3833', figure: '#F2EADB', highlight: '#C9A14B', shadow: '#0F0D0B' },
-];
-function pickHueSyncQuartet() {
-  return HUE_SYNC_QUARTETS[Math.floor(Math.random() * HUE_SYNC_QUARTETS.length)];
-}
-
-// ── DESIGNER-ZOO-CALM — Steve directive 2026-05-20 ("love this theme")
-// Inspired by accepted design 10492. Forest-green ground + bone-ivory animal
-// silhouettes, dignified standing/sitting poses (not staggering), sparse
-// fern/grass botanical fillers between animals, Cole-Son/Sister-Parish energy.
-const ZOO_CALM_ANIMALS = [
-  'pair of red pandas in symmetric grooming pose',
-  'bear seated upright in a meditative pose, head slightly tilted',
-  'a tall standing fox in profile, ears alert',
-  'pair of foxes mirrored facing each other, tails curving outward',
-  'family of three lemurs perched in a vertical stack',
-  'pair of standing wolves facing forward, dignified',
-  'mountain lion seated in regal posture, tail curled',
-  'pair of badgers facing each other in symmetric pose',
-  'standing pangolin in profile, scales catching highlight as cut-outs',
-  'pair of capybaras seated side-by-side',
-  'tall standing crane with neck curved (no flying — standing pose only)',
-  'pair of forest deer with antlers, mirrored facing inward',
-  'standing fennec fox with oversized ears, alert pose',
-  'pair of stoats standing upright, paws raised',
-  'standing armadillo in profile, shell silhouette clean',
-  'lounging snow leopard with paws extended',
-];
-const ZOO_CALM_FILLERS = [
-  'sparse fern fronds rising between the animals',
-  'tall slender grass clusters in negative space between motifs',
-  'small wildflower clusters between motifs',
-  'pinecone-and-needle sprigs',
-  'wild-rose stems with single open blossoms',
-  'tall cattails between figures',
-  'sparse oak-leaf clusters in negative space',
-  'olive branches between motifs',
-];
-const ZOO_CALM_PALETTE = [
-  { name: 'Forest Loden', tone: 'deep forest green', hex: '#3C4844' },
-  { name: 'Hunter Olive', tone: 'hunter olive', hex: '#48543A' },
-  { name: 'Bottle Green', tone: 'bottle-green', hex: '#2E4538' },
-  { name: 'Pine Charcoal', tone: 'pine charcoal', hex: '#3A4A45' },
-  { name: 'Moss Slate', tone: 'moss slate', hex: '#566554' },
-  { name: 'Ink Navy', tone: 'ink navy', hex: '#1F2B3A' },
-];
-function composeDesignerZooCalmPrompt() {
-  const animal = ZOO_CALM_ANIMALS[Math.floor(Math.random() * ZOO_CALM_ANIMALS.length)];
-  const filler = ZOO_CALM_FILLERS[Math.floor(Math.random() * ZOO_CALM_FILLERS.length)];
-  const cw = ZOO_CALM_PALETTE[Math.floor(Math.random() * ZOO_CALM_PALETTE.length)];
-  return (
-    `Designer Wallcoverings zoo wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
-    `Vignette: ${animal}. Renders at wall-scale 36-inch seamless tile. Animals are LARGE, anchored on the central vertical axis of symmetry, dignified standing or seated pose (NOT staggering, NOT falling, NOT mid-motion). ` +
-    `Between the animals: ${filler}, kept sparse — generous breathing room of clean ground tone visible throughout. ` +
-    `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior shading, NO anatomical detail, NO fur texture. Spots/stripes/markings appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
-    `Reference: Cole & Son archival, Sister Parish, Designer Wallcoverings house style. Refined, restrained, designer-grade. ` +
-    SHARED_ARTDIR
-  );
-}
-
-const TRACKS = [
-  // Weighted — designer-zoo-calm appears MORE often per Steve "do more" of 10492
-  'designer-zoo-calm','designer-zoo-calm','designer-zoo-calm','designer-zoo-calm',
-  'chinoiserie','stripe','cactus','drunk-zoo-36','face-skull-damask',
-  'designer-scenic','throttled-plaster-roses','muybridge-plate','monterey-mural',
-];
-
-// Variable repeat size per Steve directive 2026-05-20: vary 24"/36"/54" so
-// designs render at different motif scales (small/medium/large wall coverage).
-const REPEAT_SIZES = [24, 36, 36, 36, 54];
-function pickRepeatSize() {
-  return REPEAT_SIZES[Math.floor(Math.random() * REPEAT_SIZES.length)];
-}
-const REPEAT_INCHES = 36;   // wall-scale repeat — Steve directive 2026-05-20
-function nextTrack() {
-  return TRACKS[Math.floor(Math.random() * TRACKS.length)];
-}
-
-// ── Stop conditions ──────────────────────────────────────────────────
-function shouldStop(state) {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('HALT — flag file present at ' + HALT_FLAG);
-    return true;
-  }
-  if (state.spentUsd >= BUDGET_CAP_USD) {
-    logLine(`HALT — budget cap reached ($${state.spentUsd.toFixed(2)} >= $${BUDGET_CAP_USD})`);
-    return true;
-  }
-  if (Date.now() >= STOP_DEADLINE.getTime()) {
-    logLine(`HALT — stop deadline reached (${new Date().toLocaleString()} past ${STOP_DEADLINE.toLocaleString()})`);
-    return true;
-  }
-  return false;
-}
-
-// ── Generate one design ──────────────────────────────────────────────
-function generateOne(track, cw) {
-  let prompt;
-  switch (track) {
-    case 'cactus':          prompt = composeCactusPrompt(cw); break;
-    case 'chinoiserie':     prompt = composeChinoiseriePrompt(cw); break;
-    case 'toile':           prompt = composeToilePrompt(cw); break;
-    case 'stripe':          prompt = composeStripePrompt(cw); break;
-    case 'quatrefoil':      prompt = composeQuatrefoilPrompt(cw); break;
-    case 'diamond-trellis': prompt = composeDiamondTrellisPrompt(cw); break;
-    case 'drunk-zoo-36':    prompt = composeDrunkZoo36Prompt(cw); break;
-    case 'face-skull-damask': prompt = composeFaceSkullDamaskPrompt(cw); break;
-    case 'designer-scenic':  prompt = composeDesignerScenicPrompt(cw); break;
-    case 'throttled-plaster-roses': prompt = composeThrottledPlasterRosesPrompt(cw); break;
-    case 'muybridge-plate':   prompt = composeMuybridgePlatePrompt(cw); break;
-    case 'designer-zoo-calm': prompt = composeDesignerZooCalmPrompt(); break;
-    case 'monterey-mural':    {
-      const subj = MONTEREY_SUBJECTS[Math.floor(Math.random() * MONTEREY_SUBJECTS.length)];
-      const season = SEASONAL_COLORWAYS[Math.floor(Math.random() * SEASONAL_COLORWAYS.length)];
-      // Override the colorway with the seasonal one for this track
-      cw = { name: season.name, hex: season.ground, tone: season.tone, slug: season.season };
-      prompt = composeMontereyMuralPrompt(subj, season);
-      break;
-    }
-    default:                prompt = composeCactusPrompt(cw);
-  }
-  if (!settlementOK(prompt)) {
-    logLine(`SKIP settlement — track=${track} cw=${cw.slug}`);
-    return { ok: false, settlementBlock: true };
-  }
-  // Each track inserts into its own category for sortability + variety.
-  const category = track === 'cactus'          ? 'cactus'
-                 : track === 'chinoiserie'     ? 'chinoiserie'
-                 : track === 'toile'           ? 'toile-de-jouy'
-                 : track === 'stripe'          ? 'stripe'
-                 : track === 'quatrefoil'      ? 'quatrefoil-ogee'
-                 : track === 'diamond-trellis' ? 'diamond-trellis'
-                 : track === 'drunk-zoo-36'    ? 'drunk-zoo-36'
-                 : track === 'face-skull-damask' ? 'face-skull-damask'
-                 : track === 'designer-scenic' ? 'designer-scenic'
-                 : track === 'throttled-plaster-roses' ? 'throttled-plaster-roses'
-                 : track === 'muybridge-plate' ? 'muybridge-plate'
-                 : track === 'monterey-mural' ? 'monterey-mural'
-                 : track === 'designer-zoo-calm' ? 'designer-zoo-calm'
-                 : 'drunk-animals-designer';
-  // Mural panels render at 36"×132" (11ft); everything else at square tiles
-  // with variable size per Steve 2026-05-20 (24/36/54).
-  const isPanel = (track === 'monterey-mural');
-  const w = isPanel ? PANEL_W : pickRepeatSize();
-  const h = isPanel ? PANEL_H : w;
-  const kind = isPanel ? 'mural_panel' : 'seamless_tile';
-  const r = spawnSync('node', [
-    path.join(__dirname, 'generate_designs.js'),
-    '--n', '1',
-    '--kind', kind,
-    '--width', String(w),
-    '--height', String(h),
-    '--category', category,
-    '--prompts', prompt,
-  ], { cwd: ROOT, encoding: 'utf8', env: process.env });
-
-  if (r.status === 0) {
-    // generate_designs.js prints "  ✓ #<id> ·" per-design and "IDs: <id>,..." summary
-    const summary = (r.stdout || '').match(/IDs:\s*(\d+(?:,\s*\d+)*)/);
-    const perLine = (r.stdout || '').match(/✓\s*#(\d+)/);
-    const idStr = summary ? summary[1].split(/,\s*/)[0] : (perLine ? perLine[1] : null);
-    const id = idStr ? parseInt(idStr, 10) : null;
-    // POST-PROCESS: force 2-tone quantize on the output PNG. Eliminates bleed
-    // between layers (Steve directive 2026-05-20 — looks cheap). The script
-    // backs up the original to <file>.original.png before overwriting.
-    if (id) {
-      try {
-        // psqlAt returns tuples-only, no header/footer — clean single-value reads
-        const lp = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=${id};"`, { encoding: 'utf8' }).trim();
-        if (lp && fs.existsSync(lp)) {
-          const q = spawnSync('python3', [path.join(__dirname, 'quantize-2tone.py'), lp], { encoding: 'utf8' });
-          if (q.status === 0) {
-            logLine(`  2tone OK id=${id}: ${(q.stdout || '').trim().split('->')[1] || '?'}`);
-            // COLORWAY-VARIANTS — for every 2-toned design, generate 3 free
-            // recolors. Steve directive 2026-05-20. Each variant becomes a
-            // child row in spoon_all_designs with parent_design_id=id.
-            // Skip for mural panels (they're meant to be a multi-panel set,
-            // colorway variation happens at the source-prompt level there).
-            if (!isPanel) {
-              try {
-                const cwOut = spawnSync('python3', [path.join(__dirname, 'colorway-variants.py'), lp], { encoding: 'utf8' });
-                if (cwOut.status === 0) {
-                  const parsed = JSON.parse((cwOut.stdout || '').trim().split('\n').pop() || '{}');
-                  const variants = parsed.variants || [];
-                  for (const v of variants) {
-                    if (!fs.existsSync(v.path)) continue;
-                    const cwTitle = `${category} · ${v.slug}`;
-                    // Two-step fix for the "PLACEHOLDER image_url" bug — see
-                    // git log + the bulk PG UPDATE on 2026-05-20. The earlier
-                    // single-statement CTE (INSERT + UPDATE in one) failed
-                    // because PG's data-modifying CTEs share a snapshot, so the
-                    // UPDATE didn't see the just-inserted row and the URL stayed
-                    // PLACEHOLDER. Two separate psql calls — INSERT...RETURNING
-                    // captures id, then a follow-up UPDATE writes the real URL.
-                    const insertSql = `
-                      INSERT INTO spoon_all_designs (brand, kind, width_in, height_in, panels, generator, prompt, image_url, local_path, dominant_hex, category, is_published, notes, parent_design_id, created_at)
-                      SELECT brand, kind, width_in, height_in, panels, generator, prompt || E'\\n[colorway-variant of #${id}]', '/designs/img/by-id/PLACEHOLDER', '${v.path.replace(/'/g, "''")}', '${v.ground.replace(/'/g, "''")}', '${cwTitle.replace(/'/g, "''")}', TRUE, E'colorway variant: ${v.slug} (ground=${v.ground}, figure=${v.figure}) of parent #${id}', ${id}, NOW()
-                      FROM spoon_all_designs WHERE id=${id}
-                      RETURNING id;`;
-                    try {
-                      const insertRaw = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' });
-                      // psql -At emits the RETURNING value on line 1, then the
-                      // command tag (e.g. "INSERT 0 1") on line 2. Earlier
-                      // version used `.split(/\s+/).pop()` which grabbed "1"
-                      // from the command tag. Take the FIRST line instead.
-                      const cwId = (insertRaw.trim().split('\n')[0] || '').trim();
-                      if (cwId && /^\d+$/.test(cwId)) {
-                        try {
-                          const updOut = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
-                          logLine(`    cw url-patch id=${cwId} → ${updOut || '(empty)'}`);
-                        } catch (uErr) {
-                          logLine(`    cw url-patch FAIL id=${cwId}: ${(uErr.message || '').slice(0, 200)}`);
-                        }
-                      } else {
-                        logLine(`    cw NO-ID raw=${JSON.stringify(insertRaw).slice(0, 300)}`);
-                      }
-                      logLine(`    cw child id=${cwId} (${v.slug}) of parent ${id}`);
-                    } catch (e2) {
-                      logLine(`    cw INSERT FAIL ${v.slug}: ${e2.message.slice(0, 200)}`);
-                    }
-                  }
-                } else {
-                  logLine(`  cw FAIL id=${id}: ${(cwOut.stderr || cwOut.stdout || '').slice(0, 200)}`);
-                }
-              } catch (e3) {
-                logLine(`  cw EXC id=${id}: ${e3.message.slice(0, 200)}`);
-              }
-            }
-          } else {
-            logLine(`  2tone FAIL id=${id}: ${(q.stderr || q.stdout || '').slice(0, 200)}`);
-          }
-        } else {
-          logLine(`  2tone SKIP id=${id}: local_path not found (${lp})`);
-        }
-      } catch (e) {
-        logLine(`  2tone EXC id=${id}: ${e.message.slice(0, 200)}`);
-      }
-    }
-    // Auto-publish so Steve sees it in /designs
-    if (id) {
-      try {
-        psql(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
-      } catch (e) {
-        logLine(`publish-flip FAIL id=${id}: ${e.message.slice(0, 120)}`);
-      }
-    }
-    return { ok: true, id, category, colorway: cw.slug };
-  } else {
-    return { ok: false, stderr: (r.stderr || r.stdout || '').slice(0, 200) };
-  }
-}
-
-// ── Main loop ────────────────────────────────────────────────────────
-(async () => {
-  if (fs.existsSync(HALT_FLAG)) {
-    logLine('ABORT — halt flag already exists at startup. Remove it first.');
-    process.exit(1);
-  }
-  const state = { iterations: 0, ok: 0, fail: 0, settlementSkipped: 0, spentUsd: 0, startedAt: nowIso() };
-  logLine(`START night-builder · budget=$${BUDGET_CAP_USD} stop=${STOP_DEADLINE.toLocaleString()} cooldown=${COOLDOWN_MS}ms`);
-  logLine(`Track mix: 30% cactus / 70% drunk-animals-designer · ${MUTED_PALETTE.length} muted colorways`);
-
-  while (!shouldStop(state)) {
-    state.iterations++;
-    const track = nextTrack();
-    const cw = pickColorway();
-    const r = generateOne(track, cw);
-    state.spentUsd += PER_IMAGE_USD;
-    if (r.settlementBlock) {
-      state.settlementSkipped++;
-    } else if (r.ok) {
-      state.ok++;
-      logLine(`OK  #${state.iterations} ${track}/${cw.slug} id=${r.id} (cumul: ok=${state.ok} fail=${state.fail} spent=$${state.spentUsd.toFixed(2)})`);
-    } else {
-      state.fail++;
-      logLine(`FAIL #${state.iterations} ${track}/${cw.slug}: ${r.stderr || 'unknown'}`);
-      // brief backoff on failure (rate-limit or transient)
-      await new Promise(rs => setTimeout(rs, 15000));
-    }
-    await new Promise(rs => setTimeout(rs, COOLDOWN_MS));
-  }
-
-  logLine(`STOP iterations=${state.iterations} ok=${state.ok} fail=${state.fail} settlementSkip=${state.settlementSkipped} spent=$${state.spentUsd.toFixed(2)}`);
-  process.exit(0);
-})();
diff --git a/server-admin.js b/server-admin.js
deleted file mode 100644
index b7ca0b3..0000000
--- a/server-admin.js
+++ /dev/null
@@ -1,642 +0,0 @@
-#!/usr/bin/env node
-/**
- * Wallco.ai local viewer — http://127.0.0.1:9792
- *
- * Two tabs:
- *   /pd        — public-domain source corpus (Met / Wikimedia / Smithsonian)
- *   /designs   — wallco.ai generated designs (spoon_all_designs)
- * Standing rule: sort + density slider (localStorage-persisted).
- */
-require('dotenv').config();
-const express = require('express');
-const path = require('path');
-const fs = require('fs');
-const { execSync } = require('child_process');
-const inlineEditor = require('@dw/inline-editor');
-
-const app = express();
-app.use(express.json());
-const PORT = process.env.PORT || 9792;
-const DATA_ROOT = path.join(__dirname, 'data');
-
-// ── Designs cache for review layer (5s TTL) ─────────────────────────
-let DESIGNS_CACHE = { ts: 0, rows: [] };
-function getDesignsCached() {
-  if (Date.now() - DESIGNS_CACHE.ts < 5000) return DESIGNS_CACHE.rows;
-  try {
-    const raw = execSync(`psql dw_unified -At -q`, {
-      input: `SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-                SELECT id, kind, prompt, dominant_hex, palette, category, tags, motifs,
-                       generator, seed, local_path, is_published, created_at
-                FROM spoon_all_designs ORDER BY created_at DESC LIMIT 500
-              ) t;`,
-      encoding: 'utf8'
-    }).trim();
-    DESIGNS_CACHE = { ts: Date.now(), rows: JSON.parse(raw || '[]') };
-  } catch (e) { console.error('getDesignsCached:', e.message); }
-  return DESIGNS_CACHE.rows;
-}
-
-// ── Mount review layer (sliders + chips + chip-chat) ────────────────
-try { require('./src/review').mount(app, getDesignsCached); }
-catch (e) { console.error('Review mount failed:', e.message); }
-
-// Inline editor (Wix-style admin editing). Auth required — admin role only.
-app.use(inlineEditor({
-  site: 'wallco.ai',
-  uploadDir: path.join(__dirname, 'public', 'uploads'),
-  authMode: process.env.AUTH_MODE || 'dev',
-  devSecret: process.env.AUTH_DEV_SECRET || 'wallco-dev-secret-change-for-prod'
-}));
-
-// Dev login (creates HS256 cookie). Strips out in prod.
-if ((process.env.AUTH_MODE || 'dev') === 'dev') {
-  const jwt = require('jsonwebtoken');
-  app.get('/_devlogin', (_req, res) => {
-    const token = jwt.sign({
-      email: 'steve@designerwallcoverings.com',
-      role: 'admin',
-      iss: 'https://auth.agentabrams.com'
-    }, process.env.AUTH_DEV_SECRET || 'wallco-dev-secret-change-for-prod',
-       { algorithm: 'HS256', expiresIn: '8h' });
-    res.cookie('dw_auth', token, { httpOnly: false });
-    res.type('text/plain').send('Admin logged in (dev). Refresh / to see edit affordances.\n');
-  });
-  app.get('/_devlogout', (_req, res) => { res.clearCookie('dw_auth'); res.send('Logged out\n'); });
-}
-
-function psql(sql) {
-  return execSync(`psql dw_unified -At -q`, { input: sql, encoding: 'utf8' }).trim();
-}
-
-// Serve local image files (so the viewer can show downloaded PD images)
-app.use('/img', express.static(DATA_ROOT, { maxAge: '7d' }));
-
-app.get('/api/pd', (_req, res) => {
-  try {
-    const raw = psql(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-      SELECT id, source, source_id, title, creator, date_created, license,
-             source_url, image_url, local_path, dominant_hex, palette,
-             category, tags, motifs
-      FROM pd_source_designs ORDER BY ingested_at DESC LIMIT 2000
-    ) t;`);
-    res.type('application/json').send(raw || '[]');
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/designs', (_req, res) => {
-  try {
-    const raw = psql(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-      SELECT id, kind, width_in, height_in, panels, generator, prompt,
-             seed, local_path, dominant_hex, palette, category, tags,
-             motifs, is_published, created_at
-      FROM spoon_all_designs ORDER BY created_at DESC LIMIT 2000
-    ) t;`);
-    res.type('application/json').send(raw || '[]');
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/counts', (_req, res) => {
-  try {
-    const pd = psql(`SELECT json_agg(t) FROM (SELECT source, COUNT(*) FROM pd_source_designs GROUP BY source ORDER BY 1) t;`);
-    const designs = psql(`SELECT COUNT(*) FROM spoon_all_designs;`);
-    res.json({ pd: JSON.parse(pd || '[]'), designs: parseInt(designs || '0', 10) });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Helper: convert local_path absolute → /img relative URL
-function imgUrl(localPath) {
-  if (!localPath) return null;
-  if (localPath.startsWith(DATA_ROOT)) return '/img/' + localPath.slice(DATA_ROOT.length + 1);
-  return null;
-}
-
-app.get('/', (_req, res) => {
-  res.type('html').send(`<!doctype html>
-<meta charset="utf-8">
-<title>wallco.ai — corpus + designs</title>
-<style>
-  :root { --cols: 5; }
-  *,*::before,*::after { box-sizing: border-box; }
-  body { margin:0; font:13px/1.4 -apple-system, system-ui, sans-serif; color:#1a1a1a; background:#0f0e0c; }
-  header { padding: 24px 32px; border-bottom: 1px solid #2a2825; background:#1a1816; color:#e8e2d6; display:flex; gap:16px; align-items:baseline; flex-wrap:wrap; }
-  header h1 { margin:0; font-weight:300; font-size:22px; letter-spacing:.5px; }
-  header .tag { font-size:12px; color:#aaa; }
-  nav a { color:#bba; text-decoration:none; margin-right:14px; font-size:12px; }
-  nav a.on { color:#fff; border-bottom:1px solid #d2b15c; }
-  .controls { margin-left:auto; display:flex; gap:16px; align-items:center; flex-wrap:wrap; }
-  .controls label { font-size:11px; color:#bba; text-transform:uppercase; letter-spacing:.08em; display:flex; gap:6px; align-items:center; }
-  .controls select, .controls input[type=range] { font:inherit; padding:4px 8px; border:1px solid #3a3631; background:#16140f; color:#fff; border-radius:2px; }
-  .controls input[type=range] { width:140px; }
-  main { padding: 24px 32px; }
-  .grid { display:grid; gap:18px; grid-template-columns: repeat(var(--cols), 1fr); }
-  .card { background:#1a1816; border:1px solid #2a2825; border-radius:2px; overflow:hidden; color:#ddd; }
-  .card .img { aspect-ratio: 1; background:#0d0c0a no-repeat center/cover; }
-  .card .meta { padding:10px 12px 14px; }
-  .card .title { font-size:12px; margin:0 0 4px; }
-  .card .sub { font-size:10px; color:#888; }
-  .card .dot { display:inline-block; width:10px; height:10px; border-radius:50%; border:1px solid #00000060; vertical-align:middle; margin-right:4px; }
-  .card .badge { display:inline-block; padding:1px 5px; font-size:9px; background:#2a2825; color:#d2b15c; border-radius:2px; margin-left:5px; text-transform:uppercase; letter-spacing:.05em; }
-  .empty { padding:48px; text-align:center; color:#666; }
-  a.card-link { color:inherit; text-decoration:none; display:block; }
-</style>
-<header>
-  <h1>wallco.ai</h1>
-  <span class="tag" id="count">…</span>
-  <nav>
-    <a href="#" id="tab-pd" class="on">Source corpus</a>
-    <a href="#" id="tab-designs">Generated designs</a>
-  </nav>
-  <div class="controls">
-    <label>Sort
-      <select id="sort">
-        <option value="recent">Recent</option>
-        <option value="color-wheel">Color Wheel</option>
-        <option value="light-dark">Light → Dark</option>
-        <option value="dark-light">Dark → Light</option>
-        <option value="source">Source</option>
-        <option value="title">Title A→Z</option>
-      </select>
-    </label>
-    <label>Density
-      <input type="range" id="density" min="3" max="9" step="1" value="5">
-    </label>
-  </div>
-</header>
-<main><div class="grid" id="grid"></div></main>
-<script>
-const DATA_ROOT = ${JSON.stringify(DATA_ROOT)};
-const sortSel = document.getElementById('sort');
-const dens = document.getElementById('density');
-const grid = document.getElementById('grid');
-const cnt = document.getElementById('count');
-sortSel.value = localStorage.getItem('wc.sort') || 'recent';
-dens.value = localStorage.getItem('wc.density') || 5;
-document.documentElement.style.setProperty('--cols', dens.value);
-
-let tab = localStorage.getItem('wc.tab') || 'pd';
-function setTab(t) {
-  tab = t;
-  document.getElementById('tab-pd').classList.toggle('on', t === 'pd');
-  document.getElementById('tab-designs').classList.toggle('on', t === 'designs');
-  localStorage.setItem('wc.tab', t);
-  load();
-}
-document.getElementById('tab-pd').addEventListener('click', e => { e.preventDefault(); setTab('pd'); });
-document.getElementById('tab-designs').addEventListener('click', e => { e.preventDefault(); setTab('designs'); });
-
-function hexToHsl(hex) {
-  if (!hex || hex.length < 4) return [0,0,0];
-  const m = hex.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
-  if (!m) return [0,0,0];
-  const r = parseInt(m[1],16)/255, g = parseInt(m[2],16)/255, b = parseInt(m[3],16)/255;
-  const max = Math.max(r,g,b), min = Math.min(r,g,b);
-  const l = (max + min) / 2; let h = 0, s = 0;
-  if (max !== min) {
-    const d = max - min;
-    s = l > 0.5 ? d/(2-max-min) : d/(max+min);
-    if (max === r) h = (g-b)/d + (g<b?6:0);
-    else if (max === g) h = (b-r)/d + 2;
-    else h = (r-g)/d + 4;
-    h *= 60;
-  }
-  return [h,s,l];
-}
-
-function imgPath(p) {
-  if (!p) return '';
-  if (p.startsWith(DATA_ROOT)) return '/img/' + p.slice(DATA_ROOT.length + 1);
-  return p;
-}
-
-function render(data) {
-  const sort = sortSel.value;
-  const sorted = [...data].sort((a,b) => {
-    if (sort === 'recent') return 0;
-    if (sort === 'source') return (a.source||a.generator||'').localeCompare(b.source||b.generator||'');
-    if (sort === 'title')  return (a.title||a.prompt||'').localeCompare(b.title||b.prompt||'');
-    const ha = hexToHsl(a.dominant_hex), hb = hexToHsl(b.dominant_hex);
-    if (sort === 'light-dark') return hb[2]-ha[2];
-    if (sort === 'dark-light') return ha[2]-hb[2];
-    if (sort === 'color-wheel') {
-      const isaA = ha[1] < 0.08, isaB = hb[1] < 0.08;
-      if (isaA !== isaB) return isaA ? -1 : 1;
-      if (isaA) return ha[2]-hb[2];
-      const bA = Math.floor(ha[0]/30), bB = Math.floor(hb[0]/30);
-      if (bA !== bB) return bA - bB;
-      return ha[2]-hb[2];
-    }
-    return 0;
-  });
-  cnt.textContent = sorted.length + (tab === 'pd' ? ' PD source images' : ' generated designs');
-  grid.innerHTML = sorted.map(p => {
-    const img = imgPath(p.local_path) || p.image_url || '';
-    const dot = p.dominant_hex ? \`<span class="dot" style="background:\${p.dominant_hex}" title="\${p.dominant_hex}"></span>\` : '';
-    const badge = (p.source || p.generator || '');
-    const title = p.title || p.prompt || ('#'+p.id);
-    const sub = p.creator || p.date_created || p.kind || '';
-    const link = p.source_url || '#';
-    return \`<a class="card-link" href="\${link}" target="_blank"><div class="card">
-      <div class="img" style="background-image:url('\${img}')"></div>
-      <div class="meta">
-        <div class="title">\${dot}\${title.slice(0,60)}<span class="badge">\${badge}</span></div>
-        <div class="sub">\${sub.slice(0,80)}</div>
-      </div>
-    </div></a>\`;
-  }).join('') || '<div class="empty">Empty — run scripts/crawl_pd.py or scripts/generate_designs.js</div>';
-}
-
-async function load() {
-  const r = await fetch(tab === 'pd' ? '/api/pd' : '/api/designs');
-  const data = await r.json();
-  render(data);
-}
-
-sortSel.addEventListener('change', () => { localStorage.setItem('wc.sort', sortSel.value); load(); });
-dens.addEventListener('input', () => {
-  document.documentElement.style.setProperty('--cols', dens.value);
-  localStorage.setItem('wc.density', dens.value);
-});
-
-setTab(tab);
-</script>`);
-});
-
-// ── /designs — review interface (sliders + K/R + chips + chat) ────
-app.get('/designs', (_req, res) => {
-  const designs = getDesignsCached();
-  res.type('html').send(`<!doctype html>
-<meta charset="utf-8">
-<title>wallco.ai · designs review</title>
-<style>
-  :root { --cols: 4; --keep:#5fbf6e; --reject:#d2554a; --chip:#3a3631; --chip-hot:#d2b15c; }
-  *,*::before,*::after { box-sizing: border-box; }
-  body { margin:0; font:13px/1.4 -apple-system, system-ui, sans-serif; color:#e8e2d6; background:#0f0e0c; }
-  header { padding:18px 28px; border-bottom:1px solid #2a2825; background:#1a1816; display:flex; gap:18px; align-items:baseline; flex-wrap:wrap; position:sticky; top:0; z-index:10; }
-  header h1 { margin:0; font-weight:300; font-size:20px; letter-spacing:.5px; }
-  header .tag { font-size:11px; color:#aaa; }
-  nav a { color:#bba; text-decoration:none; margin-right:14px; font-size:12px; }
-  nav a.on { color:#fff; border-bottom:1px solid #d2b15c; }
-  .controls { margin-left:auto; display:flex; gap:16px; align-items:center; flex-wrap:wrap; }
-  .controls label { font-size:11px; color:#bba; text-transform:uppercase; letter-spacing:.08em; display:flex; gap:6px; align-items:center; }
-  .controls select, .controls input[type=range] { font:inherit; padding:4px 8px; border:1px solid #3a3631; background:#16140f; color:#fff; border-radius:2px; }
-  .controls input[type=range] { width:140px; }
-  .filter-pill { background:#2a2825; padding:3px 10px; border-radius:12px; font-size:11px; cursor:pointer; color:#bba; border:1px solid transparent; }
-  .filter-pill.on { background:#d2b15c; color:#0f0e0c; }
-  main { padding: 22px 28px; }
-  .grid { display:grid; gap:20px; grid-template-columns: repeat(var(--cols), 1fr); }
-  .card { background:#1a1816; border:1px solid #2a2825; border-radius:3px; overflow:hidden; transition: border-color .15s; }
-  .card[data-decision=keep]   { border-color: var(--keep); }
-  .card[data-decision=reject] { border-color: var(--reject); opacity:.55; }
-  .card .img-wrap { aspect-ratio: 1; background:#0d0c0a no-repeat center/cover; display:block; cursor:zoom-in; position:relative; }
-  .card .img-wrap .decision-badge { position:absolute; top:8px; right:8px; padding:2px 8px; font-size:10px; border-radius:2px; text-transform:uppercase; letter-spacing:.06em; display:none; }
-  .card[data-decision=keep] .img-wrap .decision-badge { display:inline-block; background:var(--keep); color:#0f0e0c; }
-  .card[data-decision=reject] .img-wrap .decision-badge { display:inline-block; background:var(--reject); color:#fff; }
-  .card .body { padding:12px 14px 14px; }
-  .card .title { font-size:12px; margin:0 0 4px; color:#e8e2d6; line-height:1.3; max-height:2.6em; overflow:hidden; }
-  .card .sub { font-size:10px; color:#888; margin-bottom:10px; }
-  .dot { display:inline-block; width:9px; height:9px; border-radius:50%; border:1px solid #00000060; vertical-align:middle; margin-right:5px; }
-  .sliders { display:flex; flex-direction:column; gap:4px; margin: 10px 0 10px; }
-  .sliders label { display:grid; grid-template-columns: 48px 1fr 22px; gap:6px; align-items:center; font-size:10px; color:#bba; text-transform:uppercase; letter-spacing:.06em; }
-  .sliders label span { text-align:right; color:#d2b15c; font-variant-numeric: tabular-nums; }
-  .sliders input[type=range] { width:100%; -webkit-appearance:none; height:2px; background:#3a3631; border-radius:2px; outline:none; }
-  .sliders input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; width:11px; height:11px; border-radius:50%; background:#d2b15c; cursor:pointer; border:none; }
-  .decision { display:flex; gap:6px; margin-bottom:9px; }
-  .decision button { flex:1; padding:5px 8px; font-size:11px; border:1px solid #3a3631; background:#16140f; color:#bba; cursor:pointer; border-radius:2px; transition: all .12s; text-transform:uppercase; letter-spacing:.06em; }
-  .decision button:hover { background:#1f1c18; color:#fff; }
-  .card[data-decision=keep]   .btn-keep   { background:var(--keep);   color:#0f0e0c; border-color:var(--keep); }
-  .card[data-decision=reject] .btn-reject { background:var(--reject); color:#fff;    border-color:var(--reject); }
-  .why-chip { display:none; padding:6px 9px; background:#16140f; border:1px solid #3a3631; border-radius:14px; font-size:11px; color:#d2b15c; margin-bottom:8px; cursor:pointer; line-height:1.35; }
-  .why-chip.visible { display:block; }
-  .why-chip.loading::after { content:' …'; opacity:.6; }
-  .chips { display:flex; flex-wrap:wrap; gap:5px; }
-  .chip { padding:3px 8px; background:var(--chip); color:#bba; border-radius:10px; font-size:10px; cursor:pointer; border:1px solid transparent; transition: all .12s; }
-  .chip:hover { background:#4a443d; color:#fff; }
-  .chip.has-chat { border-color: var(--chip-hot); color:#d2b15c; }
-  .empty { padding:48px; text-align:center; color:#666; }
-
-  /* chat modal */
-  #chat-modal { position:fixed; top:0; right:0; bottom:0; width:420px; background:#16140f; border-left:1px solid #2a2825; display:none; flex-direction:column; z-index:100; box-shadow:-8px 0 20px rgba(0,0,0,.4); }
-  #chat-modal.open { display:flex; }
-  #chat-head { padding:14px 18px; border-bottom:1px solid #2a2825; display:flex; gap:10px; align-items:center; }
-  #chat-head .ctx { flex:1; font-size:12px; color:#d2b15c; }
-  #chat-head .ctx-sub { font-size:10px; color:#888; }
-  #chat-head .close { background:none; border:none; color:#999; font-size:22px; cursor:pointer; padding:0 4px; }
-  #chat-history { flex:1; overflow-y:auto; padding:16px 18px; display:flex; flex-direction:column; gap:10px; }
-  #chat-history .msg { padding:9px 12px; border-radius:10px; max-width:88%; font-size:12px; line-height:1.45; white-space:pre-wrap; word-wrap:break-word; }
-  #chat-history .msg.user { background:#2a2825; color:#fff; align-self:flex-end; }
-  #chat-history .msg.assistant { background:#1f1c18; color:#e8e2d6; align-self:flex-start; border:1px solid #2a2825; }
-  #chat-history .msg.assistant.loading { color:#888; font-style:italic; }
-  #chat-form { display:flex; padding:12px 14px; border-top:1px solid #2a2825; gap:8px; }
-  #chat-form input { flex:1; padding:8px 12px; background:#0f0e0c; border:1px solid #2a2825; color:#fff; font:inherit; border-radius:2px; outline:none; }
-  #chat-form input:focus { border-color:#d2b15c; }
-  #chat-form button { padding:8px 14px; background:#d2b15c; color:#0f0e0c; border:none; cursor:pointer; font:inherit; font-weight:500; border-radius:2px; }
-  #chat-form button:disabled { opacity:.5; cursor:not-allowed; }
-  a.card-link { color:inherit; text-decoration:none; display:block; }
-</style>
-<header>
-  <h1>wallco.ai</h1>
-  <span class="tag" id="count">${designs.length} designs</span>
-  <nav>
-    <a href="/" class="">Source corpus / generated</a>
-    <a href="/designs" class="on">Review queue</a>
-  </nav>
-  <div class="controls">
-    <div class="filter-pill" data-filter="all">All</div>
-    <div class="filter-pill" data-filter="unreviewed">Unreviewed</div>
-    <div class="filter-pill" data-filter="keep">Kept</div>
-    <div class="filter-pill" data-filter="reject">Rejected</div>
-    <label>Sort
-      <select id="sort">
-        <option value="recent">Recent</option>
-        <option value="color-wheel">Color wheel</option>
-        <option value="light-dark">Light → Dark</option>
-        <option value="dark-light">Dark → Light</option>
-        <option value="title">Title A→Z</option>
-      </select>
-    </label>
-    <label>Density
-      <input type="range" id="density" min="2" max="7" step="1" value="4">
-    </label>
-  </div>
-</header>
-<main><div class="grid" id="grid"></div></main>
-
-<div id="chat-modal">
-  <div id="chat-head">
-    <div>
-      <div class="ctx" id="chat-chip-label">chip</div>
-      <div class="ctx-sub" id="chat-design-label">design</div>
-    </div>
-    <button class="close" id="chat-close" title="Close (Esc)">×</button>
-  </div>
-  <div id="chat-history"></div>
-  <form id="chat-form">
-    <input type="text" id="chat-input" placeholder="Ask about this chip…" autocomplete="off">
-    <button type="submit" id="chat-send">Send</button>
-  </form>
-</div>
-
-<script>
-const DATA_ROOT = ${JSON.stringify(DATA_ROOT)};
-const DESIGNS = ${JSON.stringify(designs)};
-let REVIEWS = {};      // designId -> {design,color,style,decision,why}
-let CHIPS = {};        // designId -> [{key,label,kind}]
-let CURRENT_CHIP = null;
-let FILTER = 'all';
-
-const $ = (s, el=document) => el.querySelector(s);
-const $$ = (s, el=document) => Array.from(el.querySelectorAll(s));
-const sortSel = $('#sort');
-const dens = $('#density');
-const grid = $('#grid');
-sortSel.value = localStorage.getItem('wc.review.sort') || 'recent';
-dens.value = localStorage.getItem('wc.review.density') || 4;
-document.documentElement.style.setProperty('--cols', dens.value);
-
-function imgPath(p) {
-  if (!p) return '';
-  if (p.startsWith(DATA_ROOT)) return '/img/' + p.slice(DATA_ROOT.length + 1);
-  return p;
-}
-function hexToHsl(hex) {
-  if (!hex) return [0,0,0];
-  const m = hex.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
-  if (!m) return [0,0,0];
-  const r = parseInt(m[1],16)/255, g = parseInt(m[2],16)/255, b = parseInt(m[3],16)/255;
-  const max = Math.max(r,g,b), min = Math.min(r,g,b);
-  const l = (max+min)/2; let h = 0, s = 0;
-  if (max !== min) {
-    const d = max - min;
-    s = l > 0.5 ? d/(2-max-min) : d/(max+min);
-    if (max === r) h = (g-b)/d + (g<b?6:0);
-    else if (max === g) h = (b-r)/d + 2;
-    else h = (r-g)/d + 4;
-    h *= 60;
-  }
-  return [h,s,l];
-}
-function sortDesigns(data) {
-  const sort = sortSel.value;
-  return [...data].sort((a,b) => {
-    if (sort === 'recent') return new Date(b.created_at||0) - new Date(a.created_at||0);
-    if (sort === 'title')  return (a.prompt||'').slice(0,40).localeCompare((b.prompt||'').slice(0,40));
-    const ha = hexToHsl(a.dominant_hex), hb = hexToHsl(b.dominant_hex);
-    if (sort === 'light-dark') return hb[2]-ha[2];
-    if (sort === 'dark-light') return ha[2]-hb[2];
-    if (sort === 'color-wheel') {
-      const isaA = ha[1] < 0.08, isaB = hb[1] < 0.08;
-      if (isaA !== isaB) return isaA ? -1 : 1;
-      if (isaA) return ha[2]-hb[2];
-      const bA = Math.floor(ha[0]/30), bB = Math.floor(hb[0]/30);
-      if (bA !== bB) return bA - bB;
-      return ha[2]-hb[2];
-    }
-    return 0;
-  });
-}
-
-async function fetchInitialReviews() {
-  try { REVIEWS = await (await fetch('/api/reviews/all')).json(); } catch {}
-}
-
-function makeChipsHTML(designId) {
-  const chips = CHIPS[designId] || [];
-  return chips.map(c => \`<span class="chip" data-design="\${designId}" data-chip="\${c.key}">\${c.label}</span>\`).join('');
-}
-
-function renderCard(d) {
-  const img = imgPath(d.local_path) || '';
-  const review = REVIEWS[d.id] || {};
-  const decision = review.decision || '';
-  const title = (d.prompt || '#'+d.id).slice(0, 80);
-  const sub = (d.category || '') + (d.kind ? ' · ' + d.kind : '');
-  const dot = d.dominant_hex ? '<span class="dot" style="background:'+d.dominant_hex+'"></span>' : '';
-  const sD = review.design ?? 5, sC = review.color ?? 5, sS = review.style ?? 5;
-  return \`<div class="card" data-id="\${d.id}" data-decision="\${decision}">
-    <a class="img-wrap" style="background-image:url('\${img}')" href="/design/\${d.id}" target="_blank">
-      <span class="decision-badge">\${decision || ''}</span>
-    </a>
-    <div class="body">
-      <div class="title">\${dot}\${title}</div>
-      <div class="sub">\${sub}</div>
-      <div class="sliders">
-        <label>Design <input type="range" min="0" max="10" value="\${sD}" data-axis="design"><span>\${sD}</span></label>
-        <label>Color  <input type="range" min="0" max="10" value="\${sC}" data-axis="color"><span>\${sC}</span></label>
-        <label>Style  <input type="range" min="0" max="10" value="\${sS}" data-axis="style"><span>\${sS}</span></label>
-      </div>
-      <div class="decision">
-        <button class="btn-keep">Keep</button>
-        <button class="btn-reject">Reject</button>
-      </div>
-      <div class="why-chip \${review.why ? 'visible' : ''}">\${review.why || ''}</div>
-      <div class="chips">\${makeChipsHTML(d.id)}</div>
-    </div>
-  </div>\`;
-}
-
-function applyFilter(d) {
-  if (FILTER === 'all') return true;
-  const r = REVIEWS[d.id];
-  if (FILTER === 'unreviewed') return !r || !r.decision;
-  if (FILTER === 'keep') return r && r.decision === 'keep';
-  if (FILTER === 'reject') return r && r.decision === 'reject';
-  return true;
-}
-
-function renderAll() {
-  const filtered = sortDesigns(DESIGNS.filter(applyFilter));
-  $('#count').textContent = filtered.length + ' designs' + (FILTER !== 'all' ? ' (' + FILTER + ')' : '');
-  grid.innerHTML = filtered.length
-    ? filtered.map(renderCard).join('')
-    : '<div class="empty">No matches for filter "' + FILTER + '"</div>';
-  wireCards();
-}
-
-async function loadChipsForVisibleCards() {
-  const toFetch = $$('.card').map(c => c.dataset.id).filter(id => !CHIPS[id]);
-  await Promise.all(toFetch.map(async id => {
-    try { const r = await fetch('/api/chips/' + id); const j = await r.json(); CHIPS[id] = j.chips || []; } catch {}
-  }));
-  toFetch.forEach(id => {
-    const card = grid.querySelector('.card[data-id="' + id + '"]');
-    if (card) card.querySelector('.chips').innerHTML = makeChipsHTML(id);
-  });
-}
-
-function wireCards() {
-  $$('.card').forEach(card => {
-    const id = card.dataset.id;
-    // sliders
-    card.querySelectorAll('.sliders input[type=range]').forEach(slider => {
-      slider.addEventListener('input', () => {
-        slider.nextElementSibling.textContent = slider.value;
-      });
-      slider.addEventListener('change', () => saveReview(id, card));
-    });
-    // K/R buttons
-    card.querySelector('.btn-keep').addEventListener('click', () => {
-      card.dataset.decision = card.dataset.decision === 'keep' ? '' : 'keep';
-      saveReview(id, card);
-    });
-    card.querySelector('.btn-reject').addEventListener('click', () => {
-      card.dataset.decision = card.dataset.decision === 'reject' ? '' : 'reject';
-      saveReview(id, card);
-    });
-    // chips → open chat
-    card.querySelectorAll('.chip').forEach(chip => {
-      chip.addEventListener('click', () => openChat(id, chip.dataset.chip, chip.textContent));
-    });
-    // why chip → open chat (uses special key "_verdict")
-    const whyChip = card.querySelector('.why-chip');
-    if (whyChip) whyChip.addEventListener('click', () => {
-      if (whyChip.classList.contains('visible')) openChat(id, '_verdict', '"' + whyChip.textContent + '"');
-    });
-  });
-}
-
-async function saveReview(id, card) {
-  const sliders = card.querySelectorAll('.sliders input[type=range]');
-  const payload = {
-    design: parseInt(sliders[0].value, 10),
-    color:  parseInt(sliders[1].value, 10),
-    style:  parseInt(sliders[2].value, 10),
-    decision: card.dataset.decision || null,
-  };
-  const why = card.querySelector('.why-chip');
-  if (payload.decision) { why.classList.add('visible', 'loading'); why.textContent = 'thinking…'; }
-  else                  { why.classList.remove('visible'); why.textContent = ''; }
-  try {
-    const r = await fetch('/api/review/' + id, {
-      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload)
-    });
-    const j = await r.json();
-    REVIEWS[id] = j;
-    if (j.why) { why.textContent = j.why; why.classList.add('visible'); why.classList.remove('loading'); }
-    else why.classList.remove('visible');
-  } catch (e) { why.textContent = '(error: ' + e.message + ')'; why.classList.remove('loading'); }
-}
-
-// ── chat modal ────────────────────────────────────────────────────
-const modal = $('#chat-modal');
-const chatHistory = $('#chat-history');
-const chatInput = $('#chat-input');
-const chatSend = $('#chat-send');
-const chatForm = $('#chat-form');
-$('#chat-close').addEventListener('click', () => modal.classList.remove('open'));
-document.addEventListener('keydown', e => { if (e.key === 'Escape' && modal.classList.contains('open')) modal.classList.remove('open'); });
-
-async function openChat(designId, chipKey, chipLabel) {
-  CURRENT_CHIP = { designId, chipKey, chipLabel };
-  const design = DESIGNS.find(d => String(d.id) === String(designId));
-  $('#chat-chip-label').textContent = 'chip: ' + chipLabel;
-  $('#chat-design-label').textContent = '#' + designId + ' · ' + (design ? (design.prompt||'').slice(0,60) : '');
-  chatHistory.innerHTML = '<div class="msg assistant loading">loading history…</div>';
-  modal.classList.add('open');
-  setTimeout(() => chatInput.focus(), 100);
-  try {
-    const r = await fetch('/api/chip/' + designId + '/' + encodeURIComponent(chipKey) + '/chat');
-    const j = await r.json();
-    renderChatHistory(j.history || []);
-  } catch (e) { chatHistory.innerHTML = '<div class="msg assistant">load error: ' + e.message + '</div>'; }
-}
-function renderChatHistory(msgs) {
-  chatHistory.innerHTML = msgs.length
-    ? msgs.map(m => '<div class="msg ' + m.role + '">' + escapeHTML(m.content) + '</div>').join('')
-    : '<div class="msg assistant" style="opacity:.5">Ask the panel about this chip.</div>';
-  chatHistory.scrollTop = chatHistory.scrollHeight;
-}
-function escapeHTML(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[c])); }
-
-chatForm.addEventListener('submit', async e => {
-  e.preventDefault();
-  if (!CURRENT_CHIP) return;
-  const message = chatInput.value.trim();
-  if (!message) return;
-  chatInput.value = '';
-  chatSend.disabled = true;
-  const userDiv = document.createElement('div'); userDiv.className = 'msg user'; userDiv.textContent = message;
-  const loadingDiv = document.createElement('div'); loadingDiv.className = 'msg assistant loading'; loadingDiv.textContent = 'thinking…';
-  if (chatHistory.firstElementChild && chatHistory.firstElementChild.style && chatHistory.firstElementChild.style.opacity) chatHistory.innerHTML = '';
-  chatHistory.appendChild(userDiv); chatHistory.appendChild(loadingDiv);
-  chatHistory.scrollTop = chatHistory.scrollHeight;
-  try {
-    const r = await fetch('/api/chip/' + CURRENT_CHIP.designId + '/' + encodeURIComponent(CURRENT_CHIP.chipKey) + '/chat', {
-      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message })
-    });
-    const j = await r.json();
-    loadingDiv.classList.remove('loading'); loadingDiv.textContent = j.reply || '(no reply)';
-    // mark chip as having chat history
-    document.querySelectorAll('.chip[data-design="' + CURRENT_CHIP.designId + '"][data-chip="' + CURRENT_CHIP.chipKey + '"]').forEach(c => c.classList.add('has-chat'));
-  } catch (e) {
-    loadingDiv.classList.remove('loading'); loadingDiv.textContent = 'error: ' + e.message;
-  }
-  chatSend.disabled = false;
-  chatInput.focus();
-});
-
-// ── filter pills ─────────────────────────────────────────────────
-$$('.filter-pill').forEach(p => p.addEventListener('click', () => {
-  $$('.filter-pill').forEach(x => x.classList.remove('on'));
-  p.classList.add('on');
-  FILTER = p.dataset.filter;
-  renderAll(); loadChipsForVisibleCards();
-}));
-$('.filter-pill[data-filter="all"]').classList.add('on');
-
-sortSel.addEventListener('change', () => { localStorage.setItem('wc.review.sort', sortSel.value); renderAll(); });
-dens.addEventListener('input', () => {
-  document.documentElement.style.setProperty('--cols', dens.value);
-  localStorage.setItem('wc.review.density', dens.value);
-});
-
-// ── boot ─────────────────────────────────────────────────────────
-(async () => {
-  await fetchInitialReviews();
-  renderAll();
-  await loadChipsForVisibleCards();
-})();
-</script>`);
-});
-
-app.listen(PORT, '127.0.0.1', () => console.log(`wallco.ai viewer → http://127.0.0.1:${PORT}`));
diff --git a/server.js.bak-ai-rename b/server.js.bak-ai-rename
deleted file mode 100644
index a924033..0000000
--- a/server.js.bak-ai-rename
+++ /dev/null
@@ -1,15683 +0,0 @@
-#!/usr/bin/env node
-/**
- * wallco.ai — customer-facing storefront
- * AI-original wallpaper and murals — every pattern generated, never repeated.
- *
- * Routes:
- *   GET /              — hero + featured designs grid (home)
- *   GET /designs       — full catalog grid with sort + density + filter
- *   GET /design/:id    — design detail page
- *   GET /murals        — mural-mode designs
- *   GET /about         — AI provenance
- *   GET /samples       — lead capture (no commerce in v1)
- *   GET /health        — {ok, count}
- *   POST /api/leads    — sample request lead capture → dw_unified.wallco_leads
- *   GET /designs/img/:filename — serve design images
- *   GET /api/designs   — JSON API (sort/filter/page)
- *   GET /api/_inline/state — inline editor state (admin gate)
- */
-'use strict';
-require('dotenv').config();
-
-const express  = require('express');
-const path     = require('path');
-const fs       = require('fs');
-const crypto   = require('crypto');
-const { spawn, spawnSync, execSync, execFileSync } = require('child_process');
-
-const app  = express();
-const PORT = parseInt(process.env.PORT || '9905', 10);
-const SITE = 'wallco.ai';
-const DATA_FILE = path.join(__dirname, 'data', 'designs.json');
-const IMG_DIR   = path.join(__dirname, 'data', 'generated');
-const ROOMS_DIR = path.join(__dirname, 'public', 'uploads', 'rooms');
-const SF_JOBS_DIR = path.join(__dirname, 'logs', 'spoonflower');
-const SF_PULL_DIR = path.join(__dirname, 'data', 'spoonflower-pulled');
-fs.mkdirSync(ROOMS_DIR, { recursive: true });
-fs.mkdirSync(SF_JOBS_DIR, { recursive: true });
-fs.mkdirSync(SF_PULL_DIR, { recursive: true });
-
-// Room generator + ElevenLabs config
-const ROOM_API = process.env.ROOM_API || 'http://45.61.58.125:3075/api/generate-room';
-const ELEVEN_KEY = process.env.ELEVENLABS_API_KEY || '';
-const ELEVEN_VOICE_ID = process.env.ELEVENLABS_VOICE_ID || '21m00Tcm4TlvDq8ikWAM'; // Rachel (default suggested)
-// Local trust-auth works; the URL form in .env is for prod / remote use.
-// Stick with the bare DB name so peer auth handles it cleanly.
-const DB_NAME = process.env.WALLCO_DB || 'dw_unified';
-const DB_URL = process.env.DATABASE_URL || DB_NAME;
-
-// On Linux (Kamatera) pm2 runs as root but PG has no 'root' role — sudo as
-// postgres. On macOS local trust auth works for the current user.
-const PSQL_CMD = (process.platform === 'linux')
-  ? `sudo -n -u postgres psql ${DB_NAME} -At -q`
-  : `psql ${DB_NAME} -At -q`;
-
-function psqlQuery(sql) {
-  return execSync(PSQL_CMD, { input: sql, encoding: 'utf8' }).trim();
-}
-
-// PG value escaper for inline SQL — matches the project's existing inline-string style.
-function pgEsc(v) {
-  if (v === null || v === undefined) return 'NULL';
-  if (typeof v === 'number') return Number.isFinite(v) ? String(v) : 'NULL';
-  if (typeof v === 'boolean') return v ? 'TRUE' : 'FALSE';
-  return "'" + String(v).replace(/'/g, "''") + "'";
-}
-
-// Admin-token check for destructive room ops. Empty/unset token => admin features locked off.
-const ADMIN_TOKEN = process.env.ADMIN_TOKEN || '';
-function isRoomsAdmin(req) {
-  if (!ADMIN_TOKEN) return false;
-  return (req.headers['x-admin-token'] === ADMIN_TOKEN)
-      || (req.query && req.query.admin === ADMIN_TOKEN);
-}
-
-// gzip + brotli on everything compressible. PNGs are already deflated so the
-// middleware filter skips them; HTML/JS/CSS/JSON drops ~70%. Threshold 1KB
-// avoids overhead for tiny responses. Bypass-able via X-No-Compress header.
-const compression = require('compression');
-app.use(compression({
-  threshold: 1024,
-  filter: (req, res) => {
-    if (req.headers['x-no-compress']) return false;
-    return compression.filter(req, res);
-  }
-}));
-
-// ── Marketplace early-mount: the Shopify orders/fulfilled webhook MUST register
-// before the global express.json() so the raw request body is preserved for
-// HMAC verification (express.raw runs on that route only).
-try { require('./src/marketplace').mountEarly(app); } catch (e) { console.error('Marketplace early mount failed:', e.message); }
-
-app.use(express.json({ limit: '25mb' }));  // 25mb to accept full-res baked PNGs from /api/design/:id/bake
-app.use(express.urlencoded({ extended: false }));
-
-// ── Chat layer: catalog + per-design variation
-try { require('./src/chat').mount(app); console.log('  Chat layer mounted'); } catch (e) { console.error('Chat mount failed:', e.message); }
-
-// ── Review layer: sliders + chips + chip-chat on /designs (admin-gated by hostname/query)
-try { require('./src/review').mount(app, () => DESIGNS); } catch (e) { console.error('Review mount failed:', e.message); }
-
-// Drop the Express version disclosure header (info-leak hardening per starsofdesign security audit 2026-05-12)
-app.disable('x-powered-by');
-
-// Rate limit the expensive AI endpoints (generation/compose/upload hit LLMs + Replicate)
-// 60 req/min/IP across /api/studio/* prevents a single bad actor from burning credits.
-try {
-  const rateLimit = require('express-rate-limit');
-  app.use('/api/studio', rateLimit({ windowMs: 60_000, max: 60, standardHeaders: 'draft-7', legacyHeaders: false }));
-} catch (_) { /* express-rate-limit not installed — non-fatal */ }
-
-// ── Admin layer: vendor browse + aesthetic search + inspiration pool (NO-LEAK — admin only)
-try { require('./src/admin').mount(app); console.log('  Admin layer mounted'); } catch (e) { console.error('Admin mount failed:', e.message); }
-
-// ── Marketplace layer: designer-powered marketplace (Patternbank × Material Bank × Spoonflower)
-try { require('./src/marketplace').mount(app); } catch (e) { console.error('Marketplace mount failed:', e.message); }
-
-// ── Social layer: share sheet + /reel maker (select up to 5 designs → MP4 reel)
-try { require('./src/social').mount(app); } catch (e) { console.error('Social mount failed:', e.message); }
-
-// Inject the universal share layer onto the designs grid + every design page.
-// Done as a response rewrite so the 863KB server.js page templates stay untouched.
-app.use((req, res, next) => {
-  if (req.path === '/designs' || /^\/design\/\d+$/.test(req.path)) {
-    const origSend = res.send.bind(res);
-    res.send = (body) => {
-      if (typeof body === 'string' && body.indexOf('</body>') !== -1
-          && body.indexOf('social-share.js') === -1) {
-        body = body.replace('</body>',
-          '<script src="/js/social-share.js" defer></script>\n</body>');
-      }
-      return origSend(body);
-    };
-  }
-  next();
-});
-
-// ── Cache-Control: no-store on HTML (per feedback_cloudflare_html_caching.md)
-app.use((req, res, next) => {
-  if (req.accepts('html') && !req.path.startsWith('/designs/img')) {
-    res.setHeader('Cache-Control', 'no-store, must-revalidate');
-  }
-  next();
-});
-
-// ── Static: uploaded assets + general /public (css, js, og images, favicons)
-app.use('/uploads', express.static(path.join(__dirname, 'public', 'uploads'), { maxAge: '7d' }));
-app.use(express.static(path.join(__dirname, 'public'), { maxAge: '1h' }));
-
-// ── Serve generated design images
-app.use('/designs/img', express.static(IMG_DIR, {
-  maxAge: '30d',
-  setHeaders: (res, filePath) => {
-    res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
-  }
-}));
-
-// ── Serve pre-generated room mockups (one PNG per design+room type).
-// Paths live in spoon_all_designs.room_mockups JSONB; physical files in data/rooms/.
-app.use('/designs/room', express.static(path.join(__dirname, 'data', 'rooms'), {
-  maxAge: '7d',
-  setHeaders: (res) => res.setHeader('Cache-Control', 'public, max-age=604800')
-}));
-
-// ── robots.txt — allow all + reference the sitemap.
-// Last-Modified pinned to process start (robots policy rarely changes; if it
-// does, the deploy/restart updates the timestamp). Override the global
-// no-store middleware with 1-day cacheable to maximize crawler win.
-const ROBOTS_LAST_MODIFIED = new Date();
-app.get('/robots.txt', (req, res) => {
-  res.setHeader('Cache-Control', 'public, max-age=86400, must-revalidate');
-  if (maybe304(req, res, ROBOTS_LAST_MODIFIED)) return;
-  res.type('text/plain').send(
-    `User-agent: *\nAllow: /\nDisallow: /admin/\nDisallow: /api/\nDisallow: /_devlogin\n\nSitemap: https://${SITE}/sitemap-index.xml\nSitemap: https://${SITE}/sitemap.xml\nSitemap: https://${SITE}/sitemap-marketplace.xml\n`
-  );
-});
-
-// ── sitemap.xml — static pages + every published design.
-// Priority ranks by saturation (more visually striking = better landing page);
-// designs with a room mockup get a +0.1 bonus capped at 0.9. Cached 1h.
-let SITEMAP_CACHE = { body: '', exp: 0 };
-app.get('/sitemap.xml', (req, res) => {
-  // Conditional GET — sitemap freshness = freshest design's created_at.
-  res.setHeader('Cache-Control', 'public, max-age=3600, must-revalidate');
-  if (maybe304(req, res, CATALOG_LAST_MODIFIED)) return;
-  const now = Date.now();
-  if (SITEMAP_CACHE.body && SITEMAP_CACHE.exp > now) {
-    return res.type('application/xml').send(SITEMAP_CACHE.body);
-  }
-  const today = new Date().toISOString().slice(0, 10);
-  const staticPages = [
-    { loc: '/',          priority: '1.0', freq: 'daily'   },
-    { loc: '/designs',   priority: '0.9', freq: 'daily'   },
-    { loc: '/murals',    priority: '0.7', freq: 'weekly'  },
-    { loc: '/compare',   priority: '0.5', freq: 'monthly' },
-    { loc: '/samples',   priority: '0.6', freq: 'monthly' },
-    { loc: '/about',     priority: '0.5', freq: 'monthly' },
-    // /provenance was merged into /about — removed from sitemap, route below
-    // 301s any inbound link to /about so external citations don't 404.
-  ];
-  const urls = [];
-  for (const p of staticPages) {
-    urls.push(`  <url><loc>https://${SITE}${p.loc}</loc><changefreq>${p.freq}</changefreq><priority>${p.priority}</priority><lastmod>${today}</lastmod></url>`);
-  }
-  // Note: is_published is currently false for every row in designs.json (admin
-  // hasn't run the publish step yet) but /designs renders them all — so the
-  // sitemap mirrors the live catalog. If/when a "draft" flag is introduced,
-  // gate here.
-  for (const d of DESIGNS) {
-    const sat = Math.max(0, Math.min(1, Number(d.saturation) || 0));
-    const hasRoom = (d.room_mockups || []).length > 0 ? 0.1 : 0;
-    const prio = Math.min(0.9, 0.5 + 0.3 * sat + hasRoom).toFixed(2);
-    const lastmod = (d.created_at || '').slice(0, 10) || today;
-    urls.push(`  <url><loc>https://${SITE}/design/${d.id}</loc><changefreq>monthly</changefreq><priority>${prio}</priority><lastmod>${lastmod}</lastmod></url>`);
-  }
-  const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls.join('\n')}\n</urlset>\n`;
-  SITEMAP_CACHE = { body: xml, exp: now + 60 * 60 * 1000 };
-  res.type('application/xml').send(xml);
-});
-
-// ── Load design catalog (snapshot from DB — never stale-live Shopify)
-let DESIGNS = [];
-let CATALOG_LAST_MODIFIED = new Date();  // freshest design.created_at across the catalog
-
-// Failed-generation guard — design ids whose image is a near-white blank
-// (a botched batch, e.g. the drunk-animals run, ids ~7679+). Built by
-// scripts/detect-blank-designs.js → data/blank-designs.json. Excluded from
-// every grid/listing so an empty batch can't paper page 1 with blank tiles.
-let BLANK_IDS = new Set();
-function loadBlankIds() {
-  try {
-    const j = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'blank-designs.json'), 'utf8'));
-    BLANK_IDS = new Set(j.blank_ids || []);
-    console.log(`[blank-guard] ${BLANK_IDS.size} blank designs excluded from listings`);
-  } catch (e) {
-    BLANK_IDS = new Set();
-    console.error('[blank-guard] no blank-designs.json — blank filter inactive:', e.message);
-  }
-}
-
-// ── Aesthetic gate (Steve 2026-05-19) ───────────────────────────────────────
-// Defence-in-depth against neon + multi-hue patterns. The DB-level purge
-// (scripts/purge-neon-and-multicolor.js) already removed the offenders, but
-// this runtime filter protects against any reseed / refresh_designs_snapshot
-// run that might re-introduce a row matching the rule. Drops:
-//   - design where any palette entry is neon (HSL S≥70, L 45-75, pct≥5)
-//   - design where palette has ≥ 4 distinct hue buckets
-//   - design where dominant_hex alone is neon (single-color fallback for
-//     rows without a full palette)
-//   - design where prompt / motifs / tags match the neon/rainbow regex
-function _hexToHsl(hex) {
-  if (!hex || typeof hex !== 'string') return null;
-  const m = hex.trim().match(/^#?([0-9a-f]{6})$/i);
-  if (!m) return null;
-  const n = parseInt(m[1], 16);
-  const r = ((n >> 16) & 0xff) / 255;
-  const g = ((n >> 8)  & 0xff) / 255;
-  const b = ( n        & 0xff) / 255;
-  const mx = Math.max(r, g, b), mn = Math.min(r, g, b);
-  const l  = (mx + mn) / 2;
-  let h = 0, s = 0;
-  if (mx !== mn) {
-    const d = mx - mn;
-    s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
-    switch (mx) {
-      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
-      case g: h = (b - r) / d + 2; break;
-      case b: h = (r - g) / d + 4; break;
-    }
-    h *= 60;
-  }
-  return { h, s: s * 100, l: l * 100 };
-}
-function _hueBucket(hex) {
-  const hsl = _hexToHsl(hex);
-  if (!hsl) return 'unknown';
-  if (hsl.s < 12 || hsl.l < 10 || hsl.l > 90) return 'neutral';
-  const h = hsl.h;
-  if (h < 15) return 'red'; if (h < 45) return 'orange'; if (h < 65) return 'yellow';
-  if (h < 90) return 'chartreuse'; if (h < 150) return 'green'; if (h < 180) return 'teal';
-  if (h < 210) return 'cyan'; if (h < 250) return 'blue'; if (h < 280) return 'indigo';
-  if (h < 310) return 'violet'; if (h < 340) return 'magenta'; return 'red';
-}
-function _isNeonHex(hex, pct) {
-  const hsl = _hexToHsl(hex);
-  if (!hsl) return false;
-  return hsl.s >= 70 && hsl.l >= 45 && hsl.l <= 75 && (pct == null || pct >= 5);
-}
-const _AESTHETIC_GATE_RE = /\b(neon|fluorescent|electric|rainbow|day[- ]?glo|highlighter|fluoro)\b/i;
-function passesAestheticGate(d) {
-  if (!d) return false;
-  // 1) palette pass (if present)
-  const palette = Array.isArray(d.palette) ? d.palette : null;
-  if (palette) {
-    const buckets = new Set();
-    for (const p of palette) {
-      const b = _hueBucket(p.hex);
-      if (b !== 'neutral' && b !== 'unknown') buckets.add(b);
-      if (_isNeonHex(p.hex, p.pct)) return false;
-    }
-    if (buckets.size >= 4) return false;
-  } else if (d.dominant_hex) {
-    // 2) single-color fallback when no palette is available
-    if (_isNeonHex(d.dominant_hex, 100)) return false;
-  }
-  // 3) Prompt/tags/motifs text-regex was REMOVED 2026-05-20. It was dropping
-  // 134/212 valid designs because of stray "fluorescent" / "electric" / etc.
-  // in legitimate anti-prompt language. Palette check (above) is authoritative
-  // for neon detection; the text regex was too brittle. If a design has
-  // chromatic neon hex codes, the palette filter catches it. The text never
-  // needed to be a filter — it was belt-and-suspenders that became too strict.
-  return true;
-}
-
-function loadDesigns() {
-  try {
-    const raw = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
-    // Hide soft-removed designs, then drop anything that fails the aesthetic
-    // gate (neon / >3 hues / rainbow tags).
-    const beforeGate = raw.filter(d => !d.user_removed);
-    DESIGNS = beforeGate.filter(passesAestheticGate);
-    const dropped = beforeGate.length - DESIGNS.length;
-    if (dropped > 0) console.log(`[aesthetic-gate] dropped ${dropped} designs at load (neon/multi-hue/rainbow)`);
-    let max = 0;
-    for (const d of DESIGNS) {
-      const t = Date.parse(d.created_at || '');
-      if (t && t > max) max = t;
-    }
-    CATALOG_LAST_MODIFIED = max ? new Date(max) : new Date();
-  } catch (e) {
-    console.error('Failed to load designs.json:', e.message);
-    DESIGNS = [];
-  }
-  loadBlankIds();
-}
-loadDesigns();
-
-// Conditional-GET helper: if If-Modified-Since header matches our lastmod (1-second
-// precision since HTTP-date drops millis), emit 304 with empty body. Crawlers like
-// Googlebot/Bingbot use this to skip full-body downloads on repeat visits.
-// Returns true if a 304 was sent (caller should bail). HEAD requests handled too.
-function maybe304(req, res, lastModifiedDate) {
-  if (!lastModifiedDate || isNaN(lastModifiedDate.getTime())) return false;
-  // HTTP-date truncates to 1s; floor both sides to match.
-  const lastMs = Math.floor(lastModifiedDate.getTime() / 1000) * 1000;
-  res.setHeader('Last-Modified', new Date(lastMs).toUTCString());
-  const ims = req.headers['if-modified-since'];
-  if (ims) {
-    const imsMs = Date.parse(ims);
-    if (!isNaN(imsMs) && imsMs >= lastMs) {
-      res.status(304).end();
-      return true;
-    }
-  }
-  return false;
-}
-
-// ── Levenshtein distance (early-exit at cap). Used for did-you-mean suggestions.
-function _lev(a, b, cap) {
-  if (a === b) return 0;
-  if (!a.length) return b.length;
-  if (!b.length) return a.length;
-  if (Math.abs(a.length - b.length) > cap) return cap + 1;
-  let prev = new Array(b.length + 1);
-  for (let j = 0; j <= b.length; j++) prev[j] = j;
-  for (let i = 1; i <= a.length; i++) {
-    let cur = [i];
-    let rowMin = i;
-    for (let j = 1; j <= b.length; j++) {
-      const cost = a[i-1] === b[j-1] ? 0 : 1;
-      const v = Math.min(prev[j] + 1, cur[j-1] + 1, prev[j-1] + cost);
-      cur.push(v);
-      if (v < rowMin) rowMin = v;
-    }
-    if (rowMin > cap) return cap + 1;
-    prev = cur;
-  }
-  return prev[b.length];
-}
-
-// ── Same-origin proxy for the Design Coordinate UI block. The upstream
-// pairs.designerwallcoverings.com returns valid JSON but its prod CORS
-// allowlist doesn't include 127.0.0.1:9792 / localhost, so browsers
-// silently fall into the .catch() ("Could not load coordinates right
-// now."). Proxy through wallco-ai so the call is same-origin.
-const PAIRS_UPSTREAM = process.env.PAIRS_UPSTREAM || 'https://pairs.designerwallcoverings.com';
-app.get('/api/coordinates/pairs', async (req, res) => {
-  try {
-    const qs = new URLSearchParams();
-    for (const [k, v] of Object.entries(req.query)) {
-      if (v !== undefined && v !== null) qs.set(k, String(v));
-    }
-    const r = await fetch(`${PAIRS_UPSTREAM}/api/pairs?${qs.toString()}`, {
-      headers: { 'User-Agent': 'wallco-ai/proxy' },
-    });
-    const txt = await r.text();
-    res.status(r.status).type(r.headers.get('content-type') || 'application/json').send(txt);
-  } catch (e) {
-    res.status(502).json({ ok: false, error: 'upstream_unreachable', detail: e.message });
-  }
-});
-app.get('/api/coordinates/ai', async (req, res) => {
-  try {
-    const qs = new URLSearchParams();
-    for (const [k, v] of Object.entries(req.query)) {
-      if (v !== undefined && v !== null) qs.set(k, String(v));
-    }
-    const r = await fetch(`${PAIRS_UPSTREAM}/api/ai-coordinates?${qs.toString()}`, {
-      headers: { 'User-Agent': 'wallco-ai/proxy' },
-    });
-    const txt = await r.text();
-    res.status(r.status).type(r.headers.get('content-type') || 'application/json').send(txt);
-  } catch (e) {
-    res.status(502).json({ ok: false, error: 'upstream_unreachable', detail: e.message });
-  }
-});
-
-// ── Lightweight in-memory search across the catalog.
-// /api/designs/search?q=<term>&limit=20
-// Scores each design on motif (3), title (2), category/handle (1) substring match.
-// Returns top-N by score then by saturation. Pure read-only, no DB.
-// On 0 hits, suggests near-matches via Levenshtein over motif + category vocab.
-app.get('/api/designs/search', (req, res) => {
-  const qRaw = String(req.query.q || '').toLowerCase().trim();
-  const limit = Math.max(1, Math.min(50, parseInt(req.query.limit || '20', 10)));
-  if (!qRaw || qRaw.length < 2) {
-    return res.json({ q: qRaw, limit, count: 0, results: [], note: 'q must be ≥2 chars' });
-  }
-  // Conditional GET on the catalog freshness so crawlers benefit.
-  if (maybe304(req, res, CATALOG_LAST_MODIFIED)) return;
-  res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
-  const terms = qRaw.split(/\s+/).filter(Boolean);
-  const out = [];
-  for (const d of DESIGNS) {
-    if (BLANK_IDS.has(d.id)) continue;   // skip failed-generation blanks
-    const title = (d.title || '').toLowerCase();
-    const category = (d.category || '').toLowerCase();
-    const handle = (d.handle || '').toLowerCase();
-    const motifs = (d.motifs || []).map(m => m.toLowerCase());
-    let score = 0;
-    for (const t of terms) {
-      if (motifs.some(m => m === t))      score += 5;     // exact motif match — strongest
-      else if (motifs.some(m => m.includes(t))) score += 3;
-      if (title.includes(t))    score += 2;
-      if (category === t)       score += 2;
-      else if (category.includes(t)) score += 1;
-      if (handle.includes(t))   score += 1;
-    }
-    if (score > 0) {
-      out.push({
-        id: d.id,
-        title: d.title,
-        category: d.category,
-        handle: d.handle,
-        image_url: d.image_url,
-        dominant_hex: d.dominant_hex,
-        room_mockups: d.room_mockups || [],
-        score,
-        saturation: d.saturation || 0,
-        url: `/design/${d.id}`,
-      });
-    }
-  }
-  // Did-you-mean: if no hits, search the vocabulary for near-matches to each term.
-  // Cap=2 edit distance; cheapest 5 unique candidates.
-  let didYouMean = [];
-  if (out.length === 0) {
-    const vocab = new Set();
-    for (const d of DESIGNS) {
-      (d.motifs || []).forEach(m => { const v = String(m).toLowerCase().trim(); if (v.length >= 3) vocab.add(v); });
-      if (d.category) vocab.add(String(d.category).toLowerCase().trim());
-    }
-    const candidates = new Map();
-    const cap = 2;
-    for (const t of terms) {
-      if (t.length < 3) continue; // too short — Levenshtein noise
-      for (const w of vocab) {
-        const d = _lev(t, w, cap);
-        if (d <= cap && w !== t) {
-          const prev = candidates.get(w);
-          if (prev === undefined || d < prev) candidates.set(w, d);
-        }
-      }
-    }
-    didYouMean = Array.from(candidates.entries())
-      .sort((a, b) => a[1] - b[1] || a[0].localeCompare(b[0]))
-      .slice(0, 5)
-      .map(([word, distance]) => ({ word, distance }));
-  }
-
-  out.sort((a, b) => b.score - a.score || b.saturation - a.saturation);
-  // Facet counts — rolled up from the matched designs.
-  // Lets a UI show "Refine: rose (12) · peony (8) · scrollwork (5)" alongside results.
-  const motifTally = Object.create(null);
-  const categoryTally = Object.create(null);
-  for (const r of out) {
-    // r holds the projected shape; pull the source design back for full motifs list.
-    const src = DESIGNS.find(d => d.id === r.id);
-    if (!src) continue;
-    for (const m of (src.motifs || [])) {
-      const k = String(m).toLowerCase().trim();
-      if (k) motifTally[k] = (motifTally[k] || 0) + 1;
-    }
-    if (src.category) categoryTally[src.category] = (categoryTally[src.category] || 0) + 1;
-  }
-  const topMotifs = Object.entries(motifTally)
-    .sort((a, b) => b[1] - a[1])
-    .slice(0, 8)
-    .map(([motif, count]) => ({ motif, count }));
-  const topCategories = Object.entries(categoryTally)
-    .sort((a, b) => b[1] - a[1])
-    .map(([category, count]) => ({ category, count }));
-  // Log the search query (audit + future analytics).  Only logs when q≥2 chars
-  // so the early-return path above doesn't double-log empty probes.
-  logEvent(req, 'designs_search', { q: qRaw, count: out.length, did_you_mean: didYouMean.map(d => d.word) });
-  res.json({
-    q: qRaw,
-    limit,
-    count: out.length,
-    results: out.slice(0, limit),
-    facets: {
-      motifs: topMotifs,
-      categories: topCategories,
-    },
-    did_you_mean: didYouMean,
-  });
-});
-
-// ── /api/designs/by-color?hex=%23c16d74&n=12&exclude=72
-// Returns N closest-in-color designs to a target hex via CIELAB ΔE distance.
-// Useful for "more like this in this palette" rails on the PDP. Read-only, no DB.
-function _hexToLab(hex) {
-  let h = String(hex || '').replace('#','').trim();
-  if (h.length === 3) h = h.split('').map(c => c+c).join('');
-  if (!/^[0-9a-f]{6}$/i.test(h)) return null;
-  let r = parseInt(h.slice(0,2),16)/255, g = parseInt(h.slice(2,4),16)/255, b = parseInt(h.slice(4,6),16)/255;
-  // sRGB → linear
-  [r,g,b] = [r,g,b].map(v => v <= 0.04045 ? v/12.92 : Math.pow((v+0.055)/1.055, 2.4));
-  // linear → XYZ (D65)
-  const X = (r*0.4124564 + g*0.3575761 + b*0.1804375) / 0.95047;
-  const Y = (r*0.2126729 + g*0.7151522 + b*0.0721750) / 1.00000;
-  const Z = (r*0.0193339 + g*0.1191920 + b*0.9503041) / 1.08883;
-  // XYZ → Lab
-  const f = (t) => t > 0.008856 ? Math.cbrt(t) : (7.787*t + 16/116);
-  const fx = f(X), fy = f(Y), fz = f(Z);
-  return [ 116*fy - 16, 500*(fx-fy), 200*(fy-fz) ];
-}
-function _dE(a, b) {
-  const dL = a[0]-b[0], dA = a[1]-b[1], dB = a[2]-b[2];
-  return Math.sqrt(dL*dL + dA*dA + dB*dB);
-}
-// In-memory TTL cache for /api/designs/by-color. The Lab conversion + ΔE sort
-// runs over every DESIGN on every call — the same PDP rail hex + size combo
-// gets hammered as users browse, so cache the JSON body for the same 5min
-// window the response already advertises in Cache-Control. Bounded to 1k keys
-// FIFO so memory can't grow unbounded; invalidated whenever DESIGNS is rebuilt
-// (callers should bump _byColorCacheVer if they mutate DESIGNS in place).
-const _BY_COLOR_TTL_MS = 300_000;
-const _BY_COLOR_MAX = 1000;
-const _byColorCache = new Map();   // key -> { body, expiresAt }
-let   _byColorCacheVer = 0;        // bump to invalidate
-const _byColorStats = { hits: 0, misses: 0, evictions: 0, since: Date.now() };
-// /api/designs/random ETag stats — only seeded calls participate (unseeded is no-store).
-// "hits" = 304 returned (client revalidated), "misses" = full 200 with body.
-const _randomStats = { hits: 0, misses: 0, since: Date.now() };
-app.get('/api/designs/by-color', (req, res) => {
-  const hex = String(req.query.hex || '').trim();
-  const n = Math.max(1, Math.min(50, parseInt(req.query.n || '12', 10)));
-  const excludeArr = String(req.query.exclude || '').split(',').map(s => parseInt(s.trim(),10)).filter(x => x>0).sort((a,b)=>a-b);
-  const exclude = new Set(excludeArr);
-  // Optional ?within=category — restricts results to designs in this category.
-  // Useful for "more in this palette WITHIN damask" rails on a vertical PDP.
-  const within = String(req.query.within || '').toLowerCase().trim();
-  const target = _hexToLab(hex);
-  if (!target) return res.status(400).json({ ok:false, error:'invalid hex (try ?hex=%23c16d74 or hex=c16d74)' });
-  // Cache key sorts exclude so e.g. exclude=72,5 and exclude=5,72 hit the same entry.
-  const cacheKey = `v${_byColorCacheVer}|${hex.toLowerCase()}|${n}|${within}|${excludeArr.join(',')}`;
-  // ETag wraps the same key — deterministic given (catalog version, hex, n, within, sorted excludes).
-  // Lets browsers/CDNs skip the JSON body transfer on revalidation; pairs with the in-memory cache
-  // (cache saves recompute, ETag saves the bytes on the wire). Wrapped in double quotes per RFC.
-  const _bcEtag = `"bc-${CATALOG_LAST_MODIFIED.getTime()}-${cacheKey}"`;
-  if (req.headers['if-none-match'] === _bcEtag) {
-    _byColorStats.hits++;
-    res.setHeader('ETag', _bcEtag);
-    res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
-    res.setHeader('X-Cache', 'HIT-ETAG');
-    return res.status(304).end();
-  }
-  const now = Date.now();
-  const hit = _byColorCache.get(cacheKey);
-  if (hit && hit.expiresAt > now) {
-    _byColorStats.hits++;
-    res.setHeader('ETag', _bcEtag);
-    res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
-    res.setHeader('X-Cache', 'HIT');
-    return res.json(hit.body);
-  }
-  if (hit) _byColorCache.delete(cacheKey);   // expired — drop before recompute
-  _byColorStats.misses++;
-  const ranked = [];
-  for (const d of DESIGNS) {
-    if (exclude.has(d.id)) continue;
-    if (BLANK_IDS.has(d.id)) continue;   // skip failed-generation blanks
-    if (!d.dominant_hex) continue;
-    if (within && (d.category || '').toLowerCase() !== within) continue;
-    const lab = _hexToLab(d.dominant_hex);
-    if (!lab) continue;
-    ranked.push({ id: d.id, title: d.title, category: d.category, handle: d.handle, image_url: d.image_url, dominant_hex: d.dominant_hex, room_mockups: d.room_mockups || [], delta_e: _dE(target, lab), url: `/design/${d.id}` });
-  }
-  ranked.sort((a,b) => a.delta_e - b.delta_e);
-  const body = { hex, n, within: within || null, count: ranked.length, exclude: [...exclude], results: ranked.slice(0, n).map(r => ({ ...r, delta_e: Math.round(r.delta_e * 10)/10 })) };
-  // FIFO evict if full. Map preserves insertion order, so the first key is the oldest.
-  if (_byColorCache.size >= _BY_COLOR_MAX) {
-    const firstKey = _byColorCache.keys().next().value;
-    if (firstKey !== undefined) { _byColorCache.delete(firstKey); _byColorStats.evictions++; }
-  }
-  _byColorCache.set(cacheKey, { body, expiresAt: now + _BY_COLOR_TTL_MS });
-  res.setHeader('ETag', _bcEtag);
-  res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
-  res.setHeader('X-Cache', 'MISS');
-  res.json(body);
-});
-
-// ── /api/designs/random?n=12&category=floral&seed=42
-// Returns N random designs. Useful for "Surprise me" rails + empty-state discovery.
-// Optional ?category= filter. Optional ?seed= for reproducible shuffles (otherwise time-seeded).
-// Cache-Control: no-store — randomness is the point; caching defeats it.
-app.get('/api/designs/random', (req, res) => {
-  const n = Math.max(1, Math.min(50, parseInt(req.query.n || '12', 10)));
-  const category = String(req.query.category || '').toLowerCase().trim();
-  const seedRaw = req.query.seed;
-  // Hard filter — same as /api/designs. Never feed Hot-or-not or random
-  // grids a design whose image file isn't on disk. Steve's rule 2026-05-13.
-  const onDisk = DESIGNS.filter(designHasImage);
-  const pool = category
-    ? onDisk.filter(d => (d.category || '').toLowerCase() === category)
-    : onDisk.slice();
-  // Deterministic seeded PRNG (mulberry32) if seed provided; else Math.random.
-  let rand;
-  if (seedRaw !== undefined && seedRaw !== '') {
-    let s = parseInt(seedRaw, 10) >>> 0;
-    rand = function() {
-      s |= 0; s = (s + 0x6D2B79F5) | 0;
-      let t = s;
-      t = Math.imul(t ^ (t >>> 15), t | 1);
-      t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
-      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
-    };
-  } else {
-    rand = Math.random;
-  }
-  // Partial Fisher-Yates — picks first N positions of shuffle without touching the rest.
-  const sample = [];
-  const idx = pool.map((_, i) => i);
-  const k = Math.min(n, idx.length);
-  for (let i = 0; i < k; i++) {
-    const j = i + Math.floor(rand() * (idx.length - i));
-    [idx[i], idx[j]] = [idx[j], idx[i]];
-    const d = pool[idx[i]];
-    sample.push({
-      id: d.id,
-      title: d.title,
-      category: d.category,
-      handle: d.handle,
-      image_url: d.image_url,
-      dominant_hex: d.dominant_hex,
-      room_mockups: d.room_mockups || [],
-      url: `/design/${d.id}`,
-    });
-  }
-  // Seeded calls are deterministic given (n, category, seed, DESIGNS state) — make them
-  // cacheable via ETag so a trade-user rail can poll without paying the shuffle cost.
-  // Unseeded calls stay no-store because the whole point is fresh randomness.
-  if (seedRaw !== undefined && seedRaw !== '') {
-    const etag = `"rand-${CATALOG_LAST_MODIFIED.getTime()}-${k}-${category || 'all'}-${parseInt(seedRaw,10)}-${pool.length}"`;
-    res.setHeader('ETag', etag);
-    res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
-    const inm = req.headers['if-none-match'];
-    if (inm && inm === etag) { _randomStats.hits++; return res.status(304).end(); }
-    _randomStats.misses++;
-  } else {
-    res.setHeader('Cache-Control', 'no-store');
-  }
-  res.json({
-    n: k,
-    pool_size: pool.length,
-    category: category || null,
-    seed: seedRaw !== undefined && seedRaw !== '' ? parseInt(seedRaw,10) : null,
-    results: sample,
-  });
-});
-
-// ── Color sort helpers
-function hexToHSL(hex) {
-  if (!hex) return [0, 0, 0.5];
-  const h = hex.replace('#', '');
-  const r = parseInt(h.slice(0,2),16)/255;
-  const g = parseInt(h.slice(2,4),16)/255;
-  const b = parseInt(h.slice(4,6),16)/255;
-  const max = Math.max(r,g,b), min = Math.min(r,g,b);
-  const l = (max+min)/2;
-  let hue = 0, sat = 0;
-  if (max !== min) {
-    const d = max - min;
-    sat = l > 0.5 ? d/(2-max-min) : d/(max+min);
-    if (max===r) hue = (g-b)/d + (g<b?6:0);
-    else if (max===g) hue = (b-r)/d + 2;
-    else hue = (r-g)/d + 4;
-    hue *= 60;
-  }
-  return [hue, sat, l];
-}
-
-// Saturation (0..1) from hex — used by /design/:id PG fallback.
-function hexSaturation(hex) { return Math.round(hexToHSL(hex)[1] * 1000) / 1000; }
-
-// ── Snapshot-style title generator (mirror of refresh_designs_snapshot.py).
-// Used by /design/:id PG fallback so freshly-baked designs land with proper
-// "Honey Garden No.83"-style titles, not the raw prompt prefix.
-const HUE_NAMES = [
-  [  0, 18, 'Crimson'], [ 18, 38, 'Amber'],  [ 38, 58, 'Honey'],
-  [ 58, 78, 'Olive'],   [ 78,160, 'Sage'],   [160,200, 'Teal'],
-  [200,255, 'Sapphire'],[255,295, 'Violet'], [295,335, 'Rose'],
-  [335,360, 'Crimson']
-];
-const NOUNS = {
-  floral:    ['Bouquet','Blossom','Garden','Floret','Botanical','Bloom'],
-  geometric: ['Lattice','Tessellation','Geometry','Meridian','Compass','Mosaic'],
-  damask:    ['Damask','Baroque','Heirloom','Salon','Atrium','Reverie'],
-  mixed:     ['Atelier','Studio','Origin','Folio','Sketch','Edit'],
-  'art deco':       ['Deco','Fan','Sunburst','Chevron','Marquee'],
-  'arts and crafts':['Morris','Crafts','Bough','Trellis','Vine'],
-  'drunk-animals':  ['Hiccup','Tipple','Tableau','Reverie','Soirée','Vignette','Carouse','Caprice','Saunter','Folly'],
-  'stoned-animals': ['Stealie','Trip','Stardust','Daydream','Vespers','Hazefield','Sundrift','Spore','Lullaby','Halcyon'],
-  // Iksel / de Gournay / Mural Sources luxe-mural vocabulary.
-  'mural-animals':  ['Mural','Panel','Pavillon','Atelier','Hand-Painted','Vignette','Chinoiserie','Heirloom','Salon','Reverie'],
-  // Panoramic scenic — Zuber et Cie / Gracie / de Gournay tradition.
-  'mural-scenic':   ['Scenic','Panorama','Vista','Frieze','Tapestry','Folly','Allée','Belvedere','Promenade','Pavillon']
-};
-function hueName(hex) {
-  if (!hex) return 'Bone';
-  const [h, s] = hexToHSL(hex);
-  if (s < 0.06) return 'Noir';
-  for (const r of HUE_NAMES) if (h >= r[0] && h < r[1]) return r[2];
-  return 'Bone';
-}
-function titleFor(cat, hex, id) {
-  const pool = NOUNS[(cat || 'mixed').toLowerCase()] || NOUNS.mixed;
-  return `${hueName(hex)} ${pool[id % pool.length]} No.${id}`;
-}
-
-// Card aria-label — folds title/category/dominant hue + room-preview count into
-// a single sentence so screen readers announce all the visible affordances of a
-// card in one read. Replaces the silence on the background-image divs.
-function cardAriaLabel(d) {
-  const hue = hueName(d.dominant_hex || '').toLowerCase();
-  const cat = (d.category || 'pattern').toLowerCase();
-  const rooms = (d.room_mockups || []).length;
-  const roomTxt = rooms > 1 ? `, ${rooms} room previews available`
-                 : rooms === 1 ? ', 1 room preview available' : '';
-  return `${d.title} — ${hue} ${cat} wallpaper${roomTxt}. View details.`;
-}
-
-function sortDesigns(designs, sort) {
-  const d = [...designs];
-  // Designs with a pre-generated room preview surface first within any sort —
-  // they're the "in-context" winners visitors should encounter first. Tie-break
-  // falls through to whatever sort the user picked.
-  const hasRoom = x => ((x.room_mockups || []).length > 0) ? 0 : 1;
-  const tie = (cmp) => (a,b) => {
-    const r = hasRoom(a) - hasRoom(b);
-    return r !== 0 ? r : cmp(a,b);
-  };
-  // Saturation helper — HSL S channel; neutrals fall to the end.
-  const sat = x => { try { return (hexToHSL(x.dominant_hex)||[0,0,0])[1] || 0; } catch { return 0; } };
-  switch (sort) {
-    case 'oldest':       return d.sort((a,b) => (a.id||0) - (b.id||0));
-    case 'title':        return d.sort(tie((a,b) => a.title.localeCompare(b.title)));
-    case 'title-desc':   return d.sort(tie((a,b) => b.title.localeCompare(a.title)));
-    case 'category':     return d.sort(tie((a,b) => a.category.localeCompare(b.category)));
-    case 'category-desc':return d.sort(tie((a,b) => b.category.localeCompare(a.category)));
-    case 'light-dark':   return d.sort(tie((a,b) => hexToHSL(b.dominant_hex)[2] - hexToHSL(a.dominant_hex)[2]));
-    case 'dark-light':   return d.sort(tie((a,b) => hexToHSL(a.dominant_hex)[2] - hexToHSL(b.dominant_hex)[2]));
-    case 'vivid':        return d.sort(tie((a,b) => sat(b) - sat(a)));
-    case 'muted':        return d.sort(tie((a,b) => sat(a) - sat(b)));
-    case 'has-room':     return d.sort((a,b) => {
-      const r = hasRoom(a) - hasRoom(b);
-      return r !== 0 ? r : (b.id||0) - (a.id||0);
-    });
-    case 'random': {
-      // Deterministic-per-request shuffle — keeps pagination consistent within
-      // a session by seeding off the floor of the current minute.
-      const seed = Math.floor(Date.now() / 60000);
-      const rand = (n) => { const x = Math.sin(seed * 9301 + n * 49297) * 233280; return x - Math.floor(x); };
-      return d.map((x,i) => ({ x, k: rand(i) })).sort((a,b) => a.k - b.k).map(o => o.x);
-    }
-    case 'color-wheel': return d.sort(tie((a,b) => {
-      const [ha,,la] = hexToHSL(a.dominant_hex);
-      const [hb,,lb] = hexToHSL(b.dominant_hex);
-      const bA = Math.floor(ha/30), bB = Math.floor(hb/30);
-      return bA !== bB ? bA-bB : la-lb;
-    }));
-    // newest = strict highest id first. NO has-room tie-break — when a
-    // visitor explicitly asks for newest, hide-no-rooms-promote-rooms would
-    // sandbag the actual latest designs (the drunk-animals + stoned-animals
-    // currently generating don't have room_mockups yet).
-    default: return d.sort((a,b) => (b.id||0) - (a.id||0));
-  }
-}
-
-// ── Go-Live status (admin checklist)
-app.get('/admin/golive.json', async (_req, res) => {
-  try {
-    // Run the same module the CLI uses, but inline so we can stream
-    const { main } = require('./scripts/golive_status');
-    // Capture stdout from main() — easiest: run the same probes here
-    const out = execSync(`node ${path.join(__dirname, 'scripts/golive_status.js')}`, { encoding: 'utf8', timeout: 25000 });
-    res.type('application/json').send(out.trim());
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-app.get('/admin/golive', (_req, res) => {
-  res.type('html').send(`<!doctype html>
-<html lang="en"><head>
-<meta charset="utf-8"><title>wallco.ai — Go-Live Checklist</title>
-<style>
-  body { font:14px/1.5 -apple-system,system-ui,sans-serif; margin:0; padding:24px 32px; background:#0f0e0c; color:#e8e2d6; }
-  h1 { font-weight:300; font-size:24px; margin:0 0 4px; letter-spacing:.5px; }
-  .sub { color:#888; font-size:12px; margin-bottom:24px; }
-  .step { background:#1a1816; border:1px solid #2a2825; border-radius:6px; padding:14px 18px; margin-bottom:10px; display:flex; gap:14px; align-items:flex-start; }
-  .step.ok    { border-left:4px solid #6dbe6d; }
-  .step.fail  { border-left:4px solid #c0584a; }
-  .step.warn  { border-left:4px solid #d2b15c; }
-  .step .ico  { font-size:20px; line-height:1; min-width:24px; }
-  .step .body { flex:1; }
-  .step .title { font-weight:500; margin-bottom:2px; }
-  .step .desc  { color:#888; font-size:12px; }
-  .step .cmd   { font-family:ui-monospace,monospace; font-size:11.5px; background:#0a0907; padding:6px 10px; border-radius:3px; margin-top:6px; color:#bba; display:inline-block; }
-  .step .btn   { background:#2a2825; color:#d2b15c; border:1px solid #3a3631; padding:5px 10px; border-radius:3px; font-size:11.5px; cursor:pointer; margin-top:6px; text-decoration:none; display:inline-block; }
-  .step .btn:hover { background:#3a3631; color:#fff; }
-  .reload { background:#2a2825; color:#d2b15c; border:1px solid #3a3631; padding:7px 14px; border-radius:3px; cursor:pointer; font-size:12px; margin-bottom:18px; }
-  .stale { color:#888; font-size:11px; }
-  pre.json { background:#0a0907; padding:12px; border-radius:4px; font-size:11px; color:#bba; max-height:300px; overflow:auto; }
-</style></head>
-<body>
-<h1>wallco.ai — Go-Live Checklist</h1>
-<div class="sub">Auto-refreshes every 30 s. Each step is idempotent.</div>
-<button class="reload" onclick="load(true)">↻ Refresh now</button>
-<div id="checklist">Loading…</div>
-<details style="margin-top:24px"><summary style="color:#888;cursor:pointer;font-size:12px">raw status JSON</summary><pre class="json" id="raw"></pre></details>
-<script>
-const STEPS = [
-  { key:'zone',     title:'1. Cloudflare zone exists',     desc:'CF API can\'t create zones with current token scope. One-time manual click.',
-    fix:'Open <a href="https://dash.cloudflare.com" target="_blank" style="color:#d2b15c" rel="noopener noreferrer nofollow">dash.cloudflare.com</a> → Add a site → wallco.ai → Free plan. Then refresh this page.' },
-  { key:'dns',      title:'2. DNS records (A, www, MX, SPF, DKIM, DMARC)', desc:'Six required records on the CF zone. Run after step 1 succeeds.',
-    cmd:'bash scripts/golive.sh dns' },
-  { key:'ns',       title:'3. GoDaddy NS swapped to Cloudflare', desc:'Domain still on ns41/42.domaincontrol.com — must switch to the CF nameservers.',
-    cmd:'bash scripts/golive.sh ns-swap' },
-  { key:'origin',   title:'4. Kamatera origin /health OK',  desc:'pm2 wallco-ai on :9905, nginx vhost.', cmd:null },
-  { key:'ssl',      title:'5. CF SSL mode = Full (Strict) + Always-HTTPS', desc:'Run after DNS propagates.',
-    cmd:'bash scripts/golive.sh cf-ssl' },
-  { key:'https',    title:'6. https://wallco.ai/health 200', desc:'Certbot must have issued LE cert.',
-    cmd:'bash scripts/golive.sh cert' },
-  { key:'ga4',      title:'7. GA4 gtag in HTML',            desc:'Property G-6V7HSH6K1M.' },
-  { key:'inlineEd', title:'8. Inline editor gate (401 anon)', desc:'Admin-cookie required for /api/_inline/save.' }
-];
-
-function rowClass(ok) { return ok ? 'ok' : 'fail'; }
-function ico(ok) { return ok ? '✅' : '⬜️'; }
-
-async function load(forceFresh) {
-  const r = await fetch('/admin/golive.json' + (forceFresh ? '?t=' + Date.now() : ''));
-  const j = await r.json();
-  const s = j.steps || {};
-  const cl = document.getElementById('checklist');
-  cl.innerHTML = STEPS.map(st => {
-    const v = s[st.key] || { ok: false };
-    const klass = rowClass(v.ok);
-    let detail = '';
-    if (st.key === 'zone' && !v.ok)        detail = '<div class="desc" style="margin-top:6px">' + st.fix + '</div>';
-    if (st.key === 'zone' && v.ok)         detail = '<div class="desc" style="margin-top:6px">zone_id: ' + (v.zone_id||'?') + ' · NS: ' + (v.name_servers||[]).join(', ') + '</div>';
-    if (st.key === 'dns' && !v.ok && v.reason !== 'no_zone') detail = '<div class="desc">a:' + (v.a||0) + ' www:' + (v.www||0) + ' mx:' + (v.mx||0) + ' spf:' + (v.spf||0) + ' dkim:' + (v.dkim||0) + ' dmarc:' + (v.dmarc||0) + '</div>';
-    if (st.key === 'ns')   detail = '<div class="desc">current: ' + ((v.current||[]).join(', ') || '?') + '</div>';
-    if (st.key === 'https' && v.http) detail = '<div class="desc">HTTP ' + v.http + (v.reason ? ' · ' + v.reason : '') + '</div>';
-    if (st.key === 'origin' && v.http)detail = '<div class="desc">HTTP ' + v.http + '</div>';
-    if (st.key === 'ga4' && v.measurement_id) detail = '<div class="desc">' + v.measurement_id + '</div>';
-    const cmdLine = st.cmd ? '<div class="cmd">' + st.cmd + '</div>' : '';
-    return '<div class="step ' + klass + '"><div class="ico">' + ico(v.ok) + '</div><div class="body"><div class="title">' + st.title + '</div><div class="desc">' + st.desc + '</div>' + detail + cmdLine + '</div></div>';
-  }).join('');
-  document.getElementById('raw').textContent = JSON.stringify(j, null, 2);
-}
-load();
-setInterval(() => load(true), 30000);
-</script></body></html>`);
-});
-
-// ── Admin gate (cookie-verified JWT + localhost) — moved to src/admin-gate.js
-// after security audit 2026-05-12 flagged the previous inline base64-decode-only
-// check as a CRITICAL bypass (anyone could mint a token with {role:"admin"}).
-const { isAdmin, requireAdmin } = require('./src/admin-gate');
-
-// ── Fliepaper Bugs — Gucci Resort Edition (6 patterns × 6 colorways = 36 cells)
-// Settlement-gated regen routes a job file into data/fliepaper-bugs/queue/ that
-// yolo-runner picks up. Public collection page + admin grid; placeholders show
-// while yolo-runner generates art.
-try { require('./src/fliepaper-bugs').mount(app, { isAdmin }); }
-catch (e) { console.error('Fliepaper-bugs mount failed:', e.message); }
-
-// ── /library — 57k professional vendor wallpapers from dw_unified
-// surfaced as a first-class catalog under the Designer Wallcoverings brand.
-// Vendor names are NEVER rendered. See src/library.js for the redaction +
-// sort + render pipeline. Cache builds via scripts/build-library-cache.js.
-try { require('./src/library').mount(app, { htmlHead, htmlHeader, getDesigns: () => DESIGNS }); }
-catch (e) { console.error('Library mount failed:', e.message); }
-
-// ── /admin/reload-designs — re-read data/designs.json without a pm2 reload.
-// Localhost-admin per src/admin-gate. Used by drunk-animals tick + future
-// generators to surface freshly persisted designs in the live grid without
-// restarting the server.
-app.post('/admin/reload-designs', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
-  const before = DESIGNS.length;
-  loadDesigns();
-  // Bump the by-color cache version + clear in-memory entries so stale palette/ΔE results
-  // can't outlive the new catalog state. ETags rebuild against the new CATALOG_LAST_MODIFIED.
-  _byColorCacheVer++;
-  _byColorCache.clear();
-  res.json({ ok: true, before, after: DESIGNS.length, catalog_last_modified: CATALOG_LAST_MODIFIED, by_color_cache_ver: _byColorCacheVer });
-});
-
-// ── Inspirations gallery (admin-only) ─────────────────────────────────────
-// Side-by-side viewer: real archival seed (vendor visible) ON LEFT, the
-// AI-generated wallco design (NO vendor leak) ON RIGHT. Powered by the new
-// scripts/inspiration_generator.js pipeline. Vendor identity is ADMIN-ONLY —
-// surfaced here, NEVER on /design/:id.
-app.get('/admin/inspirations', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
-  res.sendFile(path.join(__dirname, 'public', 'admin', 'inspirations.html'));
-});
-
-app.get('/api/admin/inspirations/list', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const limit  = Math.min(Math.max(parseInt(req.query.limit  || '50', 10), 1), 200);
-  const offset = Math.max(parseInt(req.query.offset || '0',  10), 0);
-  try {
-    const sql = `
-      SELECT json_agg(t) FROM (
-        SELECT
-          d.id, d.inspired_by_sku, d.inspired_by_vendor,
-          d.dominant_hex, d.palette, d.is_published, d.image_url,
-          d.local_path, d.created_at, d.prompt, d.notes,
-          sp.title         AS seed_title,
-          sp.image_url     AS seed_image_url,
-          sp.handle        AS seed_handle,
-          sp.product_type  AS seed_product_type,
-          sce.dominant_hex AS seed_dominant_hex,
-          sce.hex_codes    AS seed_hex_codes,
-          sce.design_era   AS seed_era
-        FROM all_designs d
-        LEFT JOIN shopify_products sp
-          ON sp.sku     = d.inspired_by_sku
-          OR sp.mfr_sku = d.inspired_by_sku
-          OR sp.handle  = d.inspired_by_sku
-        LEFT JOIN shopify_color_enrichment sce
-          ON sce.shopify_id = sp.shopify_id
-        WHERE d.inspired_by_sku IS NOT NULL
-        ORDER BY d.id DESC
-        LIMIT ${limit} OFFSET ${offset}
-      ) t;
-    `;
-    const raw = psqlQuery(sql);
-    const rows = raw ? JSON.parse(raw) : [];
-    res.json({ ok: true, count: (rows || []).length, rows: rows || [] });
-  } catch (e) {
-    console.error('inspirations list failed:', e.message);
-    res.status(500).json({ error: 'query failed', detail: e.message.slice(0, 200) });
-  }
-});
-
-// Per-design inspired-by lookup — minimal, NO vendor name in response so the
-// public-facing /design/:id template can use it safely. Vendor identity stays
-// behind the admin gate on /api/admin/inspirations/list.
-app.get('/api/design/:id/inspired-by-badge', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id)) return res.status(400).json({ error: 'bad id' });
-  try {
-    const sql = `SELECT (inspired_by_sku IS NOT NULL) AS inspired FROM all_designs WHERE id=${id};`;
-    const v = psqlQuery(sql);
-    const inspired = (v === 't' || v === 'true' || v === 'TRUE');
-    res.set('Cache-Control', 'public, max-age=600');
-    res.json({ inspired });
-  } catch (e) {
-    res.json({ inspired: false });
-  }
-});
-
-// Trigger another generation from the same seed (admin-only). Spawns the
-// generator as a detached child so the HTTP response returns fast.
-app.post('/api/admin/inspirations/regenerate', express.json(), (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const sku = String(req.body?.sku || '').trim();
-  if (!sku) return res.status(400).json({ error: 'sku required' });
-  try {
-    const child = require('child_process').spawn(
-      'node',
-      [path.join(__dirname, 'scripts', 'inspiration_generator.js'), '--n', '1'],
-      { detached: true, stdio: 'ignore',
-        env: { ...process.env, INSPIRATION_BUDGET_N: '1', INSPIRATION_REGEN_SKU: sku } }
-    );
-    child.unref();
-    res.json({ ok: true, queued: true, sku });
-  } catch (e) {
-    res.status(500).json({ error: 'spawn failed', detail: e.message });
-  }
-});
-
-// ── Merge ────────────────────────────────────────────────────────────────
-// Take 1-3 designs (catalog IDs and/or uploaded files), pull element
-// descriptions from each, fuse into a single SDXL prompt, generate a new
-// design that visually pulls from all sources. Saved with category='merged'.
-const mergeUpload = require('multer')({
-  dest: path.join(__dirname, 'data', 'merge-uploads'),
-  limits: { fileSize: 20 * 1024 * 1024, files: 3 },
-});
-
-app.get('/merge', (_req, res) => {
-  res.type('html').send(`<!doctype html>
-<html lang="en"><head><meta charset="utf-8">
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<title>Merge designs — wallco.ai</title>
-<style>
-:root { --ink:#2a1f10; --line:#d8d0c0; --gold:#c9a14b; --accent:#1a1a1a; --bg:#fff; --card:#f8f4ec; }
-body { margin:0; background:var(--bg); color:var(--ink); font:14px -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif }
-header { padding:20px 32px; border-bottom:1px solid var(--line); display:flex; gap:16px; align-items:baseline }
-header h1 { font-family:'Playfair Display','Didot',serif; font-weight:400; font-size:26px; margin:0 }
-header .meta { color:#888; font-size:13px }
-header a { margin-left:auto; color:var(--ink); text-decoration:none; font-size:13px; padding:6px 14px; border:1px solid var(--line); border-radius:14px }
-main { max-width:1080px; margin:0 auto; padding:32px 24px }
-.slots { display:grid; grid-template-columns:repeat(3,1fr); gap:18px; margin-bottom:22px }
-@media (max-width:760px) { .slots { grid-template-columns:1fr } }
-.slot { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:14px; min-height:240px; display:flex; flex-direction:column; gap:8px }
-.slot h3 { margin:0; font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:#7a6e5a }
-.slot .preview { flex:1; min-height:160px; background-size:cover; background-position:center; background-color:#fff; border:1px dashed var(--line); border-radius:6px; display:flex; align-items:center; justify-content:center; color:#bba; font-size:12px }
-.slot input[type=text], .slot input[type=file] { width:100%; padding:6px 8px; border:1px solid var(--line); border-radius:4px; font:12px var(--sans); background:#fff }
-.controls { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:14px }
-.controls textarea { flex:1; min-width:280px; padding:8px 10px; border:1px solid var(--line); border-radius:4px; font:13px monospace; min-height:54px }
-button.go { padding:12px 28px; background:var(--accent); color:#f4ece0; border:0; border-radius:6px; font:13px var(--sans); letter-spacing:.06em; text-transform:uppercase; cursor:pointer }
-button.go[disabled] { opacity:.55; cursor:wait }
-.status { font-size:12px; color:#888 }
-.result { margin-top:24px; padding:16px; border:1px solid var(--line); border-radius:8px; background:var(--card); display:none }
-.result.show { display:block }
-.result img { max-width:100%; border:1px solid var(--line); border-radius:6px; display:block; margin:10px 0 }
-</style>
-</head><body>
-<header>
-  <h1>Merge designs</h1>
-  <span class="meta">Pull elements from 1-3 designs into one new generation</span>
-  <a href="/designs">← Grid</a>
-</header>
-<main>
-  <p style="color:#666;line-height:1.6">Drop in up to 3 source images (uploads) or paste catalog design IDs. The merger pulls each design's motifs / color story / style and fuses them into a single new SDXL generation. Saved to the catalog as <code>category='merged'</code>.</p>
-
-  <div class="slots">
-    ${[1,2,3].map(n => `
-      <div class="slot" data-slot="${n}">
-        <h3>Source ${n}</h3>
-        <div class="preview" id="prev-${n}">drop image or paste design ID</div>
-        <label style="font-size:11px;color:#888">Catalog ID
-          <input type="text" id="src-id-${n}" placeholder="e.g. 1537" inputmode="numeric">
-        </label>
-        <label style="font-size:11px;color:#888">— or upload —
-          <input type="file" id="src-file-${n}" accept="image/png,image/jpeg,image/webp">
-        </label>
-      </div>
-    `).join('')}
-  </div>
-
-  <div class="controls">
-    <textarea id="extra-hint" placeholder="Optional extra direction — e.g. 'in a Persian miniature manner, dusty palette, mural-scale'"></textarea>
-    <button class="go" id="btn-merge" type="button">Merge & Generate</button>
-    <span class="status" id="status"></span>
-  </div>
-
-  <div class="result" id="result">
-    <strong>Merged design</strong>
-    <p id="result-meta" style="margin:6px 0;font-size:12px;color:#888"></p>
-    <img id="result-img" alt="merged">
-    <p><a id="result-link" href="#" style="color:var(--accent);font-weight:600">Open on the design page →</a></p>
-  </div>
-</main>
-<script>
-// Preview helpers — show image when ID is entered or file is picked
-[1,2,3].forEach(function(n){
-  var idIn = document.getElementById('src-id-'+n);
-  var fileIn = document.getElementById('src-file-'+n);
-  var prev = document.getElementById('prev-'+n);
-  idIn.addEventListener('input', function(){
-    var v = idIn.value.replace(/\\D/g,'');
-    if (v && parseInt(v,10) > 0) {
-      prev.style.backgroundImage = "url('/designs/img/by-id/" + v + "')";
-      // Fallback: just show the URL — server will respond if it exists
-      var img = new Image();
-      img.onload = function(){ prev.style.backgroundImage = "url('"+img.src+"')"; prev.textContent=''; };
-      img.onerror = function(){ prev.style.backgroundImage = ''; prev.textContent = '#'+v+' not found'; };
-      img.src = '/designs/img/by-id/' + v;
-      fileIn.value = '';
-    } else { prev.style.backgroundImage = ''; prev.textContent = 'drop image or paste design ID'; }
-  });
-  fileIn.addEventListener('change', function(){
-    var f = fileIn.files && fileIn.files[0]; if (!f) return;
-    var reader = new FileReader();
-    reader.onload = function(){ prev.style.backgroundImage = "url('"+reader.result+"')"; prev.textContent=''; };
-    reader.readAsDataURL(f);
-    idIn.value = '';
-  });
-});
-
-document.getElementById('btn-merge').addEventListener('click', function(){
-  var btn = this, status = document.getElementById('status');
-  var fd = new FormData();
-  var hint = document.getElementById('extra-hint').value.trim();
-  if (hint) fd.append('hint', hint);
-  var n = 0;
-  [1,2,3].forEach(function(i){
-    var id = (document.getElementById('src-id-'+i).value||'').replace(/\\D/g,'');
-    var f = document.getElementById('src-file-'+i).files[0];
-    if (id) { fd.append('source_ids', id); n++; }
-    else if (f) { fd.append('uploads', f); n++; }
-  });
-  if (n === 0) { status.textContent = 'Add at least one source'; return; }
-  status.textContent = 'Merging ~30-60s…'; btn.disabled = true;
-  fetch('/api/merge', { method:'POST', body: fd })
-    .then(function(r){ return r.json(); })
-    .then(function(j){
-      btn.disabled = false;
-      if (!j.ok) { status.textContent = 'Error: ' + (j.error||'unknown'); return; }
-      status.textContent = 'Merged → design #' + j.design_id;
-      var res = document.getElementById('result');
-      document.getElementById('result-meta').textContent =
-        'Sources: ' + (j.source_count||n) + ' · seed ' + j.seed + ' · ' + j.prompt.slice(0,80) + '…';
-      document.getElementById('result-img').src = j.image_url + '?t=' + Date.now();
-      document.getElementById('result-link').href = '/design/' + j.design_id;
-      res.classList.add('show');
-      res.scrollIntoView({ behavior:'smooth' });
-    })
-    .catch(function(e){ btn.disabled = false; status.textContent = 'Error: ' + e.message; });
-});
-</script>
-</body></html>`);
-});
-
-// Helper: serve a design image by id (used by /merge previews + new cards).
-// Prefer the in-memory DESIGNS snapshot (sourced from designs.json which IS
-// synced across Mac2 → Kamatera). Fall back to PG only if not in cache.
-// Important — local_path is a Mac2-absolute path (`/Users/stevestudio2/…`)
-// and won't resolve on Kamatera. Strip to basename and resolve against the
-// local IMG_DIR so the route works on both Mac2 and prod.
-app.get('/designs/img/by-id/:id', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(404).end();
-  let lp = null;
-  // 1) try DESIGNS array (matches designs.json, platform-agnostic)
-  const d = DESIGNS.find(x => x.id === id);
-  if (d && d.local_path) lp = d.local_path;
-  // 2) fallback to PG (only useful on the box where the row was inserted)
-  if (!lp) {
-    try {
-      lp = psqlExecLocal(`SELECT local_path FROM spoon_all_designs WHERE id=${id}`);
-    } catch {}
-  }
-  if (!lp) return res.status(404).end();
-  // local_path may be an absolute Mac2 path that doesn't exist on prod —
-  // resolve by basename against the local IMG_DIR.
-  const basename = String(lp).split('/').pop();
-  const candidate1 = path.resolve(lp);
-  const candidate2 = path.join(IMG_DIR, basename);
-  const finalPath = fs.existsSync(candidate1) ? candidate1
-                  : fs.existsSync(candidate2) ? candidate2
-                  : null;
-  if (!finalPath) return res.status(404).end();
-  res.sendFile(finalPath);
-});
-
-app.post('/api/merge', mergeUpload.array('uploads', 3), async (req, res) => {
-  try {
-    const sourceIds = []
-      .concat(req.body.source_ids || [])
-      .map(s => parseInt(s, 10))
-      .filter(n => Number.isFinite(n) && n > 0);
-    const uploads = (req.files || []);
-    const totalSources = sourceIds.length + uploads.length;
-    if (totalSources === 0) return res.status(400).json({ ok: false, error: 'no sources' });
-    if (totalSources > 3) return res.status(400).json({ ok: false, error: 'max 3 sources' });
-    const hint = String(req.body.hint || '').slice(0, 400);
-
-    // Pull a short element-description from each source.
-    // - Catalog IDs: read prompt + dominant_hex + motifs straight from PG (free).
-    // - Uploads: skip llava for now; just note "user-uploaded reference N" and
-    //   include the file in the prompt as an inspiration (textual cue only —
-    //   SDXL won't ingest the image without img2img; this is a future bump).
-    const fragments = [];
-    const palettes = [];
-    for (const sid of sourceIds) {
-      const row = psqlExecLocal(
-        `SELECT json_build_object('prompt', prompt, 'dominant_hex', dominant_hex, 'motifs', motifs, 'category', category)::text
-         FROM spoon_all_designs WHERE id=${sid}`
-      );
-      if (!row) continue;
-      let r; try { r = JSON.parse(row); } catch { continue; }
-      // Truncate the original prompt aggressively — keep first ~120 chars
-      const seed = (r.prompt || '').slice(0, 160).replace(/,?\s*NO\s.*$/i, '').trim();
-      fragments.push(`[source #${sid}: ${seed}${(r.motifs || []).length ? ' · motifs: ' + r.motifs.slice(0,4).join(', ') : ''}]`);
-      if (r.dominant_hex) palettes.push(r.dominant_hex);
-    }
-    for (let i = 0; i < uploads.length; i++) {
-      fragments.push(`[user reference #${i+1}: blend its overall feel and palette]`);
-    }
-    const mergedPrompt = [
-      `A new wallcovering design that visually fuses elements from ${totalSources} reference${totalSources>1?'s':''}:`,
-      fragments.join(' '),
-      palettes.length ? `Tie the palette around these dominants: ${palettes.join(', ')}.` : '',
-      hint ? `Direction: ${hint}.` : '',
-      'Refined hand-painted execution, seamless wallpaper repeat, archival quality, no text, no watermark.',
-    ].filter(Boolean).join(' ');
-
-    // Fire generate_designs.js with the merged prompt. category='merged'.
-    const { spawnSync } = require('child_process');
-    const r = spawnSync('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1',
-      '--kind', 'seamless_tile',
-      '--category', 'merged',
-      '--prompts', mergedPrompt,
-    ], { encoding: 'utf8', cwd: __dirname, timeout: 180_000 });
-    if (r.status !== 0) {
-      return res.status(500).json({ ok: false, error: 'generator failed: ' + (r.stderr || r.stdout || '').slice(-200) });
-    }
-    // Pull the newest 'merged' row — that's the one we just made.
-    const idRaw = psqlExecLocal(`SELECT id FROM spoon_all_designs WHERE category='merged' ORDER BY id DESC LIMIT 1`);
-    const newId = parseInt(idRaw, 10);
-    const seedRaw = psqlExecLocal(`SELECT json_build_object('seed', seed, 'local_path', local_path)::text FROM spoon_all_designs WHERE id=${newId}`);
-    let meta = {}; try { meta = JSON.parse(seedRaw); } catch {}
-    const filename = (meta.local_path || '').split('/').pop();
-    res.json({
-      ok: true, design_id: newId,
-      seed: meta.seed,
-      prompt: mergedPrompt,
-      image_url: '/designs/img/' + filename,
-      source_count: totalSources,
-    });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── License + download flow ────────────────────────────────────────────
-// One-time $950 infinite-use license per mural-scenic panel. Modeled on
-// Hannah's archive licensing — pay once, perpetual unlimited use, the buyer
-// downloads the 150dpi upscaled file.
-//
-// Flow:
-//   POST /api/design/:id/license/checkout  body {email}
-//       → creates Stripe Checkout Session ($950 one-time), returns redirect URL
-//       → inserts pending row in design_licenses
-//   GET  /api/design/:id/license/return?session_id=...
-//       → Stripe redirects here on success, marks status=paid, granted_at=now
-//       → redirects to /design/:id?licensed=1
-//   POST /api/stripe/webhook
-//       → backstop for async confirmation (idempotent against return-url)
-//   GET  /api/design/:id/license/status?email=...
-//       → JSON { has_license, license_id }
-//   GET  /api/design/:id/download?email=...
-//       → Verifies paid license. Streams the upscaled file. If not yet
-//         upscaled, kicks Replicate Real-ESRGAN x4 and waits.
-const LICENSE_PRICE_CENTS = parseInt(process.env.WALLCO_LICENSE_PRICE_CENTS || '95000', 10);
-const STRIPE_SECRET_KEY = (process.env.STRIPE_SECRET_KEY || '').trim();
-const PUBLIC_BASE_URL = (process.env.PUBLIC_BASE_URL || 'https://wallco.ai').replace(/\/$/, '');
-
-async function stripeCall(pathname, params) {
-  if (!STRIPE_SECRET_KEY) throw new Error('STRIPE_SECRET_KEY not configured');
-  const body = new URLSearchParams();
-  function flatten(obj, prefix = '') {
-    for (const [k, v] of Object.entries(obj)) {
-      const key = prefix ? `${prefix}[${k}]` : k;
-      if (v === undefined || v === null) continue;
-      if (typeof v === 'object' && !Array.isArray(v)) flatten(v, key);
-      else if (Array.isArray(v)) v.forEach((vv, i) => flatten({ [i]: vv }, key));
-      else body.append(key, String(v));
-    }
-  }
-  flatten(params);
-  const r = await fetch('https://api.stripe.com' + pathname, {
-    method: 'POST',
-    headers: { 'Authorization': 'Bearer ' + STRIPE_SECRET_KEY, 'Content-Type': 'application/x-www-form-urlencoded' },
-    body: body.toString()
-  });
-  const j = await r.json();
-  if (!r.ok) throw new Error(`Stripe ${pathname}: ${j.error?.message || r.status}`);
-  return j;
-}
-
-app.post('/api/design/:id/license/checkout', express.json({ limit: '4kb' }), async (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  const email = String(req.body?.email || '').trim().toLowerCase();
-  if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
-    return res.status(400).json({ error: 'valid email required' });
-  }
-  if (!STRIPE_SECRET_KEY) return res.status(500).json({ error: 'payments not configured' });
-  // Look up design title for the line item description
-  const design = DESIGNS.find(d => d.id === id);
-  const title = design?.title || `Wallco Mural #${id}`;
-  try {
-    const session = await stripeCall('/v1/checkout/sessions', {
-      mode: 'payment',
-      customer_email: email,
-      payment_method_types: ['card'],
-      line_items: [{
-        quantity: 1,
-        price_data: {
-          currency: 'usd',
-          unit_amount: LICENSE_PRICE_CENTS,
-          product_data: {
-            name: `${title} · Infinite-use license`,
-            description: `One-time payment for unlimited use of the 20ft × 11ft panoramic mural at 150 dpi. Perpetual. Download access included.`
-          }
-        }
-      }],
-      success_url: `${PUBLIC_BASE_URL}/api/design/${id}/license/return?session_id={CHECKOUT_SESSION_ID}`,
-      cancel_url:  `${PUBLIC_BASE_URL}/design/${id}?canceled=1`,
-      metadata: { design_id: String(id), user_email: email, license_type: 'infinite' },
-    });
-    psqlExecLocal(`INSERT INTO design_licenses
-      (design_id, user_email, stripe_session_id, amount_cents, license_type, status)
-      VALUES (${id}, '${email.replace(/'/g, "''")}', '${session.id}', ${LICENSE_PRICE_CENTS}, 'infinite', 'pending')`);
-    res.json({ ok: true, checkout_url: session.url });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-app.get('/api/design/:id/license/return', async (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  const sid = String(req.query.session_id || '');
-  if (!sid) return res.redirect(`/design/${id}?error=missing_session`);
-  try {
-    // Verify with Stripe that the session was paid
-    const r = await fetch('https://api.stripe.com/v1/checkout/sessions/' + encodeURIComponent(sid), {
-      headers: { 'Authorization': 'Bearer ' + STRIPE_SECRET_KEY }
-    });
-    const session = await r.json();
-    if (session.payment_status !== 'paid') {
-      return res.redirect(`/design/${id}?error=not_paid`);
-    }
-    psqlExecLocal(`UPDATE design_licenses
-       SET status='paid', granted_at=now(), stripe_payment_intent='${(session.payment_intent || '').replace(/'/g, "''")}'
-       WHERE stripe_session_id='${sid.replace(/'/g, "''")}'`);
-    const email = encodeURIComponent(session.customer_details?.email || session.customer_email || '');
-    res.redirect(`/design/${id}?licensed=1&email=${email}`);
-  } catch (e) {
-    res.redirect(`/design/${id}?error=${encodeURIComponent(e.message)}`);
-  }
-});
-
-app.get('/api/design/:id/license/status', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  const email = String(req.query.email || '').trim().toLowerCase();
-  if (!email) return res.json({ has_license: false });
-  try {
-    const row = psqlExecLocal(
-      `SELECT id FROM design_licenses WHERE design_id=${id} AND lower(user_email)='${email.replace(/'/g, "''")}' AND status='paid' LIMIT 1`
-    );
-    res.json({ has_license: !!row, license_id: row ? parseInt(row, 10) : null });
-  } catch (e) { res.json({ has_license: false }); }
-});
-
-app.get('/api/design/:id/download', async (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  const email = String(req.query.email || '').trim().toLowerCase();
-  if (!email) return res.status(403).type('text/plain').send('email required');
-  try {
-    // Verify paid license
-    const row = psqlExecLocal(
-      `SELECT id, upscaled_path FROM design_licenses WHERE design_id=${id} AND lower(user_email)='${email.replace(/'/g, "''")}' AND status='paid' ORDER BY id DESC LIMIT 1`
-    );
-    if (!row) return res.status(403).type('text/plain').send('no active license — purchase first');
-    const [licId, upPath] = row.split('|');
-    if (upPath && fs.existsSync(upPath)) {
-      psqlExecLocal(`UPDATE design_licenses SET download_count=download_count+1, last_downloaded_at=now() WHERE id=${licId}`);
-      res.setHeader('Content-Disposition', `attachment; filename="wallco-${id}-150dpi.png"`);
-      return res.sendFile(path.resolve(upPath));
-    }
-    // No upscale yet — kick Real-ESRGAN, wait for result. We do it inline so
-    // the user's request hangs until ready (single download, simple flow).
-    // Replicate nightmareai/real-esrgan x4 cost ~$0.03 per upscale.
-    const srcRow = psqlExecLocal(`SELECT local_path FROM spoon_all_designs WHERE id=${id}`);
-    if (!srcRow || !fs.existsSync(srcRow)) {
-      return res.status(500).type('text/plain').send('source image missing');
-    }
-    res.setHeader('Cache-Control', 'no-store');
-    const upsDir = path.join(__dirname, 'data', 'upscaled');
-    fs.mkdirSync(upsDir, { recursive: true });
-    const outPath = path.join(upsDir, `${id}-150dpi.png`);
-    // For the first pass we run Real-ESRGAN via Replicate if creds available;
-    // otherwise we ship the SDXL native with a note. Real-ESRGAN integration
-    // is the next iteration.
-    const replicateToken = (process.env.REPLICATE_API_TOKEN || '').trim() ||
-      (fs.existsSync(path.join(require('os').homedir(),'Projects/animate-museum-posts/.env'))
-        ? (fs.readFileSync(path.join(require('os').homedir(),'Projects/animate-museum-posts/.env'),'utf8').match(/^REPLICATE_API_TOKEN=(\S+)/m)||[])[1] : '');
-    if (!replicateToken) {
-      // Fallback: ship the SDXL native, mark download_count
-      psqlExecLocal(`UPDATE design_licenses SET download_count=download_count+1, last_downloaded_at=now() WHERE id=${licId}`);
-      res.setHeader('Content-Disposition', `attachment; filename="wallco-${id}-native.png"`);
-      return res.sendFile(path.resolve(srcRow));
-    }
-    // Kick Real-ESRGAN x4
-    const buf = fs.readFileSync(srcRow);
-    const b64 = 'data:image/png;base64,' + buf.toString('base64');
-    const submit = await fetch('https://api.replicate.com/v1/predictions', {
-      method: 'POST',
-      headers: { 'Authorization': 'Bearer ' + replicateToken, 'Content-Type': 'application/json' },
-      body: JSON.stringify({
-        // nightmareai/real-esrgan model
-        version: 'f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa',
-        input: { image: b64, scale: 4, face_enhance: false }
-      })
-    });
-    const pred = await submit.json();
-    if (!pred.id) return res.status(500).type('text/plain').send('upscale start failed: ' + JSON.stringify(pred));
-    let final;
-    const t0 = Date.now();
-    while (Date.now() - t0 < 5 * 60_000) {
-      await new Promise(r => setTimeout(r, 4000));
-      const p = await (await fetch('https://api.replicate.com/v1/predictions/' + pred.id, {
-        headers: { 'Authorization': 'Bearer ' + replicateToken }
-      })).json();
-      if (p.status === 'succeeded') { final = p; break; }
-      if (p.status === 'failed' || p.status === 'canceled') {
-        return res.status(500).type('text/plain').send('upscale failed: ' + JSON.stringify(p.error||p.logs||'').slice(0,200));
-      }
-    }
-    if (!final) return res.status(504).type('text/plain').send('upscale timeout');
-    const imgUrl = Array.isArray(final.output) ? final.output[0] : final.output;
-    const imgBuf = Buffer.from(await (await fetch(imgUrl)).arrayBuffer());
-    fs.writeFileSync(outPath, imgBuf);
-    psqlExecLocal(`UPDATE design_licenses
-       SET upscaled_path='${outPath.replace(/'/g, "''")}', upscaled_at=now(),
-           download_count=download_count+1, last_downloaded_at=now()
-       WHERE id=${licId}`);
-    res.setHeader('Content-Disposition', `attachment; filename="wallco-${id}-150dpi.png"`);
-    res.sendFile(path.resolve(outPath));
-  } catch (e) {
-    res.status(500).type('text/plain').send('download error: ' + e.message);
-  }
-});
-
-// ── User feedback endpoints — color/style/scale yes-or-no toggles, 1-5
-// star rating, and soft-remove. Persists to spoon_all_designs.user_*
-// columns (added 2026-05-13). Feedback fuels future prompt-tuning so the
-// LLM gets better at what we actually want.
-//
-// POST /api/design/:id/rate  body: { color_good?, style_good?, scale_good?, stars? }
-//   Localhost admin gate. Each field is optional — only what's sent gets
-//   updated. Boolean fields accept true/false/null (null = unset).
-function psqlExecLocal(sql) {
-  // Was: spawnSync('psql', ['dw_unified', ...]) — broken on prod where pm2
-  // runs as root and PG has no 'root' role. Switched to the same PSQL_CMD
-  // path psqlQuery() uses, which sudos to postgres on Linux.
-  return execSync(PSQL_CMD + ' -c ' + JSON.stringify(sql), { encoding: 'utf8' }).trim();
-}
-app.post('/api/design/:id/rate', express.json({ limit: '4kb' }), (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  const b = req.body || {};
-  const sets = [];
-  function setBool(field, val) {
-    if (val === null) sets.push(`${field}=NULL`);
-    else if (val === true || val === false) sets.push(`${field}=${val}`);
-  }
-  setBool('user_color_good', b.color_good);
-  setBool('user_style_good', b.style_good);
-  setBool('user_scale_good', b.scale_good);
-  if (b.stars === null) sets.push('user_stars=NULL');
-  else if (Number.isInteger(b.stars) && b.stars >= 1 && b.stars <= 5) sets.push(`user_stars=${b.stars}`);
-  if (!sets.length) return res.status(400).json({ error: 'nothing to update' });
-  sets.push('user_rated_at=now()');
-  try {
-    psqlExecLocal(`UPDATE spoon_all_designs SET ${sets.join(', ')} WHERE id=${id} RETURNING id;`);
-    // Mirror back into the in-memory DESIGNS so the grid sort sees it
-    // immediately (no full snapshot refresh needed).
-    const d = DESIGNS.find(x => x.id === id);
-    if (d) {
-      if (b.color_good !== undefined) d.user_color_good = b.color_good;
-      if (b.style_good !== undefined) d.user_style_good = b.style_good;
-      if (b.scale_good !== undefined) d.user_scale_good = b.scale_good;
-      if (b.stars      !== undefined) d.user_stars      = b.stars;
-    }
-    res.json({ ok: true, id });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// POST /api/design/:id/remove  — soft-remove a design from public listings.
-// Sets user_removed=true. Reverse with ?undo=1.
-app.post('/api/design/:id/remove', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  const undo = req.query.undo === '1' || req.query.undo === 'true';
-  const val = undo ? 'FALSE' : 'TRUE';
-  try {
-    psqlExecLocal(`UPDATE spoon_all_designs SET user_removed=${val} WHERE id=${id};`);
-    // Remove from / re-add to the in-memory list. Easiest path: filter.
-    if (!undo) {
-      const idx = DESIGNS.findIndex(x => x.id === id);
-      if (idx >= 0) DESIGNS.splice(idx, 1);
-    }
-    res.json({ ok: true, id, removed: !undo });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// ── TRADE LOGIN (separate from admin layer) — magic-link, no passwords.
-// Sister cookie to dw_auth; different name (`wallco_trade_session`), different
-// table (wallco_trade_users), so admin and trade tiers never collide.
-// Per Steve's standing rule: NO autonomous email sending. The magic link is
-// logged to console + echoed in the API response. Wire SES/sendgrid later.
-const jwt = require('jsonwebtoken');
-const WALLCO_JWT_SECRET = process.env.WALLCO_JWT_SECRET
-  || process.env.JWT_SECRET
-  || (function(){
-       console.warn('[trade-login] WALLCO_JWT_SECRET / JWT_SECRET unset — using dev fallback. Set one of these in .env for prod.');
-       return 'wallco-trade-dev-secret-CHANGE-IN-PROD';
-     })();
-const TRADE_COOKIE = 'wallco_trade_session';
-
-// Minimal cookie reader. cookie-parser isn't in deps and `req.cookies` is
-// undefined on non-admin paths. Pull what we need straight from the header.
-function readCookie(req, name) {
-  const raw = req.headers.cookie || '';
-  if (!raw) return null;
-  for (const part of raw.split(/;\s*/)) {
-    const eq = part.indexOf('=');
-    if (eq < 0) continue;
-    const k = part.slice(0, eq);
-    if (k === name) return decodeURIComponent(part.slice(eq + 1));
-  }
-  return null;
-}
-
-// Returns { id, email, name } or null. Never throws.
-function getTradeUser(req) {
-  const token = readCookie(req, TRADE_COOKIE);
-  if (!token) return null;
-  try {
-    const decoded = jwt.verify(token, WALLCO_JWT_SECRET, { algorithms: ['HS256'] });
-    if (!decoded || !decoded.id || !decoded.email) return null;
-    return { id: decoded.id, email: decoded.email, name: decoded.name || '' };
-  } catch {
-    return null;  // expired, bad signature, malformed
-  }
-}
-
-// Migrations — wallco_trade_users + wallco_saved_designs.
-// Idempotent CREATE IF NOT EXISTS, run at server start. Mirrors the
-// design_ratings / studio_renders init pattern below.
-function ensureTradeUserTables() {
-  try {
-    psqlQuery(`CREATE TABLE IF NOT EXISTS wallco_trade_users (
-      id          SERIAL PRIMARY KEY,
-      email       TEXT UNIQUE NOT NULL,
-      name        TEXT,
-      company     TEXT,
-      phone       TEXT,
-      magic_token TEXT,
-      token_exp   TIMESTAMPTZ,
-      last_login  TIMESTAMPTZ,
-      created_at  TIMESTAMPTZ DEFAULT NOW()
-    );`);
-    psqlQuery(`CREATE TABLE IF NOT EXISTS wallco_saved_designs (
-      user_id    INTEGER REFERENCES wallco_trade_users(id) ON DELETE CASCADE,
-      design_id  INTEGER NOT NULL,
-      note       TEXT,
-      saved_at   TIMESTAMPTZ DEFAULT NOW(),
-      PRIMARY KEY (user_id, design_id)
-    );`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS wallco_saved_designs_user_idx
-                 ON wallco_saved_designs(user_id, saved_at DESC);`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS wallco_trade_users_token_idx
-                 ON wallco_trade_users(magic_token) WHERE magic_token IS NOT NULL;`);
-    console.log('  Trade-user tables ready (wallco_trade_users + wallco_saved_designs)');
-  } catch (e) {
-    console.error('[trade-login] table init failed:', e.message);
-  }
-}
-ensureTradeUserTables();
-
-// ── Ensure user_removed column on spoon_all_designs ─────────────────
-// The /api/design/:id/remove endpoint writes user_removed=TRUE to this
-// column. On prod, the column was missing — UPDATE returned "column does
-// not exist" and the soft-remove silently failed. Add it idempotently.
-function ensureUserRemovedColumn() {
-  try {
-    psqlQuery(`ALTER TABLE spoon_all_designs ADD COLUMN IF NOT EXISTS user_removed BOOLEAN NOT NULL DEFAULT FALSE;`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS spoon_all_designs_user_removed_idx ON spoon_all_designs(user_removed) WHERE user_removed = TRUE;`);
-  } catch (e) {
-    console.error('[migrate] ensureUserRemovedColumn:', e.message);
-  }
-}
-ensureUserRemovedColumn();
-
-// Audit-log — every user + admin action lands here. Append-only. Steve's call:
-// trade users see only their own at /me/activity; admin sees everything at /admin/activity.
-function ensureAuditLogTable() {
-  try {
-    psqlQuery(`CREATE TABLE IF NOT EXISTS wallco_audit_log (
-      id           BIGSERIAL PRIMARY KEY,
-      user_id      INTEGER REFERENCES wallco_trade_users(id) ON DELETE SET NULL,
-      is_admin     BOOLEAN NOT NULL DEFAULT FALSE,
-      actor_label  TEXT,
-      action       TEXT NOT NULL,
-      payload      JSONB,
-      ip           INET,
-      user_agent   TEXT,
-      created_at   TIMESTAMPTZ DEFAULT NOW()
-    );`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS wallco_audit_user_idx
-                 ON wallco_audit_log(user_id, created_at DESC) WHERE user_id IS NOT NULL;`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS wallco_audit_admin_idx
-                 ON wallco_audit_log(is_admin, created_at DESC) WHERE is_admin = TRUE;`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS wallco_audit_action_idx
-                 ON wallco_audit_log(action, created_at DESC);`);
-    console.log('  Audit-log table ready (wallco_audit_log)');
-  } catch (e) {
-    console.error('[audit-log] table init failed:', e.message);
-  }
-}
-ensureAuditLogTable();
-
-// logEvent(req, action, payload) — fire-and-forget INSERT into wallco_audit_log.
-// Never throws (audit MUST NOT break the request path). Extracts actor from
-// cookies — admin > trade user > anonymous.
-function logEvent(req, action, payload) {
-  try {
-    const u = getTradeUser(req);
-    const admin = isAdmin(req);
-    const ip = (req.headers['x-forwarded-for'] || req.socket?.remoteAddress || '').toString().split(',')[0].trim() || null;
-    const ua = (req.headers['user-agent'] || '').toString().slice(0, 500);
-    const actorLabel = admin ? 'admin' : (u && u.email) ? u.email : 'anon';
-    const payloadJson = payload ? JSON.stringify(payload).slice(0, 8000) : null;
-    psqlQuery(`INSERT INTO wallco_audit_log (user_id, is_admin, actor_label, action, payload, ip, user_agent)
-               VALUES (
-                 ${u ? parseInt(u.id, 10) : 'NULL'},
-                 ${admin ? 'TRUE' : 'FALSE'},
-                 ${pgEsc(actorLabel)},
-                 ${pgEsc(action)},
-                 ${payloadJson ? pgEsc(payloadJson) + '::jsonb' : 'NULL'},
-                 ${ip ? pgEsc(ip) + '::inet' : 'NULL'},
-                 ${pgEsc(ua)}
-               );`);
-  } catch (e) {
-    // Audit failures are non-fatal; log once but never re-raise.
-    if (!global.__auditLogWarned) {
-      console.warn('[audit-log] insert failed:', e.message);
-      global.__auditLogWarned = true;
-    }
-  }
-}
-
-// HTML-escape helper for safe interpolation into trade-login templates.
-function _escTrade(s) {
-  return String(s == null ? '' : s)
-    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
-    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
-}
-
-// Small helper — set cookie `secure` flag only on HTTPS (so dev still works).
-function tradeProtoIsHttps(req) {
-  const proto = (req.headers['x-forwarded-proto'] || req.protocol || 'http').split(',')[0].trim();
-  return proto === 'https';
-}
-
-// GET /trade/login — minimal email-only form. `return` query param round-trips
-// the user back to /design/:id (or wherever they came from) after auth.
-app.get('/trade/login', (req, res) => {
-  const returnTo = String(req.query.return || '/').slice(0, 500);
-  const existing = getTradeUser(req);
-  if (existing) {
-    return res.redirect(returnTo || '/');
-  }
-  res.type('html').send(`${htmlHead({
-    title: 'Trade Login — wallco.ai',
-    description: 'Sign in to save designs, request samples, and build proposals.',
-    canonical: 'https://wallco.ai/trade/login'
-  })}
-<body>
-${htmlHeader('/trade/login')}
-<main class="prose-main" style="max-width:480px;margin:48px auto;padding:0 24px">
-  <article class="prose">
-    <h1 style="font:300 32px/1.2 'Cormorant Garamond',serif;margin:0 0 8px">Trade sign-in</h1>
-    <p style="color:var(--ink-soft,#555);font:14px/1.5 var(--sans,system-ui);margin:0 0 24px">
-      Enter your email — we'll send you a one-tap sign-in link. No passwords.
-      For designers, architects, and trade pros.
-    </p>
-    <form id="trade-login-form" autocomplete="on" novalidate>
-      <input type="hidden" name="return" value="${_escTrade(returnTo)}">
-      <label for="trade-email" style="display:block;font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);margin-bottom:6px">Email</label>
-      <input type="email" id="trade-email" name="email" required autofocus
-             placeholder="you@studio.com"
-             style="width:100%;padding:14px 16px;font:16px var(--sans,system-ui);border:1px solid var(--line,#ddd);border-radius:6px;margin-bottom:10px;box-sizing:border-box">
-      <button type="submit" class="btn-primary" style="width:100%;padding:14px;font:14px var(--sans);cursor:pointer">
-        Send sign-in link
-      </button>
-    </form>
-    <div id="trade-login-msg" role="status" aria-live="polite" style="margin-top:16px;font:13px/1.5 var(--sans);color:var(--ink-soft,#555)"></div>
-    <p style="margin-top:32px;font:11.5px/1.5 var(--sans);color:var(--ink-faint,#888)">
-      By signing in, you agree to receive trade-only communications from wallco.ai
-      (part of Designer Wallcoverings). We never sell your data.
-    </p>
-  </article>
-</main>
-${FOOTER}
-<script>
-(function(){
-  var form = document.getElementById('trade-login-form');
-  var msg  = document.getElementById('trade-login-msg');
-  if (!form) return;
-  form.addEventListener('submit', function(e){
-    e.preventDefault();
-    var email = document.getElementById('trade-email').value.trim();
-    var returnTo = form.querySelector('input[name=return]').value;
-    if (!email) { msg.textContent = 'Email required.'; return; }
-    msg.textContent = 'Generating sign-in link...';
-    fetch('/api/trade/login', {
-      method:'POST', headers:{'Content-Type':'application/json'},
-      body: JSON.stringify({ email: email, return: returnTo })
-    })
-      .then(function(r){ return r.json().then(function(j){ return {ok:r.ok, j:j}; }); })
-      .then(function(out){
-        if (!out.ok || !out.j.magic_link) {
-          msg.textContent = (out.j && out.j.error) || 'Sign-in failed. Try again.';
-          msg.style.color = '#b03a2e';
-          return;
-        }
-        msg.style.color = '';
-        msg.innerHTML = '<div style="padding:14px;background:#faf8f3;border:1px solid #e6e2d8;border-radius:6px">'
-          + '<div style="font-weight:500;margin-bottom:6px">Sign-in link ready</div>'
-          + '<div style="font:12px/1.4 var(--sans);color:var(--ink-soft);margin-bottom:8px">'
-          + 'Email sending isn’t wired yet — copy this link to sign in:'
-          + '</div>'
-          + '<a href="' + out.j.magic_link + '" style="display:block;font:12px/1.5 ui-monospace,monospace;word-break:break-all;color:var(--accent,#d2b15c);text-decoration:underline;margin-bottom:8px">'
-          + out.j.magic_link + '</a>'
-          + '<a href="' + out.j.magic_link + '" class="btn-primary" style="display:inline-block;padding:8px 14px;font:13px var(--sans);text-decoration:none">Sign in now →</a>'
-          + '</div>';
-      })
-      .catch(function(err){ msg.style.color='#b03a2e'; msg.textContent = 'Network error: ' + err.message; });
-  });
-})();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// POST /api/trade/login — generate token, store, return magic link.
-// NOTE: Steve standing rule — NO autonomous email sending. The link is
-// console-logged + returned in JSON. Steve will wire SES/sendgrid later.
-app.post('/api/trade/login', (req, res) => {
-  const email = String((req.body && req.body.email) || '').trim().toLowerCase();
-  const returnTo = String((req.body && req.body.return) || '/').slice(0, 500);
-  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
-    return res.status(400).json({ ok: false, error: 'Valid email required.' });
-  }
-  // 32-byte token, 30-minute expiry.
-  const token = crypto.randomBytes(32).toString('hex');
-  const expSql = `NOW() + INTERVAL '30 minutes'`;
-  try {
-    // Upsert user; bind a fresh token.
-    psqlQuery(`INSERT INTO wallco_trade_users (email, magic_token, token_exp)
-               VALUES (${pgEsc(email)}, ${pgEsc(token)}, ${expSql})
-               ON CONFLICT (email) DO UPDATE
-                 SET magic_token = EXCLUDED.magic_token,
-                     token_exp   = EXCLUDED.token_exp;`);
-  } catch (e) {
-    console.error('[trade-login] upsert failed:', e.message);
-    return res.status(500).json({ ok: false, error: 'Could not generate sign-in link.' });
-  }
-  // Build link from request host so dev (localhost:9792) + prod (wallco.ai) both work.
-  const proto = (req.headers['x-forwarded-proto'] || req.protocol || 'http').split(',')[0].trim();
-  const host = req.headers['x-forwarded-host'] || req.get('host');
-  const magicLink = `${proto}://${host}/trade/auth?token=${token}&return=${encodeURIComponent(returnTo)}`;
-  console.log(`[trade-login] magic link: ${magicLink} (for ${email})`);
-  logEvent(req, 'trade_login_request', { email });
-  res.json({
-    ok: true,
-    magic_link: magicLink,
-    expires_in: '30 minutes',
-    note: 'Email sending not yet configured — copy the link above.'
-  });
-});
-
-// GET /trade/auth?token=... — validate token, mint JWT, set cookie, redirect.
-app.get('/trade/auth', (req, res) => {
-  const token = String(req.query.token || '').trim();
-  const returnTo = String(req.query.return || '/').slice(0, 500);
-  if (!token || !/^[a-f0-9]{20,128}$/i.test(token)) {
-    return res.status(400).type('html').send('<h1>Invalid sign-in link</h1><p>The token is malformed. <a href="/trade/login">Try again →</a></p>');
-  }
-  let row;
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT id, email, name, token_exp, (token_exp > NOW()) AS still_valid
-      FROM wallco_trade_users WHERE magic_token = ${pgEsc(token)} LIMIT 1
-    ) t;`);
-    row = raw ? JSON.parse(raw) : null;
-  } catch (e) {
-    console.error('[trade-login] auth lookup failed:', e.message);
-    return res.status(500).type('html').send('<h1>Sign-in failed</h1><p>Server error.</p>');
-  }
-  if (!row) {
-    return res.status(404).type('html').send('<h1>Sign-in link not found</h1><p>It may have already been used. <a href="/trade/login">Get a new one →</a></p>');
-  }
-  if (!row.still_valid) {
-    return res.status(410).type('html').send('<h1>Sign-in link expired</h1><p>Links are valid for 30 minutes. <a href="/trade/login">Get a new one →</a></p>');
-  }
-  // Burn the token, stamp last_login.
-  try {
-    psqlQuery(`UPDATE wallco_trade_users
-                 SET magic_token = NULL, token_exp = NULL, last_login = NOW()
-                 WHERE id = ${parseInt(row.id, 10)};`);
-  } catch (e) {
-    console.error('[trade-login] burn-token update failed:', e.message);
-  }
-  // Mint a 14-day JWT.
-  const jwtToken = jwt.sign(
-    { id: row.id, email: row.email, name: row.name || '' },
-    WALLCO_JWT_SECRET,
-    { algorithm: 'HS256', expiresIn: '14d' }
-  );
-  res.cookie(TRADE_COOKIE, jwtToken, {
-    httpOnly: true,
-    sameSite: 'lax',
-    secure: tradeProtoIsHttps(req),
-    maxAge: 14 * 24 * 3600 * 1000,
-    path: '/'
-  });
-  // Admin promotion via email allowlist: if the verified email matches
-  // WALLCO_ADMIN_EMAILS (comma-separated), ALSO mint a `dw_auth` cookie so
-  // /designs renders the full admin chrome + the view-mode toggle.
-  // Defaults to Steve's two known emails when env unset.
-  try {
-    const ADMIN_EMAIL_LIST = (process.env.WALLCO_ADMIN_EMAILS
-      || 'steve@designerwallcoverings.com,info@designerwallcoverings.com')
-      .toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
-    const userEmailNorm = String(row.email || '').toLowerCase().trim();
-    if (ADMIN_EMAIL_LIST.includes(userEmailNorm) && process.env.AUTH_DEV_SECRET) {
-      const adminJwt = jwt.sign(
-        { role: 'admin', email: row.email, name: row.name || '', source: 'trade-login-allowlist' },
-        process.env.AUTH_DEV_SECRET,
-        { algorithm: 'HS256', expiresIn: '14d' }
-      );
-      res.cookie('dw_auth', adminJwt, {
-        httpOnly: true,
-        sameSite: 'lax',
-        secure: tradeProtoIsHttps(req),
-        maxAge: 14 * 24 * 3600 * 1000,
-        path: '/'
-      });
-      console.log(`[trade-login] admin promotion: ${row.email} → dw_auth minted`);
-    }
-  } catch (e) {
-    console.error('[trade-login] admin-promo failed (non-fatal):', e.message);
-  }
-  // Whitelist returnTo to relative paths (prevent open-redirect).
-  const safeReturn = (returnTo && returnTo.startsWith('/') && !returnTo.startsWith('//')) ? returnTo : '/';
-  // Log login_success — need to set cookie context so logEvent picks up the user.
-  // The cookie is already in res but not yet on the request; pass actor explicitly.
-  try {
-    psqlQuery(`INSERT INTO wallco_audit_log (user_id, is_admin, actor_label, action, payload, ip, user_agent)
-               VALUES (${parseInt(row.id, 10)}, FALSE, ${pgEsc(row.email)}, ${pgEsc('trade_login_success')},
-                       ${pgEsc(JSON.stringify({ return_to: safeReturn }))}::jsonb,
-                       ${pgEsc(((req.headers['x-forwarded-for'] || req.socket?.remoteAddress || '').toString().split(',')[0].trim()) || '0.0.0.0')}::inet,
-                       ${pgEsc((req.headers['user-agent'] || '').toString().slice(0, 500))});`);
-  } catch(_) {}
-  res.redirect(safeReturn);
-});
-
-// GET /trade/logout — clear cookie, send home.
-app.get('/trade/logout', (req, res) => {
-  logEvent(req, 'trade_logout', null);
-  res.clearCookie(TRADE_COOKIE, { path: '/' });
-  res.redirect('/');
-});
-
-// GET /api/trade/me — current user + saved-count, for header rendering.
-app.get('/api/trade/me', (req, res) => {
-  const u = getTradeUser(req);
-  if (!u) return res.json({ authenticated: false });
-  let savedCount = 0;
-  try {
-    const raw = psqlQuery(`SELECT count(*) FROM wallco_saved_designs WHERE user_id=${parseInt(u.id, 10)};`);
-    savedCount = parseInt(raw, 10) || 0;
-  } catch (e) { /* tolerate; header just shows 0 */ }
-  res.json({
-    authenticated: true,
-    user: { id: u.id, email: u.email, name: u.name || '' },
-    saved_count: savedCount
-  });
-});
-
-// GET /api/me/saved — bulk-friendly: returns all saved design IDs for the current user.
-// Used by autocomplete + grid renderers to mark ♥ on already-saved items without N round-trips.
-// Anonymous users get { authenticated:false, ids:[] }.
-app.get('/api/me/saved', (req, res) => {
-  const u = getTradeUser(req);
-  // No-store — saved state changes mid-session; cache poisoning here is a real risk.
-  res.setHeader('Cache-Control', 'no-store');
-  if (!u) return res.json({ authenticated: false, ids: [] });
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(design_id ORDER BY saved_at DESC), '[]'::json)
-                             FROM wallco_saved_designs WHERE user_id=${parseInt(u.id, 10)};`);
-    const ids = JSON.parse(raw || '[]');
-    res.json({ authenticated: true, count: ids.length, ids });
-  } catch (e) {
-    console.error('[api/me/saved] failed:', e.message);
-    res.status(500).json({ ok: false, error: 'Lookup failed.' });
-  }
-});
-
-// GET /api/saved/state?design_id=... — idempotent probe for the PDP heart icon.
-// Returns { authenticated, saved }. Anonymous users get { authenticated:false, saved:false }.
-app.get('/api/saved/state', (req, res) => {
-  const u = getTradeUser(req);
-  if (!u) return res.json({ authenticated: false, saved: false });
-  const designId = parseInt(req.query.design_id, 10);
-  if (!Number.isFinite(designId) || designId <= 0) {
-    return res.status(400).json({ ok: false, error: 'design_id required.' });
-  }
-  try {
-    const hit = psqlQuery(`SELECT 1 FROM wallco_saved_designs
-                             WHERE user_id=${parseInt(u.id, 10)} AND design_id=${designId} LIMIT 1;`);
-    res.json({ authenticated: true, saved: !!hit });
-  } catch (e) {
-    console.error('[saved/state] failed:', e.message);
-    res.status(500).json({ ok: false, error: 'Lookup failed.' });
-  }
-});
-
-// POST /api/saved  { design_id } — toggle save state for current trade user.
-// Returns { ok, saved: bool, saved_count }. 401 if not logged in.
-app.post('/api/saved', (req, res) => {
-  const u = getTradeUser(req);
-  if (!u) return res.status(401).json({ ok: false, error: 'Sign in to save designs.' });
-  const designId = parseInt((req.body && req.body.design_id), 10);
-  if (!Number.isFinite(designId) || designId <= 0) {
-    return res.status(400).json({ ok: false, error: 'design_id required.' });
-  }
-  try {
-    // Check existing → toggle.
-    const exists = psqlQuery(`SELECT 1 FROM wallco_saved_designs
-                                WHERE user_id=${parseInt(u.id, 10)} AND design_id=${designId} LIMIT 1;`);
-    let nowSaved;
-    if (exists) {
-      psqlQuery(`DELETE FROM wallco_saved_designs
-                   WHERE user_id=${parseInt(u.id, 10)} AND design_id=${designId};`);
-      nowSaved = false;
-    } else {
-      psqlQuery(`INSERT INTO wallco_saved_designs (user_id, design_id)
-                   VALUES (${parseInt(u.id, 10)}, ${designId})
-                   ON CONFLICT DO NOTHING;`);
-      nowSaved = true;
-    }
-    const cnt = parseInt(psqlQuery(`SELECT count(*) FROM wallco_saved_designs WHERE user_id=${parseInt(u.id, 10)};`), 10) || 0;
-    logEvent(req, nowSaved ? 'design_saved' : 'design_unsaved', { design_id: designId, saved_count: cnt });
-    res.json({ ok: true, saved: nowSaved, saved_count: cnt });
-  } catch (e) {
-    console.error('[saved] toggle failed:', e.message);
-    res.status(500).json({ ok: false, error: 'Save failed.' });
-  }
-});
-
-// GET /me/saved — auth-gated page listing all the user's saved designs.
-// Uses the same .design-grid CSS class as /designs so it inherits the same
-// layout + density-slider system.
-app.get('/me/saved', (req, res) => {
-  const u = getTradeUser(req);
-  if (!u) {
-    return res.redirect('/trade/login?return=' + encodeURIComponent('/me/saved'));
-  }
-  // Pull save rows joined to live DESIGNS array (DESIGNS is the in-memory
-  // snapshot we already render everywhere). Designs that no longer exist in
-  // the catalog are silently filtered out.
-  let savedIds = [];
-  // Carry saved_at + note through to the card render so users can see
-  // "Saved 2h ago" without opening DevTools — the rail audit gap that
-  // previously dropped this metadata immediately after the SELECT.
-  let savedMetaById = new Map();
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.saved_at DESC), '[]'::json) FROM (
-      SELECT design_id, note, saved_at FROM wallco_saved_designs
-      WHERE user_id=${parseInt(u.id, 10)}) t;`);
-    const rows = JSON.parse(raw || '[]');
-    savedIds = rows.map(r => r.design_id);
-    savedMetaById = new Map(rows.map(r => [r.design_id, r]));
-  } catch (e) {
-    console.error('[me/saved] query failed:', e.message);
-  }
-  const byId = new Map(DESIGNS.map(d => [d.id, d]));
-  const designs = savedIds.map(id => byId.get(id)).filter(Boolean);
-  // Relative-time formatter (server-rendered so the badge ships in HTML — no JS
-  // flash). Uses 'just now' under 60s, '12m', '4h', '3d', else date.
-  const _meSavedRelTime = (iso) => {
-    if (!iso) return '';
-    const t = Date.parse(iso);
-    if (!t) return '';
-    const dMs = Date.now() - t;
-    if (dMs < 60_000) return 'just now';
-    const m = Math.floor(dMs / 60_000);
-    if (m < 60) return m + 'm ago';
-    const h = Math.floor(m / 60);
-    if (h < 24) return h + 'h ago';
-    const days = Math.floor(h / 24);
-    if (days < 30) return days + 'd ago';
-    return new Date(t).toISOString().slice(0, 10);
-  };
-
-  const cards = designs.map(d => {
-    const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
-                  : (d.room_mockups || []).includes('bedroom')     ? 'bedroom'
-                  : (d.room_mockups || []).includes('office')      ? 'office'
-                  : (d.room_mockups || [])[0];
-    const roomOverlay = roomKey
-      ? `<img class="card-room" src="/designs/room/design_${d.id}_${roomKey}.png" loading="lazy" alt="" aria-hidden="true">`
-      : '';
-    const meta = savedMetaById.get(d.id) || {};
-    const rel = _meSavedRelTime(meta.saved_at);
-    const absISO = meta.saved_at ? new Date(meta.saved_at).toISOString() : '';
-    const savedBadge = rel
-      ? `<span class="saved-when" title="Saved ${_escTrade(absISO)}" style="position:absolute;top:8px;left:8px;background:rgba(15,14,12,.78);color:#e8e2d6;border-radius:4px;padding:3px 7px;font:11px var(--sans,system-ui);letter-spacing:.02em;pointer-events:none">Saved ${_escTrade(rel)}</span>`
-      : '';
-    return `
-    <div class="saved-card-wrap" data-design-id="${d.id}" style="position:relative">
-      <a href="/design/${d.id}" class="design-card${roomKey ? ' has-room' : ''}" aria-label="${_escTrade(d.title || ('Design #' + d.id))}">
-        <div class="card-img" style="background-image:url('${_escTrade(d.image_url)}')" role="img" aria-label="${_escTrade(d.title)} pattern thumbnail">${roomOverlay}</div>
-        <div class="card-meta">
-          <span class="card-title">${_escTrade(d.title)}</span>
-          <span class="card-cat">${_escTrade(d.category)}</span>
-          <span class="color-dot" style="background:${_escTrade(d.dominant_hex || '#ccc')}" aria-hidden="true"></span>
-        </div>
-      </a>
-      ${savedBadge}
-      <button type="button" class="btn-remove-saved" data-id="${d.id}" title="Remove from saved"
-              style="position:absolute;top:8px;right:8px;background:rgba(15,14,12,.85);color:#e8e2d6;border:0;border-radius:4px;padding:5px 9px;font:11px var(--sans,system-ui);cursor:pointer;letter-spacing:.04em;text-transform:uppercase">
-        Remove
-      </button>
-    </div>`;
-  }).join('');
-
-  const proposalIds = designs.map(d => d.id).join(',');
-  const proposalHref = proposalIds ? ('/brief?ids=' + proposalIds) : '#';
-
-  res.type('html').send(`${htmlHead({
-    title: 'Saved Designs — wallco.ai',
-    description: 'Your saved wallco.ai designs.',
-    canonical: 'https://wallco.ai/me/saved'
-  })}
-<body>
-${htmlHeader('/me/saved')}
-<main>
-<section style="padding:32px 40px 16px">
-  <div style="display:flex;flex-wrap:wrap;align-items:baseline;gap:16px;justify-content:space-between;margin-bottom:18px">
-    <div>
-      <h1 style="font:300 28px/1.2 'Cormorant Garamond',serif;margin:0 0 4px">Saved designs</h1>
-      <div style="font:13px var(--sans,system-ui);color:var(--ink-soft,#555)">
-        ${designs.length} ${designs.length === 1 ? 'design' : 'designs'} saved
-        · <a href="/trade/logout" style="color:var(--ink-soft,#555)">Sign out</a>
-      </div>
-    </div>
-    <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
-      ${designs.length ? `<a href="${proposalHref}" target="_blank" rel="noopener noreferrer" id="saved-brief" style="padding:10px 18px;font:13px var(--sans);background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);text-decoration:none;border:0;border-radius:4px;font-weight:500" title="Export all saved designs as a printable multi-design proposal PDF">Build proposal PDF →</a>` : ''}
-      <a href="/designs" class="btn-outline" style="padding:10px 18px;font:13px var(--sans);text-decoration:none">Browse catalog</a>
-      ${designs.length ? `<details style="font:12px var(--sans,system-ui);color:var(--ink-soft,#555)">
-        <summary style="cursor:pointer;padding:6px 10px;border:1px solid var(--line,#ddd);border-radius:4px;list-style:none">Brief options ▾</summary>
-        <div style="margin-top:8px;padding:12px;background:var(--card-bg,#faf8f3);border:1px solid var(--line,#ebe5d6);border-radius:6px;display:grid;gap:8px;min-width:280px;position:absolute;z-index:20">
-          <label>Project name<br><input id="saved-brief-project" type="text" placeholder="e.g. Living Room" style="margin-top:4px;width:100%;padding:6px 8px;font:13px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <label>Client name<br><input id="saved-brief-client" type="text" placeholder="e.g. Smith Residence" style="margin-top:4px;width:100%;padding:6px 8px;font:13px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <button type="button" id="saved-brief-go" style="padding:8px 14px;background:var(--ink,#111);color:var(--bg,#fff);border:0;border-radius:4px;cursor:pointer;font:13px var(--sans)">Open Brief →</button>
-        </div>
-      </details>` : ''}
-    </div>
-    <script>
-    (function(){
-      var go = document.getElementById('saved-brief-go');
-      if (!go) return;
-      var ids = ${JSON.stringify(designs.map(d => d.id))};
-      go.addEventListener('click', function(){
-        var p = (document.getElementById('saved-brief-project').value || '').trim();
-        var c = (document.getElementById('saved-brief-client').value || '').trim();
-        var qs = ['ids=' + ids.join(',')];
-        if (p) qs.push('project=' + encodeURIComponent(p));
-        if (c) qs.push('client=' + encodeURIComponent(c));
-        window.open('/brief?' + qs.join('&'), '_blank', 'noopener');
-      });
-    })();
-    </script>
-  </div>
-  ${designs.length
-    ? `<div class="design-grid catalog-grid" id="saved-grid">${cards}</div>`
-    : `<div style="padding:56px 28px;text-align:center;color:var(--ink-soft,#555);font:14px/1.6 var(--sans);background:var(--card-bg,#faf8f3);border-radius:8px">
-         <div style="font-size:42px;line-height:1;margin-bottom:14px;color:var(--gold,#c9a14b)">♡</div>
-         <p style="margin:0 0 8px;font:300 22px/1.2 var(--serif,Georgia,serif);color:var(--ink,#111)">Nothing saved yet.</p>
-         <p style="margin:0 0 18px;max-width:38em;margin-inline:auto">Save designs you're considering for a project — they'll wait here until you're ready to build a proposal or request samples.</p>
-         <p style="margin:0"><a href="/designs" class="btn-outline" style="display:inline-block;padding:9px 18px;font:13px var(--sans);text-decoration:none">Browse the catalog →</a></p>
-         <p style="margin:14px 0 0;font:12px var(--sans);color:var(--ink-faint,#999)">Tap <strong>♥ Save</strong> on any design page.</p>
-       </div>`}
-</section>
-</main>
-${FOOTER}
-<script>
-(function(){
-  // Remove from saved — POST /api/saved toggles, then fade the card out.
-  document.querySelectorAll('.btn-remove-saved').forEach(function(btn){
-    btn.addEventListener('click', function(e){
-      e.preventDefault();
-      var id = parseInt(btn.dataset.id, 10);
-      if (!id) return;
-      btn.disabled = true;
-      fetch('/api/saved', {
-        method:'POST', headers:{'Content-Type':'application/json'},
-        body: JSON.stringify({ design_id: id })
-      })
-        .then(function(r){ return r.json(); })
-        .then(function(j){
-          if (j.ok && !j.saved) {
-            if (window.__tradeMe) window.__tradeMe.saved_count = j.saved_count;
-            try { window.dispatchEvent(new CustomEvent('wallco-saved-changed', { detail: { saved_count: j.saved_count, design_id: id, saved: false } })); } catch(e){}
-            var wrap = btn.closest('.saved-card-wrap');
-            if (wrap) {
-              wrap.style.transition = 'opacity .25s, transform .25s';
-              wrap.style.opacity = '0';
-              wrap.style.transform = 'scale(.94)';
-              setTimeout(function(){ wrap.remove();
-                if (!document.querySelectorAll('.saved-card-wrap').length) {
-                  location.reload();
-                }
-              }, 260);
-            }
-          } else {
-            btn.disabled = false;
-            alert(j.error || 'Could not remove.');
-          }
-        })
-        .catch(function(err){ btn.disabled = false; alert('Network error: ' + err.message); });
-    });
-  });
-})();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /me/activity — auth-gated. Per-trade-user activity log (own rows only).
-// Used by users to see their own footprint (saves, basket adds, sample requests, etc.).
-// JSON via ?format=json for client/export use.
-app.get('/me/activity', (req, res) => {
-  const u = getTradeUser(req);
-  if (!u) return res.redirect('/trade/login?return=' + encodeURIComponent('/me/activity'));
-  const limit = Math.max(1, Math.min(500, parseInt(req.query.limit || '100', 10)));
-  let rows = [];
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC), '[]'::json) FROM (
-      SELECT id, action, payload, ip::text AS ip, user_agent, created_at
-      FROM wallco_audit_log
-      WHERE user_id = ${parseInt(u.id, 10)}
-      ORDER BY created_at DESC
-      LIMIT ${limit}
-    ) t;`);
-    rows = JSON.parse(raw || '[]');
-  } catch (e) {
-    console.error('[me/activity] query failed:', e.message);
-  }
-  // JSON export path (CCPA/GDPR-aligned data-portability).
-  if (String(req.query.format || '').toLowerCase() === 'json') {
-    res.setHeader('Cache-Control', 'no-store');
-    return res.json({ user_id: u.id, email: u.email, count: rows.length, rows });
-  }
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-  const actionPretty = (a) => ({
-    trade_login_request: 'Requested sign-in link',
-    trade_login_success: 'Signed in',
-    trade_logout: 'Signed out',
-    design_saved: 'Saved a design',
-    design_unsaved: 'Unsaved a design',
-    sample_request_submitted: 'Requested samples',
-    designs_search: 'Searched the catalog',
-  })[a] || a;
-  const rowsHtml = rows.map(r => {
-    const t = new Date(r.created_at);
-    const when = t.toISOString().replace('T',' ').slice(0,16) + ' UTC';
-    let summary = '';
-    try {
-      const p = r.payload || {};
-      if (r.action === 'design_saved' || r.action === 'design_unsaved') summary = `design #${p.design_id}`;
-      else if (r.action === 'designs_search') summary = `“${esc(p.q || '')}” → ${p.count} hits`;
-      else if (r.action === 'sample_request_submitted') summary = `request_id=${esc(p.request_id || '')} · ${p.design_count} designs`;
-      else if (p && Object.keys(p).length) summary = esc(JSON.stringify(p).slice(0, 140));
-    } catch(_) {}
-    return `<tr>
-      <td style="padding:8px 12px;font:11px ui-monospace,Menlo,monospace;color:var(--ink-faint,#888);white-space:nowrap">${esc(when)}</td>
-      <td style="padding:8px 12px;font:13px var(--sans,system-ui);color:var(--ink,#111)">${esc(actionPretty(r.action))}</td>
-      <td style="padding:8px 12px;font:12px var(--sans,system-ui);color:var(--ink-soft,#555)">${summary}</td>
-    </tr>`;
-  }).join('');
-  res.setHeader('Cache-Control', 'no-store');
-  res.type('html').send(`${htmlHead({
-    title: 'My Activity — wallco.ai',
-    description: 'Your wallco.ai activity log.',
-    canonical: 'https://wallco.ai/me/activity'
-  })}
-<body>
-${htmlHeader('/me/activity')}
-<main>
-<section style="padding:32px 40px 16px;max-width:980px;margin:0 auto">
-  <div style="display:flex;flex-wrap:wrap;align-items:baseline;gap:16px;justify-content:space-between;margin-bottom:18px">
-    <div>
-      <h1 style="font:300 28px/1.2 'Cormorant Garamond',serif;margin:0 0 4px">My activity</h1>
-      <div style="font:13px var(--sans,system-ui);color:var(--ink-soft,#555)">
-        ${rows.length} recorded ${rows.length === 1 ? 'event' : 'events'} · <a href="/me/saved" style="color:var(--ink-soft,#555)">Saved →</a>
-      </div>
-    </div>
-    <div style="display:flex;gap:10px">
-      <a href="/me/activity?format=json" class="btn-outline" style="padding:8px 14px;font:12px var(--sans);text-decoration:none">Export JSON</a>
-    </div>
-  </div>
-  ${rows.length
-    ? `<table style="width:100%;border-collapse:collapse">
-        <thead><tr>
-          <th style="text-align:left;padding:6px 12px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">When (UTC)</th>
-          <th style="text-align:left;padding:6px 12px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Action</th>
-          <th style="text-align:left;padding:6px 12px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Detail</th>
-        </tr></thead>
-        <tbody>${rowsHtml}</tbody>
-      </table>`
-    : `<div style="padding:48px 28px;text-align:center;color:var(--ink-soft,#555);font:14px/1.6 var(--sans);background:var(--card-bg,#faf8f3);border-radius:8px">
-         <p style="margin:0 0 8px;font:300 22px/1.2 var(--serif,Georgia,serif);color:var(--ink,#111)">No activity yet.</p>
-         <p>Sign-ins, saves, sample requests, and catalog searches show up here.</p>
-       </div>`}
-</section>
-</main>
-${FOOTER}
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /admin/activity — admin-gated. All events across all users.
-// Filters: ?user_id=, ?action=, ?from=YYYY-MM-DD, ?to=YYYY-MM-DD, ?limit=
-// JSON export at ?format=json.
-app.get('/admin/activity', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
-  const limit = Math.max(1, Math.min(1000, parseInt(req.query.limit || '200', 10)));
-  const userId = parseInt(req.query.user_id, 10);
-  const action = String(req.query.action || '').trim();
-  const from = String(req.query.from || '').trim();
-  const to = String(req.query.to || '').trim();
-  const wheres = [];
-  if (Number.isFinite(userId) && userId > 0) wheres.push(`l.user_id = ${userId}`);
-  if (action) wheres.push(`l.action = ${pgEsc(action)}`);
-  if (/^\d{4}-\d{2}-\d{2}$/.test(from)) wheres.push(`l.created_at >= ${pgEsc(from)}::timestamptz`);
-  if (/^\d{4}-\d{2}-\d{2}$/.test(to))   wheres.push(`l.created_at < (${pgEsc(to)}::date + 1)::timestamptz`);
-  const whereClause = wheres.length ? `WHERE ${wheres.join(' AND ')}` : '';
-  let rows = [];
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC), '[]'::json) FROM (
-      SELECT l.id, l.user_id, l.is_admin, l.actor_label, l.action, l.payload,
-             l.ip::text AS ip, l.user_agent, l.created_at,
-             u.email AS user_email
-      FROM wallco_audit_log l
-      LEFT JOIN wallco_trade_users u ON u.id = l.user_id
-      ${whereClause}
-      ORDER BY l.created_at DESC
-      LIMIT ${limit}
-    ) t;`);
-    rows = JSON.parse(raw || '[]');
-  } catch (e) {
-    console.error('[admin/activity] query failed:', e.message);
-  }
-  if (String(req.query.format || '').toLowerCase() === 'json') {
-    res.setHeader('Cache-Control', 'no-store');
-    return res.json({ count: rows.length, filter: { user_id: userId || null, action: action || null, from: from || null, to: to || null }, rows });
-  }
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-  const rowsHtml = rows.map(r => {
-    const t = new Date(r.created_at);
-    const when = t.toISOString().replace('T',' ').slice(0,19) + 'Z';
-    const actor = r.is_admin ? '<span style="color:var(--gold,#c9a14b);font-weight:600">admin</span>'
-                : (r.user_email ? esc(r.user_email) : esc(r.actor_label || 'anon'));
-    let summary = '';
-    try { summary = r.payload ? esc(JSON.stringify(r.payload).slice(0, 240)) : ''; } catch(_) {}
-    return `<tr>
-      <td style="padding:6px 10px;font:10px ui-monospace,Menlo,monospace;color:var(--ink-faint,#888);white-space:nowrap">${esc(when)}</td>
-      <td style="padding:6px 10px;font:12px var(--sans,system-ui)">${actor}</td>
-      <td style="padding:6px 10px;font:12px var(--sans,system-ui);color:var(--ink,#111)">${esc(r.action)}</td>
-      <td style="padding:6px 10px;font:11px ui-monospace,Menlo,monospace;color:var(--ink-soft,#555);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${summary}">${summary}</td>
-      <td style="padding:6px 10px;font:11px ui-monospace,Menlo,monospace;color:var(--ink-faint,#888);white-space:nowrap">${esc(r.ip || '')}</td>
-    </tr>`;
-  }).join('');
-  res.setHeader('Cache-Control', 'no-store');
-  res.type('html').send(`${htmlHead({
-    title: 'Activity Log — wallco.ai admin',
-    description: 'Admin view of all audit events.',
-    canonical: 'https://wallco.ai/admin/activity'
-  })}
-<body>
-${htmlHeader('/admin/activity')}
-<main>
-<section style="padding:24px 40px;max-width:1280px;margin:0 auto">
-  <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:baseline;justify-content:space-between;margin-bottom:14px">
-    <div>
-      <h1 style="font:300 28px/1.2 'Cormorant Garamond',serif;margin:0">Activity log <span style="font:12px var(--sans);color:var(--ink-faint,#888);text-transform:uppercase;letter-spacing:.08em;margin-left:6px">admin · all events</span></h1>
-      <div style="font:12px var(--sans,system-ui);color:var(--ink-soft,#555);margin-top:4px">${rows.length} rows · limit ${limit}${userId ? ' · user '+userId : ''}${action ? ' · action '+esc(action) : ''}${from ? ' · from '+esc(from) : ''}${to ? ' · to '+esc(to) : ''}</div>
-    </div>
-    <form method="get" action="/admin/activity" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;font:12px var(--sans,system-ui)">
-      <input name="user_id" type="number" min="1" placeholder="user_id" value="${userId||''}" style="width:90px;padding:5px 8px;border:1px solid var(--line,#ddd);border-radius:4px">
-      <input name="action" type="text" placeholder="action" value="${esc(action)}" style="width:160px;padding:5px 8px;border:1px solid var(--line,#ddd);border-radius:4px">
-      <input name="from" type="date" value="${esc(from)}" style="padding:5px 8px;border:1px solid var(--line,#ddd);border-radius:4px">
-      <input name="to" type="date" value="${esc(to)}" style="padding:5px 8px;border:1px solid var(--line,#ddd);border-radius:4px">
-      <input name="limit" type="number" min="10" max="1000" value="${limit}" style="width:70px;padding:5px 8px;border:1px solid var(--line,#ddd);border-radius:4px">
-      <button type="submit" style="padding:5px 14px;background:var(--ink,#111);color:var(--bg,#fff);border:0;border-radius:4px;cursor:pointer">Filter</button>
-      <a href="?${new URLSearchParams(req.query).toString()}&format=json" style="padding:5px 12px;text-decoration:none;color:var(--ink-soft,#555);border:1px solid var(--line,#ddd);border-radius:4px">JSON</a>
-    </form>
-  </div>
-  ${rows.length
-    ? `<table style="width:100%;border-collapse:collapse;font-size:12px">
-        <thead><tr style="background:var(--card-bg,#faf8f3)">
-          <th style="text-align:left;padding:7px 10px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">When (UTC)</th>
-          <th style="text-align:left;padding:7px 10px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Actor</th>
-          <th style="text-align:left;padding:7px 10px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Action</th>
-          <th style="text-align:left;padding:7px 10px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Payload</th>
-          <th style="text-align:left;padding:7px 10px;font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">IP</th>
-        </tr></thead>
-        <tbody>${rowsHtml}</tbody>
-      </table>`
-    : '<p style="padding:32px;text-align:center;color:var(--ink-soft,#555)">No matching events.</p>'}
-</section>
-</main>
-${FOOTER}
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /admin/dashboard — key metrics for wallco.ai operations
-app.get('/admin/dashboard', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
-  // Pull metrics in one round-trip via single SQL block.
-  let metrics = {};
-  try {
-    const out = execSync(PSQL_CMD, { encoding: 'utf8', input: `
-SELECT json_build_object(
-  'designs_total',        (SELECT count(*) FROM spoon_all_designs WHERE generator='replicate'),
-  'designs_24h',          (SELECT count(*) FROM spoon_all_designs WHERE generator='replicate' AND created_at > now()-interval '24 hours'),
-  'designs_7d',           (SELECT count(*) FROM spoon_all_designs WHERE generator='replicate' AND created_at > now()-interval '7 days'),
-  'designs_published',    (SELECT count(*) FROM spoon_all_designs WHERE generator='replicate' AND is_published),
-  'by_category',          (SELECT COALESCE(json_object_agg(category, n),'{}') FROM (SELECT category, count(*) n FROM spoon_all_designs WHERE generator='replicate' GROUP BY category) t),
-  'saturation_high',      (SELECT count(*) FROM spoon_all_designs WHERE generator='replicate' AND dominant_hex IS NOT NULL),
-  'mockups_done',         (SELECT count(*) FROM spoon_all_designs WHERE room_mockups IS NOT NULL),
-  'leads_total',          (SELECT count(*) FROM wallco_leads),
-  'leads_7d',             (SELECT count(*) FROM wallco_leads WHERE created_at > now()-interval '7 days'),
-  'leads_24h',            (SELECT count(*) FROM wallco_leads WHERE created_at > now()-interval '24 hours'),
-  'chat_sessions',        (SELECT count(*) FROM wallco_chat_sessions),
-  'chat_msgs_24h',        (SELECT count(*) FROM wallco_chat_messages WHERE created_at > now()-interval '24 hours'),
-  'votes_total',          (SELECT count(*) FROM wallco_votes),
-  'votes_7d',             (SELECT count(*) FROM wallco_votes WHERE created_at > now()-interval '7 days'),
-  'inspiration_pool',     (SELECT count(*) FROM wallco_inspiration_pool),
-  'generator_runs',       (SELECT count(*) FROM wallco_generator_runs),
-  'generator_runs_24h',   (SELECT count(*) FROM wallco_generator_runs WHERE started_at > now()-interval '24 hours')
-);
-    `});
-    metrics = JSON.parse(out.trim());
-  } catch (e) {
-    return res.status(500).type('html').send(`<h1>dashboard query failed</h1><pre>${(e.message||'').replace(/</g,'&lt;')}</pre>`);
-  }
-
-  // Replicate cost: $0.014/SDXL gen × designs_total + $0.014/room mockup × mockups (multi-key JSONB).
-  // Sum keys per design — each room type is one Replicate call.
-  let mockupCount = metrics.mockups_done;
-  try {
-    const raw = execSync(PSQL_CMD, { encoding: 'utf8', input:
-      `SELECT COALESCE(SUM((SELECT count(*) FROM jsonb_object_keys(room_mockups))), 0)
-       FROM spoon_all_designs WHERE room_mockups IS NOT NULL;`
-    }).trim();
-    if (raw) mockupCount = parseInt(raw, 10) || metrics.mockups_done;
-  } catch { /* fall through to metrics.mockups_done */ }
-  const replicateCostSdxl = (metrics.designs_total * 0.014).toFixed(2);
-  const replicateCostRooms = (mockupCount * 0.014).toFixed(2);
-  const replicateCostTotal = (parseFloat(replicateCostSdxl) + parseFloat(replicateCostRooms)).toFixed(2);
-
-  const card = (label, value, sub) => `
-    <div class="m-card">
-      <div class="m-label">${label}</div>
-      <div class="m-value">${value}</div>
-      ${sub ? `<div class="m-sub">${sub}</div>` : ''}
-    </div>`;
-
-  const catBreakdown = Object.entries(metrics.by_category || {})
-    .sort((a,b) => b[1]-a[1])
-    .map(([k,v]) => `<span class="cat-chip"><span class="cat-k">${k}</span> <span class="cat-v">${v}</span></span>`)
-    .join('');
-
-  res.type('html').send(`<!doctype html>
-<html lang="en"><head>
-<meta charset="utf-8"><title>wallco.ai — admin dashboard</title>
-<style>
-  :root { --bg:#0f0e0c; --panel:#1a1816; --line:#2a2825; --ink:#e8e2d6; --ink-faint:#888; --accent:#d2b15c; }
-  * { box-sizing:border-box }
-  body { font:14px/1.5 -apple-system,system-ui,sans-serif; margin:0; padding:32px 40px; background:var(--bg); color:var(--ink); }
-  h1 { font-weight:300; font-size:28px; margin:0 0 6px; letter-spacing:.5px; }
-  .sub { color:var(--ink-faint); font-size:12.5px; margin-bottom:28px; }
-  .sub a { color:var(--accent); text-decoration:none; margin-right:14px; }
-  .grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:14px; margin-bottom:28px; }
-  .m-card { background:var(--panel); border:1px solid var(--line); border-radius:6px; padding:16px 18px; }
-  .m-label { color:var(--ink-faint); font-size:11.5px; text-transform:uppercase; letter-spacing:.7px; margin-bottom:8px; }
-  .m-value { font-size:28px; font-weight:300; letter-spacing:.3px; color:var(--ink); }
-  .m-sub { color:var(--accent); font-size:11.5px; margin-top:4px; }
-  .section-h { font-size:13px; text-transform:uppercase; letter-spacing:1px; color:var(--ink-faint); margin:0 0 12px; }
-  .cat-row { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:28px; }
-  .cat-chip { background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:6px 12px; font-size:12.5px; }
-  .cat-k { color:var(--ink); text-transform:capitalize; }
-  .cat-v { color:var(--accent); margin-left:6px; font-weight:500; }
-  .footer { color:var(--ink-faint); font-size:11px; margin-top:24px; padding-top:14px; border-top:1px solid var(--line); }
-</style></head>
-<body>
-  <h1>wallco.ai · admin dashboard</h1>
-  <div class="sub">
-    <a href="/admin/golive">go-live</a>
-    <a href="/admin/leads">leads</a>
-    <a href="/designs?review=1">review designs</a>
-    <a href="/admin/inspiration">inspiration</a>
-    · refreshed ${new Date().toLocaleString('en-US',{ timeZone:'America/Los_Angeles' })} PT
-  </div>
-
-  <h2 class="section-h">Catalog</h2>
-  <div class="grid">
-    ${card('Total designs',       metrics.designs_total, `${metrics.designs_published} published`)}
-    ${card('Generated · last 24h', metrics.designs_24h)}
-    ${card('Generated · last 7d',  metrics.designs_7d)}
-    ${card('Room mockups',         metrics.mockups_done, `${mockupCount} total renderings`)}
-    ${card('Inspiration pool',     metrics.inspiration_pool)}
-  </div>
-
-  <h2 class="section-h">By category</h2>
-  <div class="cat-row">${catBreakdown || '<span class="m-sub">no data</span>'}</div>
-
-  <h2 class="section-h">Demand signals</h2>
-  <div class="grid">
-    ${card('Leads · total',       metrics.leads_total)}
-    ${card('Leads · last 7d',     metrics.leads_7d)}
-    ${card('Leads · last 24h',    metrics.leads_24h)}
-    ${card('Chat sessions',       metrics.chat_sessions, `${metrics.chat_msgs_24h} msgs last 24h`)}
-    ${card('Votes',               metrics.votes_total, `${metrics.votes_7d} in last 7d`)}
-  </div>
-
-  <h2 class="section-h">Cost burn</h2>
-  <div class="grid">
-    ${card('Replicate · SDXL',    '$' + replicateCostSdxl, `${metrics.designs_total} gens @ $0.014`)}
-    ${card('Replicate · rooms',   '$' + replicateCostRooms, `${mockupCount} renders @ $0.014`)}
-    ${card('Replicate · TOTAL',   '$' + replicateCostTotal)}
-    ${card('Generator runs',      metrics.generator_runs, `${metrics.generator_runs_24h} last 24h`)}
-  </div>
-
-  <div class="footer">Source: dw_unified.spoon_all_designs + wallco_*. Admin-only route. Cache: live.</div>
-</body></html>`);
-});
-
-// Color-isolate module — palette extract, Sherwin-Williams nearest match,
-// coordinating-color suggestions. Sourced from ~/Projects/_shared/color-isolate.js
-// and copied locally per deploy-portability rule (Kamatera lacks ~/.claude/skills/).
-const colorIsolate = require('./src/color-isolate');
-
-// ── GA4 measurement ID
-const GA4_ID = process.env.GA4_MEASUREMENT_ID || '';
-
-// ── Design brief / color palette swatches
-function paletteSwatches(design) {
-  if (!design.dominant_hex) return '';
-  const colors = [design.dominant_hex];
-  // complementary approximation
-  const [h,,l] = hexToHSL(design.dominant_hex);
-  return colors.map(c =>
-    `<span class="swatch" style="background:${c}" title="${c}"></span>`
-  ).join('');
-}
-
-// ── Shared HTML fragments
-const GTAG = GA4_ID ? `
-<!-- Google tag (gtag.js) -->
-<script async src="https://www.googletagmanager.com/gtag/js?id=${GA4_ID}"></script>
-<script>
-  window.dataLayer = window.dataLayer || [];
-  function gtag(){dataLayer.push(arguments);}
-  gtag('js', new Date());
-  gtag('config', '${GA4_ID}');
-</script>` : '';
-
-function htmlHead({ title, description, canonical, ogImage = '', ogType = 'website', ogImageAlt = '', productOG = null, preloadImages = [] }) {
-  const siteUrl = `https://${SITE}`;
-  const ogImg = ogImage || `${siteUrl}/og-default.png`;
-  const imgAlt = ogImageAlt || title;
-  // product:* tags add Facebook/Pinterest product-pin metadata when ogType==='product'.
-  // productOG = { availability, price, currency, retailer_item_id } — all optional.
-  const productTags = ogType === 'product' && productOG ? [
-    productOG.availability && `<meta property="product:availability" content="${productOG.availability}">`,
-    productOG.retailer_item_id && `<meta property="product:retailer_item_id" content="${productOG.retailer_item_id}">`,
-    productOG.price && productOG.currency && `<meta property="product:price:amount" content="${productOG.price}">`,
-    productOG.price && productOG.currency && `<meta property="product:price:currency" content="${productOG.currency}">`,
-    productOG.brand && `<meta property="product:brand" content="${productOG.brand}">`,
-    productOG.category && `<meta property="product:category" content="${productOG.category}">`
-  ].filter(Boolean).join('\n') : '';
-  // Preload tags for above-the-fold images (LCP). Browsers start fetching
-  // these during <head> parse, ~100-300ms before the body scan would discover
-  // the same URLs in background-image: declarations. fetchpriority=high marks
-  // the first one as the LCP candidate so the network stack prioritizes it.
-  const preloadTags = (preloadImages || []).slice(0, 6).map((u, i) =>
-    `<link rel="preload" as="image" href="${u}"${i === 0 ? ' fetchpriority="high"' : ''}>`
-  ).join('\n');
-  return `<!doctype html>
-<html lang="en">
-<head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-<!-- Mobile browser chrome — Android/Chrome tints the address bar; iOS Safari
-     uses it for the status bar in standalone PWA mode. Cream in light mode,
-     dark accent in dark mode so the bar visually merges with page content. -->
-<meta name="theme-color" content="#faf8f3" media="(prefers-color-scheme: light)">
-<meta name="theme-color" content="#0d0d0d" media="(prefers-color-scheme: dark)">
-<meta name="apple-mobile-web-app-status-bar-style" content="default">
-<title>${title}</title>
-<meta name="description" content="${description}">
-<link rel="canonical" href="${canonical}">
-${preloadTags}
-<!-- Open Graph -->
-<meta property="og:type" content="${ogType}">
-<meta property="og:site_name" content="wallco.ai">
-<meta property="og:title" content="${title}">
-<meta property="og:description" content="${description}">
-<meta property="og:url" content="${canonical}">
-<meta property="og:image" content="${ogImg}">
-<meta property="og:image:secure_url" content="${ogImg}">
-<meta property="og:image:alt" content="${imgAlt}">
-<meta property="og:locale" content="en_US">
-${productTags}
-<!-- Twitter Card -->
-<meta name="twitter:card" content="summary_large_image">
-<meta name="twitter:title" content="${title}">
-<meta name="twitter:description" content="${description}">
-<meta name="twitter:image" content="${ogImg}">
-<meta name="twitter:image:alt" content="${imgAlt}">
-${GTAG}
-<!-- Dark/Light anti-flash + Age-band early apply (must be first JS in <head>) -->
-<script>
-(function(){
-  var t = localStorage.getItem('wallco-theme');
-  if (!t) {
-    t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
-  }
-  document.documentElement.setAttribute('data-theme', t);
-  var age = parseInt(localStorage.getItem('wallco-age'),10);
-  function band(a){
-    if (!a) return '';
-    if (a < 6)  return 'toddler';
-    if (a < 11) return 'elementary';
-    if (a < 14) return 'tween';
-    if (a < 18) return 'teen';
-    if (a < 40) return 'adult';
-    if (a < 60) return 'mature';
-    if (a < 80) return 'senior';
-    return 'elder';
-  }
-  if (age) document.documentElement.setAttribute('data-age-band', band(age));
-})();
-</script>
-<link rel="stylesheet" href="/css/site.css">
-<link rel="preconnect" href="https://fonts.googleapis.com">
-<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-<link rel="preconnect" href="https://www.googletagmanager.com">
-<link rel="dns-prefetch" href="https://www.google-analytics.com">
-<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,400&family=Inter:wght@300;400;500&display=swap" rel="stylesheet">
-</head>`;
-}
-
-function htmlHeader(active) {
-  const navLinks = [
-    ['/', 'Home'],
-    ['/designs', 'Designs'],
-    ['/murals', 'Murals'],
-    ['/samples', 'Samples'],
-    ['/basket', 'Basket'],
-    ['/studio', 'Studio'],
-    ['/play',   'Play'],
-    ['/generator', 'Generator'],
-    ['/marketplace', 'Marketplace'],
-    ['/designers', 'Designers'],
-    ['/patterns', 'Patterns'],
-    ['/about', 'About'],
-  ];
-  return `<a class="skip-link" href="#main-content">Skip to main content</a>
-<header class="site-header">
-  <a class="brand" href="/">wallco.ai</a>
-  <div class="trade-nav" id="trade-nav" style="position:absolute;right:64px;top:50%;transform:translateY(-50%);font:12px var(--sans,system-ui);display:flex;align-items:center;gap:10px;z-index:5">
-    <a id="trade-nav-signin" href="/trade/login" style="color:var(--ink,#111);text-decoration:none;padding:6px 12px;border:1px solid var(--line,#ddd);border-radius:999px;background:transparent">Sign In</a>
-    <span id="trade-nav-user" style="display:none;align-items:center;gap:8px">
-      <a href="/me/saved" style="color:var(--ink,#111);text-decoration:none">
-        <span id="trade-nav-name" style="font-weight:500"></span>
-        <span style="margin-left:6px;color:var(--ink-faint,#888)">·</span>
-        Saved (<span id="trade-nav-saved-count">0</span>)
-      </a>
-      <a href="/trade/logout" title="Sign out" style="color:var(--ink-faint,#888);text-decoration:none;font-size:11px;margin-left:4px">⎋</a>
-    </span>
-  </div>
-  <button class="hamburger" id="nav-toggle" aria-controls="nav-panel" aria-expanded="false" aria-label="Open menu">
-    <span></span><span></span><span></span>
-  </button>
-</header>
-<nav class="nav-panel" id="nav-panel" hidden>
-  ${navLinks.map(([href, label]) =>
-    `<a href="${href}" class="${active === href ? 'active' : ''}"${href === '/basket' ? ' id="nav-basket-link"' : ''}>${label}${href === '/basket' ? '<span id="nav-basket-count" style="display:none;margin-left:6px;font:11px var(--sans,system-ui);background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);padding:1px 7px;border-radius:999px;vertical-align:middle"></span>' : ''}</a>`
-  ).join('\n  ')}
-  <a href="/me/saved" class="${active === '/me/saved' ? 'active' : ''}">My Saved</a>
-  <a href="/me/activity" class="${active === '/me/activity' ? 'active' : ''}">My Activity</a>
-  <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark/light mode">
-    <svg class="sun-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
-    <svg class="moon-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
-  </button>
-</nav>
-<script>
-(function(){
-  // Trade-user nav hydration. Hits /api/trade/me; swaps Sign-In ↔ name+saved-count.
-  // window.__tradeMe is shared with the PDP save-button + /me/saved so we hit
-  // the endpoint only once per page.
-  if (window.__tradeMeLoaded) return;
-  window.__tradeMeLoaded = true;
-  fetch('/api/trade/me', { credentials: 'same-origin' })
-    .then(function(r){ return r.json(); })
-    .then(function(j){
-      window.__tradeMe = j;
-      var signin = document.getElementById('trade-nav-signin');
-      var userBox = document.getElementById('trade-nav-user');
-      if (j && j.authenticated) {
-        if (signin) signin.style.display = 'none';
-        if (userBox) userBox.style.display = 'inline-flex';
-        var nameEl = document.getElementById('trade-nav-name');
-        var countEl = document.getElementById('trade-nav-saved-count');
-        if (nameEl) nameEl.textContent = (j.user && (j.user.name || j.user.email.split('@')[0])) || '';
-        if (countEl) countEl.textContent = j.saved_count || 0;
-      } else {
-        if (signin) {
-          var ret = encodeURIComponent(location.pathname + location.search);
-          signin.setAttribute('href', '/trade/login?return=' + ret);
-        }
-      }
-      // Notify other scripts on this page (e.g. PDP save button)
-      try { window.dispatchEvent(new CustomEvent('trade-me-loaded', { detail: j })); } catch(e){}
-    })
-    .catch(function(){});
-  // Listen for save-state changes from any surface (PDP toggle, /me/saved Remove,
-  // future autocomplete heart, etc.) and re-sync the nav pill count without a fetch.
-  window.addEventListener('wallco-saved-changed', function(ev){
-    var countEl = document.getElementById('trade-nav-saved-count');
-    var n = ev && ev.detail && typeof ev.detail.saved_count === 'number' ? ev.detail.saved_count : null;
-    if (n === null) return; // fallback: ignore; nav can still hydrate on next page load
-    if (countEl) countEl.textContent = n;
-    if (window.__tradeMe) window.__tradeMe.saved_count = n;
-  });
-})();
-</script>
-<span id="main-content" tabindex="-1"></span>`;
-}
-
-const FOOTER = `<footer class="site-footer">
-  <p>wallco.ai &mdash; AI-original wallpaper &amp; murals. Every pattern generated, never repeated.</p>
-  <p>Part of the <a href="https://designerwallcoverings.com">Designer Wallcoverings</a> family.</p>
-  <p class="footer-contact">
-    <a href="mailto:info@wallco.ai">info@wallco.ai</a>
-    <span aria-hidden="true">&middot;</span>
-    <a href="tel:+18883734564">888-373-4564</a>
-  </p>
-  <address class="footer-address">
-    Designer Wallcoverings &middot; 15442 Ventura Bl #102 &middot; Sherman Oaks, CA 91403
-  </address>
-  <p class="footer-legal">&copy; ${new Date().getFullYear()} Designer Wallcoverings. Memo samples are always free.</p>
-</footer>
-<script src="/js/wallco-game.js" defer></script>
-<!-- Mobile tap-target minimums (Apple HIG / WCAG 2.5.5) — applied site-wide.
-     Forces every clickable to ≥44×44 on phones without overriding desktop layout. -->
-<style id="mobile-tap-floor">
-@media (max-width: 640px) {
-  button:not(.hero-dot):not(.palette-dot):not([aria-hidden="true"]):not(.hamburger),
-  .btn-primary, .btn-outline,
-  a.proj-card, .play-card, .lb-card,
-  .material-tabs > *, .nav-panel a, .nav-panel button,
-  .filter-pill, .dots-sort, .edit-tab,
-  .hue-chip, a.hue-chip, .motif-chip, a.motif-chip,
-  .pill, .tab-pill, .chip, a.chip,
-  a.design-card, .featured-grid > a, a.brand,
-  .pager-link, a.pager-link,
-  .detail-nav-link, a.detail-nav-link,
-  input[type="submit"], input[type="button"] {
-    min-height: 44px;
-  }
-  .material-tabs > *, .dots-sort, .filter-pill,
-  .hue-chip, a.hue-chip, .motif-chip, a.motif-chip,
-  .pill, .tab-pill, .chip, a.chip {
-    padding-top: 10px; padding-bottom: 10px;
-    line-height: 1.2; box-sizing: border-box;
-    display: inline-flex; align-items: center; justify-content: center;
-  }
-  .lb-card .meta { padding: 10px 10px 12px; min-height: 44px; }
-  .edit-tab { padding: 14px 12px !important; }
-  body { font-size: 16px; }
-  input, select, textarea { font-size: 16px; }
-}
-</style>
-<script src="/js/chat.js" defer></script>`;
-
-const HAMBURGER_JS = `
-<!-- First-visit Age Prompt (drives age-adaptive UI per ageBand) -->
-<style>
-  #age-prompt-overlay { position:fixed; inset:0; background:rgba(15,14,12,.85); z-index:9999; align-items:center; justify-content:center; backdrop-filter:blur(8px); display:none; }
-  #age-prompt-overlay.is-open { display:flex; }
-</style>
-<div id="age-prompt-overlay" aria-hidden="true">
-  <div style="background:#fff;border-radius:12px;padding:36px 40px;max-width:480px;width:90%;box-shadow:0 24px 64px rgba(0,0,0,.4);font-family:'Inter',system-ui,sans-serif">
-    <h2 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:26px;margin:0 0 6px;color:#1a1a1a">Welcome to wallco.ai</h2>
-    <p style="margin:0 0 22px;color:#666;font-size:14px;line-height:1.5">Tell us your age so we can show you the version of this site that's easiest for you to use.</p>
-    <input type="number" id="age-input" min="3" max="99" placeholder="Your age" style="width:100%;padding:14px 16px;font-size:16px;border:1px solid #ccc;border-radius:6px;margin-bottom:14px;font-family:inherit">
-    <div style="display:flex;gap:10px">
-      <button id="age-submit" style="flex:1;padding:14px;background:#1a1a1a;color:#fff;border:0;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer">Show me the site</button>
-      <button id="age-skip" style="padding:14px 18px;background:transparent;color:#999;border:1px solid #ddd;border-radius:6px;font-size:13px;cursor:pointer">Skip</button>
-    </div>
-    <p style="margin:14px 0 0;color:#999;font-size:11px;text-align:center">Adjustable anytime in Settings.</p>
-  </div>
-</div>
-<script>
-(function(){
-  var btn = document.getElementById('nav-toggle');
-  var panel = document.getElementById('nav-panel');
-  if (btn && panel) {
-    btn.addEventListener('click', function() {
-      var open = panel.hidden;
-      panel.hidden = !open;
-      btn.setAttribute('aria-expanded', open ? 'true' : 'false');
-    });
-  }
-  // Theme toggle
-  var tt = document.getElementById('theme-toggle');
-  if (tt) {
-    tt.addEventListener('click', function() {
-      var cur = document.documentElement.getAttribute('data-theme') || 'light';
-      var next = cur === 'dark' ? 'light' : 'dark';
-      document.documentElement.setAttribute('data-theme', next);
-      localStorage.setItem('wallco-theme', next);
-    });
-  }
-  // Age prompt — first visit only
-  function ageBand(a){
-    if (!a) return '';
-    if (a < 6)  return 'toddler';
-    if (a < 11) return 'elementary';
-    if (a < 14) return 'tween';
-    if (a < 18) return 'teen';
-    if (a < 40) return 'adult';
-    if (a < 60) return 'mature';
-    if (a < 80) return 'senior';
-    return 'elder';
-  }
-  var overlay = document.getElementById('age-prompt-overlay');
-  function closeOverlay(){
-    if (!overlay) return;
-    overlay.classList.remove('is-open');
-    overlay.setAttribute('aria-hidden','true');
-  }
-  var seen = localStorage.getItem('wallco-age') || localStorage.getItem('wallco-age-skipped');
-  // Only show on the home page — deep-links (/design/:id, /studio, etc.) shouldn't be blocked.
-  var onHome = (location.pathname === '/' || location.pathname === '/index.html');
-  if (!seen && overlay && onHome) {
-    overlay.classList.add('is-open');
-    overlay.setAttribute('aria-hidden','false');
-    setTimeout(function(){ var i = document.getElementById('age-input'); if (i) i.focus(); }, 100);
-    document.getElementById('age-submit').addEventListener('click', function(){
-      var a = parseInt(document.getElementById('age-input').value, 10);
-      if (!a || a < 3 || a > 99) { document.getElementById('age-input').style.borderColor='#c00'; return; }
-      localStorage.setItem('wallco-age', String(a));
-      document.documentElement.setAttribute('data-age-band', ageBand(a));
-      closeOverlay();
-    });
-    document.getElementById('age-skip').addEventListener('click', function(){
-      localStorage.setItem('wallco-age-skipped', '1');
-      closeOverlay();
-    });
-    document.getElementById('age-input').addEventListener('keydown', function(e){
-      if (e.key === 'Enter') document.getElementById('age-submit').click();
-    });
-    // Escape key also skips
-    document.addEventListener('keydown', function(e){
-      if (e.key === 'Escape' && overlay.classList.contains('is-open')) {
-        localStorage.setItem('wallco-age-skipped', '1');
-        closeOverlay();
-      }
-    });
-  } else if (overlay) {
-    // Already seen — make sure overlay never reappears
-    closeOverlay();
-  }
-})();
-</script>
-<script>
-// Header basket-count hydration — reads localStorage['wallco-basket'] and
-// writes the count chip into the nav. No count shown when basket is empty.
-// Also listens for 'wallco-basket-changed' so the chip stays in sync when
-// + Sample is toggled on the PDP or Remove is clicked on /basket.
-(function(){
-  function paint(n){
-    var chip = document.getElementById('nav-basket-count');
-    if (!chip) return;
-    if (n > 0) { chip.textContent = n; chip.style.display = 'inline-block'; }
-    else { chip.textContent = ''; chip.style.display = 'none'; }
-  }
-  function readFromStorage(){
-    try {
-      var list = JSON.parse(localStorage.getItem('wallco-basket') || '[]');
-      if (!Array.isArray(list)) list = [];
-      return list.filter(function(x){ return typeof x === 'number' && x > 0; }).length;
-    } catch(e) { return 0; }
-  }
-  paint(readFromStorage());
-  // Event payload may carry an authoritative count; fall back to localStorage read.
-  window.addEventListener('wallco-basket-changed', function(ev){
-    var n = (ev && ev.detail && typeof ev.detail.count === 'number') ? ev.detail.count : readFromStorage();
-    paint(n);
-  });
-})();
-</script>`;
-
-// ── Hero rotation images (highest-saturation designs, sat > 0.4)
-function heroDesigns() {
-  const h = DESIGNS
-    .filter(d => d.saturation > 0.3)
-    .sort((a,b) => b.saturation - a.saturation);
-  // fallback: if none qualify, use all sorted by saturation
-  return h.length >= 2 ? h : [...DESIGNS].sort((a,b) => b.saturation - a.saturation);
-}
-
-// ── HOME PAGE
-app.get('/', (req, res) => {
-  const heroes = heroDesigns();
-  // Newest-first Featured grid — was sat-sorted, but Steve wants newly
-  // generated work to be the first thing visitors see. created_at lex sort is
-  // safe because every row is ISO-8601 UTC.
-  const featured = [...DESIGNS].sort((a,b) => String(b.created_at||'').localeCompare(String(a.created_at||''))).slice(0, 8);
-  // Pinned "Just-generated · Butterflies" rail — surfaces the latest 6
-  // butterfly-flavored designs above the Featured grid. Matches on category
-  // OR motif so we catch butterfly-trellis as well as future butterfly-*
-  // categories. Empty array → rail renders nothing (no broken shelf).
-  const butterflyLatest = [...DESIGNS]
-    .filter(d => {
-      const cat = String(d.category || '').toLowerCase();
-      if (cat.includes('butterfly')) return true;
-      return (d.motifs || []).some(m => String(m).toLowerCase().includes('butterfly'));
-    })
-    .sort((a,b) => String(b.created_at||'').localeCompare(String(a.created_at||'')))
-    .slice(0, 6);
-  const schema = JSON.stringify({
-    "@context": "https://schema.org",
-    "@graph": [
-      {
-        "@type": "Organization",
-        "name": "wallco.ai",
-        "url": "https://wallco.ai",
-        "email": "info@wallco.ai",
-        "parentOrganization": {
-          "@type": "Organization",
-          "name": "Designer Wallcoverings",
-          "url": "https://designerwallcoverings.com",
-          "address": {
-            "@type": "PostalAddress",
-            "streetAddress": "15442 Ventura Bl #102",
-            "addressLocality": "Sherman Oaks",
-            "addressRegion": "CA",
-            "postalCode": "91403"
-          }
-        }
-      },
-      {
-        "@type": "WebSite",
-        "name": "wallco.ai",
-        "url": "https://wallco.ai",
-        "description": "AI-original wallpaper and murals — every pattern generated, never repeated."
-      },
-      {
-        "@type": "ItemList",
-        "name": "Featured wallco.ai original designs",
-        "numberOfItems": featured.length,
-        "itemListElement": featured.map((d, i) => ({
-          "@type": "ListItem",
-          "position": i + 1,
-          "name": d.title,
-          "url": `https://wallco.ai/design/${d.id}`
-        }))
-      }
-    ]
-  });
-
-  const heroSlides = heroes.slice(0, 6).map((d, i) =>
-    `<div class="hero-slide ${i===0?'active':''}" data-idx="${i}" style="background-image:url('${d.image_url}')" aria-label="${d.title}"></div>`
-  ).join('\n');
-
-  // Top categories by count — used to render the home random-rail chip strip.
-  const homeCatCounts = Object.create(null);
-  for (const d of DESIGNS) {
-    const c = (d.category || '').toLowerCase().trim();
-    if (c) homeCatCounts[c] = (homeCatCounts[c] || 0) + 1;
-  }
-  const homeCats = Object.entries(homeCatCounts).sort((a,b) => b[1] - a[1]).slice(0, 6).map(([k,n]) => ({ key: k, count: n }));
-
-  const renderCard = (d) => {
-    const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
-                  : (d.room_mockups || []).includes('bedroom')     ? 'bedroom'
-                  : (d.room_mockups || []).includes('office')      ? 'office'
-                  : (d.room_mockups || [])[0];
-    const roomCount = (d.room_mockups || []).length;
-    const badgeText = roomCount > 1 ? `${roomCount} rooms` : (roomCount === 1 ? 'In a room' : '');
-    const roomOverlay = roomKey
-      ? `<img class="card-room" src="/designs/room/design_${d.id}_${roomKey}.png" loading="lazy" alt="" aria-hidden="true"><span class="card-room-badge">${badgeText}</span>`
-      : '';
-    return `
-    <a href="/design/${d.id}" class="design-card${roomKey ? ' has-room' : ''}" aria-label="${cardAriaLabel(d)}">
-      <div class="card-img" style="background-image:url('${d.image_url}')" role="img" aria-label="${d.title} pattern thumbnail">${roomOverlay}</div>
-      <div class="card-meta">
-        <span class="card-title">${d.title}</span>
-        <span class="card-cat">${d.category}</span>
-        <span class="color-dot" style="background:${d.dominant_hex}" aria-hidden="true"></span>
-      </div>
-    </a>`;
-  };
-  const featuredCards = featured.map(renderCard).join('');
-  const butterflyCards = butterflyLatest.map(renderCard).join('');
-  // Hide the whole rail if no butterflies exist — keeps the page clean if the
-  // generator goes through a non-butterfly streak.
-  const butterflySection = butterflyLatest.length ? `
-<!-- Just-generated · Butterflies — pinned above Featured -->
-<section class="featured-section">
-  <div class="section-header">
-    <h2>Just generated &middot; Butterflies</h2>
-    <a href="/designs?q=butterfly&amp;sort=newest" class="see-all">See all butterfly designs &rarr;</a>
-  </div>
-  <div class="design-grid featured-grid">
-    ${butterflyCards}
-  </div>
-</section>
-` : '';
-
-  res.type('html').send(`${htmlHead({
-    title: 'wallco.ai — AI-Original Wallpaper & Murals',
-    description: 'Original wallpaper and murals from wallco.ai. Every pattern hand-curated through our proprietary design system, never repeated. Browse, preview, and order samples.',
-    canonical: 'https://wallco.ai/',
-    // Preload the hero's first slide + first few featured cards so LCP starts
-    // fetching during <head> parse instead of after body scan discovers the
-    // background-image URLs. heroes[0] is the most likely LCP candidate.
-    preloadImages: [
-      heroes[0]?.image_url,
-      ...featured.slice(0, 4).map(d => d.image_url)
-    ].filter(Boolean)
-  })}
-<body>
-${htmlHeader('/')}
-<main>
-
-<!-- Hero: full-bleed rotating -->
-<section class="hero-section">
-  <div class="hero-track" id="hero-track">
-    ${heroSlides}
-  </div>
-  <div class="hero-controls" aria-hidden="true">
-    ${heroes.slice(0, 6).map((_,i) => `<button class="hero-dot ${i===0?'active':''}" data-slide="${i}"></button>`).join('')}
-  </div>
-</section>
-
-<!-- Wordmark below hero (Gucci-anatomy per standing rule) -->
-<div class="hero-wordmark-below">
-  <h1 class="wordmark">wallco.ai</h1>
-  <p class="tagline">AI-original wallpaper &amp; murals &mdash; every pattern generated, never repeated.</p>
-</div>
-
-${butterflySection}
-<!-- Featured designs grid -->
-<section class="featured-section">
-  <div class="section-header">
-    <h2>Newest Designs</h2>
-    <a href="/designs?sort=newest" class="see-all">See all ${DESIGNS.length} designs &rarr;</a>
-  </div>
-  <div class="design-grid featured-grid" id="featured-grid">
-    ${featuredCards}
-  </div>
-</section>
-
-<!-- Random discovery rail — fresh per visit -->
-<section class="featured-section" style="padding-top:18px">
-  <div class="section-header" style="display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap">
-    <h2>Stumble through the catalog</h2>
-    <button type="button" id="home-rail-shuffle" style="font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);padding:6px 14px;border-radius:999px;cursor:pointer">↻ Shuffle</button>
-  </div>
-  <div id="home-rail-cats" role="tablist" aria-label="Random rail category" style="display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px;align-items:center">
-    <button type="button" role="tab" data-cat="" class="home-cat-chip is-on" style="font:11px var(--sans,system-ui);padding:5px 12px;border-radius:999px;border:1px solid var(--accent,#0d0d0d);background:var(--accent,#0d0d0d);color:var(--bg,#fff);cursor:pointer">All</button>
-    ${homeCats.map(c => `<button type="button" role="tab" data-cat="${c.key}" class="home-cat-chip" style="font:11px var(--sans,system-ui);padding:5px 12px;border-radius:999px;border:1px solid var(--line,#ddd);background:transparent;color:var(--ink-soft,#555);cursor:pointer">${c.key.charAt(0).toUpperCase()+c.key.slice(1)} <span style="opacity:.55">·${c.count}</span></button>`).join('')}
-  </div>
-  <div id="home-random-rail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px;min-height:240px"></div>
-</section>
-<script>
-(function(){
-  var rail = document.getElementById('home-random-rail');
-  var btn = document.getElementById('home-rail-shuffle');
-  var catBar = document.getElementById('home-rail-cats');
-  if (!rail) return;
-  var esc = function(s){ return String(s||'').replace(/[<>&"]/g, function(c){ return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]; }); };
-  var activeCat = '';
-  try { activeCat = localStorage.getItem('wallco-home-rail-cat') || ''; } catch(e){}
-  function paintChips(){
-    if (!catBar) return;
-    catBar.querySelectorAll('.home-cat-chip').forEach(function(b){
-      var on = (b.dataset.cat || '') === activeCat;
-      b.classList.toggle('is-on', on);
-      b.style.background = on ? 'var(--accent,#0d0d0d)' : 'transparent';
-      b.style.color      = on ? 'var(--bg,#fff)'        : 'var(--ink-soft,#555)';
-      b.style.borderColor= on ? 'var(--accent,#0d0d0d)' : 'var(--line,#ddd)';
-      b.setAttribute('aria-selected', on ? 'true' : 'false');
-    });
-  }
-  function load(){
-    rail.innerHTML = '<div style="grid-column:1/-1;padding:50px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Loading…</div>';
-    var url = '/api/designs/random?n=12' + (activeCat ? '&category=' + encodeURIComponent(activeCat) : '');
-    fetch(url).then(function(r){ return r.json(); }).then(function(j){
-      if (!j.results || !j.results.length) { rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">No designs in &ldquo;'+esc(activeCat||'(any)')+'&rdquo; yet.</div>'; return; }
-      rail.innerHTML = j.results.map(function(r){
-        var dot = r.dominant_hex ? '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
-        return '<a href="'+esc(r.url)+'" style="display:block;text-decoration:none;color:inherit"><div style="aspect-ratio:1/1.15;background:#f4f1ea url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:6px;border:1px solid var(--line,#e6e2d8);margin-bottom:6px"></div><div style="font:13px var(--sans,system-ui);color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title || ('Design #'+r.id))+'</div><div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:2px">'+dot+esc(r.category||'mixed')+'</div></a>';
-      }).join('');
-    }).catch(function(){ rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Couldn\\'t load — try again.</div>'; });
-  }
-  if (catBar) {
-    catBar.addEventListener('click', function(e){
-      var b = e.target.closest('.home-cat-chip');
-      if (!b) return;
-      activeCat = b.dataset.cat || '';
-      try { localStorage.setItem('wallco-home-rail-cat', activeCat); } catch(e){}
-      paintChips();
-      load();
-    });
-  }
-  paintChips();
-  load();
-  if (btn) btn.addEventListener('click', load);
-})();
-</script>
-
-<!-- CTA band -->
-<section class="cta-band">
-  <h2>Every Design is an Original.</h2>
-  <p>No licensing. No vendor catalogs. Each pattern is generated — a wallcovering that exists nowhere else.</p>
-  <a href="/samples" class="btn-primary">Request a Free Sample</a>
-  <a href="/designs" class="btn-outline">Browse All Designs</a>
-</section>
-
-</main>
-${FOOTER}
-
-<script type="application/ld+json">${schema}</script>
-
-<!-- Hero rotation -->
-<script>
-(function(){
-  var slides = document.querySelectorAll('.hero-slide');
-  var dots   = document.querySelectorAll('.hero-dot');
-  var cur    = 0;
-  var interval;
-  function goTo(idx) {
-    slides[cur].classList.remove('active');
-    dots[cur].classList.remove('active');
-    cur = (idx + slides.length) % slides.length;
-    slides[cur].classList.add('active');
-    dots[cur].classList.add('active');
-  }
-  dots.forEach(function(d) {
-    d.addEventListener('click', function() { goTo(+this.dataset.slide); clearInterval(interval); interval = setInterval(function(){goTo(cur+1);}, 4500); });
-  });
-  interval = setInterval(function(){ goTo(cur+1); }, 4500);
-  // Pause when tab hidden
-  document.addEventListener('visibilitychange', function() {
-    if (document.hidden) clearInterval(interval);
-    else interval = setInterval(function(){goTo(cur+1);}, 4500);
-  });
-})();
-</script>
-${HAMBURGER_JS}
-<!-- Big Red avatar chat — floating launcher in lower-left -->
-<script src="${process.env.BIG_RED_URL || 'http://localhost:9935'}/widget.js"
-        data-host="${process.env.BIG_RED_URL || 'http://localhost:9935'}"
-        defer onerror="this.remove()"></script>
-</body>
-</html>`);
-});
-
-// ── Friendly category URLs — bookmarkable shortcuts to /designs?cat=...
-// Forwards any incoming query (sort/page/motif/hue) so deep links still work.
-app.get('/drunk-animals', (req, res) => {
-  const qs = new URLSearchParams({ cat: 'drunk-animals', ...req.query }).toString();
-  res.redirect(302, '/designs?' + qs);
-});
-app.get('/stoned-animals', (req, res) => {
-  const qs = new URLSearchParams({ cat: 'stoned-animals', ...req.query }).toString();
-  res.redirect(302, '/designs?' + qs);
-});
-app.get('/mural-animals', (req, res) => {
-  const qs = new URLSearchParams({ cat: 'mural-animals', ...req.query }).toString();
-  res.redirect(302, '/designs?' + qs);
-});
-app.get('/mural-scenic', (req, res) => {
-  const qs = new URLSearchParams({ cat: 'mural-scenic', ...req.query }).toString();
-  res.redirect(302, '/designs?' + qs);
-});
-
-// ── JSON feed for the /drunk-animals/live page — newest 60 designs with
-// just the fields the viewer needs (id/title/image_url/dominant_hex/prompt).
-app.get('/api/drunk-animals/feed', (req, res) => {
-  res.setHeader('Cache-Control', 'no-store');
-  const sinceId = parseInt(req.query.since_id || '0', 10) || 0;
-  const limit = Math.min(60, Math.max(1, parseInt(req.query.limit || '60', 10)));
-  const rows = DESIGNS
-    .filter(d => d.category === 'drunk-animals' && d.id > sinceId)
-    .sort((a, b) => b.id - a.id)
-    .slice(0, limit)
-    .map(d => ({
-      id: d.id,
-      title: d.title,
-      handle: d.handle,
-      image_url: d.image_url,
-      dominant_hex: d.dominant_hex,
-      created_at: d.created_at,
-      motifs: d.motifs || [],
-      url: `/design/${d.id}`,
-    }));
-  res.json({ count: rows.length, total: DESIGNS.filter(d => d.category === 'drunk-animals').length, designs: rows });
-});
-
-// ── /api/drunk-animals/best — highest-saturation N drunk-animal designs.
-// Powers the "Best of the night" rail pinned to the top of /live.
-app.get('/api/drunk-animals/best', (req, res) => {
-  res.setHeader('Cache-Control', 'no-store');
-  const n = Math.min(12, Math.max(1, parseInt(req.query.n || '3', 10)));
-  const best = DESIGNS
-    .filter(d => d.category === 'drunk-animals')
-    .sort((a, b) => (b.saturation || 0) - (a.saturation || 0))
-    .slice(0, n)
-    .map(d => ({
-      id: d.id, title: d.title, image_url: d.image_url,
-      dominant_hex: d.dominant_hex, saturation: d.saturation,
-      motifs: d.motifs || [], url: `/design/${d.id}`,
-    }));
-  res.json({ best });
-});
-
-// ── /api/drunk-animals/stats — derived snapshot for the live viewer's
-// "tonight in numbers" bar. Animals are extracted from motifs[] (when
-// llava tagged them) so the bar reflects what actually rendered, not
-// just what was prompted.
-app.get('/api/drunk-animals/stats', (_req, res) => {
-  res.setHeader('Cache-Control', 'no-store');
-  const all = DESIGNS.filter(d => d.category === 'drunk-animals');
-  const total = all.length;
-  if (total === 0) return res.json({ total: 0 });
-  const ANIMAL_TAGS = ['frog','elephant','giraffe','orangutan','capybara','lemur','sloth','macaw','panda','peacock','parrot','monkey'];
-  const animalCounts = {};
-  let satSum = 0, satCount = 0, earliest = null, latest = null;
-  for (const d of all) {
-    const motifs = (d.motifs || []);
-    for (const a of ANIMAL_TAGS) if (motifs.includes(a)) animalCounts[a] = (animalCounts[a] || 0) + 1;
-    if (typeof d.saturation === 'number') { satSum += d.saturation; satCount++; }
-    const t = Date.parse(d.created_at || '');
-    if (t) {
-      if (!earliest || t < earliest) earliest = t;
-      if (!latest || t > latest)   latest = t;
-    }
-  }
-  const animals = Object.entries(animalCounts).sort((a,b)=>b[1]-a[1]);
-  const elapsedMs = (earliest && latest) ? (latest - earliest) : 0;
-  const hoursElapsed = elapsedMs / 3_600_000;
-  const designsPerHour = hoursElapsed > 0.1 ? (total / hoursElapsed) : null;
-  // Is the generator currently in its active window? Tick script runs only
-  // 18:00→05:59 PT (STOP_HOUR=6). Outside that window, show 'complete'.
-  const STOP_HOUR = 6;
-  const nowPT = new Date(new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));
-  const h = nowPT.getHours();
-  const inWindow = (h >= 18) || (h < STOP_HOUR);
-  res.json({
-    total,
-    animals,
-    avg_saturation: satCount ? +(satSum/satCount).toFixed(3) : null,
-    earliest_at: earliest ? new Date(earliest).toISOString() : null,
-    latest_at:   latest   ? new Date(latest).toISOString()   : null,
-    designs_per_hour: designsPerHour ? +designsPerHour.toFixed(1) : null,
-    status: inWindow ? 'live' : 'complete',
-    hours_elapsed: +hoursElapsed.toFixed(1),
-  });
-});
-
-// ── /drunk-animals/live — auto-polling stream view of the overnight run.
-// New designs slide in at the top every 30s. Dark immersive theme matches
-// the collection's botanical-pop-Warhol aesthetic.
-app.get('/drunk-animals/live', (_req, res) => {
-  res.type('html').send(`<!doctype html>
-<html lang="en"><head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<title>Drunk Animals · Live · wallco.ai</title>
-<style>
-:root { --bg:#0e0612; --fg:#fff; --pink:#ff79c6; --green:#50fa7b; --line:rgba(255,255,255,.12); }
-* { box-sizing:border-box }
-body { margin:0; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Roboto,sans-serif; min-height:100vh }
-header { position:sticky; top:0; z-index:10; padding:14px 20px; background:linear-gradient(180deg,rgba(14,6,18,.96),rgba(14,6,18,.7)); backdrop-filter:blur(12px); border-bottom:1px solid var(--line); display:flex; align-items:center; gap:14px; flex-wrap:wrap }
-header h1 { font-family:'Playfair Display','Times New Roman',serif; font-weight:400; font-size:22px; margin:0; line-height:1 }
-header .meta { display:flex; gap:8px; align-items:center; font-size:12px; color:#e6d4f0 }
-header .pill { padding:3px 10px; border-radius:999px; border:1px solid var(--pink); color:var(--pink) }
-header .pill.green { border-color:var(--green); color:var(--green) }
-header .pulse::before { content:""; display:inline-block; width:7px; height:7px; background:var(--green); border-radius:50%; margin-right:6px; animation:pulse 1.6s ease-in-out infinite }
-@keyframes pulse { 0%,100%{opacity:.4} 50%{opacity:1} }
-header a { color:#e6d4f0; text-decoration:none; font-size:12px; padding:4px 10px; border:1px solid var(--line); border-radius:999px }
-header a:hover { border-color:var(--pink); color:var(--pink) }
-.stats-bar { padding:14px 20px; background:rgba(255,255,255,.02); border-bottom:1px solid var(--line); display:flex; gap:16px; flex-wrap:wrap; align-items:center; font-size:12px }
-.stats-bar .stat { display:flex; flex-direction:column; gap:2px; padding:0 14px; border-right:1px solid var(--line) }
-.stats-bar .stat:last-child { border-right:0 }
-.stats-bar .stat-label { font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:#9586a3 }
-.stats-bar .stat-value { font-family:'Playfair Display',serif; font-size:18px; color:#fff }
-.stats-bar .animals { display:flex; gap:6px; flex-wrap:wrap; padding-left:14px }
-.stats-bar .animal-pill { padding:3px 10px; border-radius:999px; background:rgba(255,121,198,.12); color:var(--pink); font-size:11px; white-space:nowrap }
-.stats-bar .animal-pill strong { color:#fff; margin-left:4px; font-weight:600 }
-.best-rail { padding:24px 20px 8px; background:linear-gradient(180deg,rgba(255,121,198,.06),transparent) }
-.best-rail h2 { font-family:'Playfair Display',serif; font-weight:400; font-size:14px; margin:0 0 12px; letter-spacing:.2em; text-transform:uppercase; color:var(--pink) }
-.best-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:14px }
-.best-card { aspect-ratio:1/1; background-size:cover; background-position:center; border-radius:14px; position:relative; overflow:hidden; display:block; text-decoration:none; color:inherit; border:1px solid rgba(255,121,198,.25); transition:transform .25s ease, box-shadow .25s ease }
-.best-card:hover { transform:translateY(-3px); box-shadow:0 12px 28px rgba(255,121,198,.25) }
-.best-card .info { position:absolute; left:0; right:0; bottom:0; padding:10px 14px; background:linear-gradient(180deg,transparent,rgba(0,0,0,.7)); font-size:12px }
-.best-card .info .t { font-family:'Playfair Display',serif; font-size:16px; line-height:1.2; margin:0 }
-.best-card .info .id { font-size:10px; opacity:.7; letter-spacing:.08em; margin-top:2px }
-main { padding:20px; max-width:1500px; margin:0 auto }
-.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:18px }
-.card { background:#1a0f1a; border:1px solid var(--line); border-radius:14px; overflow:hidden; position:relative; opacity:0; transform:translateY(-12px); animation:slidein .5s ease forwards }
-@keyframes slidein { to { opacity:1; transform:translateY(0) } }
-.card a { display:block; text-decoration:none; color:inherit }
-.card .img { aspect-ratio:1/1; background-size:cover; background-position:center }
-.card .meta { padding:12px 14px }
-.card .meta .title { font-family:'Playfair Display',serif; font-size:17px; line-height:1.2; margin:0 0 6px }
-.card .meta .id { font-size:11px; opacity:.55; letter-spacing:.06em }
-.card .dot { display:inline-block; width:10px; height:10px; border-radius:50%; vertical-align:middle; margin-left:6px; border:1px solid rgba(255,255,255,.2) }
-.card .motifs { display:flex; flex-wrap:wrap; gap:4px; margin-top:8px }
-.card .motifs span { font-size:10px; padding:2px 7px; background:rgba(255,121,198,.15); color:var(--pink); border-radius:999px; text-transform:lowercase }
-.card.new { box-shadow:0 0 0 2px var(--green), 0 8px 28px rgba(80,250,123,.25) }
-.empty { text-align:center; padding:80px 20px; opacity:.5 }
-</style>
-</head><body>
-<header>
-  <h1>The Drunk Animals Collection</h1>
-  <span class="meta pulse" id="status-pill">live</span>
-  <span class="pill" id="count">0 designs</span>
-  <span class="pill green" id="next">next in —</span>
-  <a href="/drunk-animals">Grid view</a>
-  <a href="/">wallco.ai</a>
-</header>
-<div class="stats-bar" id="statsbar">
-  <div class="stat"><span class="stat-label">Total</span><span class="stat-value" id="s-total">—</span></div>
-  <div class="stat"><span class="stat-label">Per hour</span><span class="stat-value" id="s-rate">—</span></div>
-  <div class="stat"><span class="stat-label">Avg saturation</span><span class="stat-value" id="s-sat">—</span></div>
-  <div class="animals" id="s-animals"></div>
-</div>
-<section class="best-rail" id="best-rail" style="display:none">
-  <h2>Best of the night · most saturated 3</h2>
-  <div class="best-row" id="best-row"></div>
-</section>
-<main>
-  <div class="grid" id="grid"></div>
-  <div class="empty" id="empty">Loading the menagerie…</div>
-</main>
-<script>
-const grid = document.getElementById('grid');
-const empty = document.getElementById('empty');
-const countEl = document.getElementById('count');
-const nextEl = document.getElementById('next');
-let maxId = 0;
-let lastTickAt = Date.now();
-
-function card(d, isNew) {
-  const motifs = (d.motifs || []).slice(0, 6).map(m => '<span>' + m + '</span>').join('');
-  return \`<article class="card\${isNew?' new':''}">
-    <a href="\${d.url}" target="_blank" rel="noopener">
-      <div class="img" style="background-image:url('\${d.image_url}')"></div>
-      <div class="meta">
-        <p class="title">\${d.title}<span class="dot" style="background:\${d.dominant_hex||'#888'}"></span></p>
-        <p class="id">#\${d.id} · \${(d.created_at||'').slice(11,16)} UTC</p>
-        <div class="motifs">\${motifs}</div>
-      </div>
-    </a>
-  </article>\`;
-}
-
-async function poll() {
-  try {
-    const r = await fetch('/api/drunk-animals/feed?since_id=' + maxId);
-    const j = await r.json();
-    countEl.textContent = j.total + ' design' + (j.total === 1 ? '' : 's');
-    if (j.designs.length === 0 && maxId === 0) {
-      empty.textContent = 'No designs yet — generator just started';
-      return;
-    }
-    empty.style.display = 'none';
-    // Prepend new
-    if (j.designs.length) {
-      const isFirstLoad = maxId === 0;
-      const fragHtml = j.designs.map(d => card(d, !isFirstLoad)).join('');
-      grid.insertAdjacentHTML('afterbegin', fragHtml);
-      maxId = Math.max(maxId, ...j.designs.map(d => d.id));
-      if (!isFirstLoad) lastTickAt = Date.now();
-    }
-  } catch (e) {
-    console.error('feed poll failed', e);
-  }
-}
-
-function updateNext() {
-  // We expect a new design every 4 min from the daemon. Show countdown.
-  const ms = 4*60*1000 - (Date.now() - lastTickAt);
-  if (ms <= 0) { nextEl.textContent = 'next any moment'; return; }
-  const m = Math.floor(ms / 60000), s = Math.floor((ms % 60000) / 1000);
-  nextEl.textContent = 'next in ' + m + ':' + String(s).padStart(2,'0');
-}
-
-async function pollBest() {
-  try {
-    const r = await fetch('/api/drunk-animals/best?n=3');
-    const j = await r.json();
-    if (!j.best || !j.best.length) return;
-    const rail = document.getElementById('best-rail');
-    const row  = document.getElementById('best-row');
-    row.innerHTML = j.best.map(d => \`<a class="best-card" href="\${d.url}" target="_blank" rel="noopener" style="background-image:url('\${d.image_url}')">
-      <div class="info"><p class="t">\${d.title}</p><p class="id">#\${d.id} · sat \${(d.saturation||0).toFixed(2)}</p></div>
-    </a>\`).join('');
-    rail.style.display = 'block';
-  } catch (e) { console.warn('best poll failed', e); }
-}
-
-async function pollStats() {
-  try {
-    const r = await fetch('/api/drunk-animals/stats');
-    const j = await r.json();
-    if (!j.total) return;
-    document.getElementById('s-total').textContent = j.total;
-    document.getElementById('s-rate').textContent  = j.designs_per_hour != null ? j.designs_per_hour.toFixed(1) : '—';
-    document.getElementById('s-sat').textContent   = j.avg_saturation   != null ? j.avg_saturation.toFixed(2)  : '—';
-    const ani = document.getElementById('s-animals');
-    ani.innerHTML = (j.animals || []).slice(0, 8)
-      .map(([name, n]) => '<span class="animal-pill">' + name + '<strong>' + n + '</strong></span>').join('');
-    // Status pill: live (overnight window) vs complete (daylight quiet hours).
-    const pill = document.getElementById('status-pill');
-    const next = document.getElementById('next');
-    if (j.status === 'complete') {
-      pill.textContent = 'complete · ' + (j.hours_elapsed||0).toFixed(1) + 'h run';
-      pill.classList.remove('pulse');
-      pill.style.color = '#50fa7b';
-      next.style.display = 'none';
-    } else {
-      pill.textContent = 'live';
-      pill.classList.add('pulse');
-      pill.style.color = '';
-      next.style.display = '';
-    }
-  } catch (e) { console.warn('stats poll failed', e); }
-}
-
-poll();
-pollStats();
-pollBest();
-setInterval(poll, 30_000);
-setInterval(pollStats, 60_000);
-setInterval(pollBest, 120_000);
-setInterval(updateNext, 1000);
-</script>
-</body></html>`);
-});
-
-// ── DESIGNS CATALOG (full grid with sort + density + category filter)
-// ── /designs role gate (2026-05-20) — login required to view ──
-// Steve directive: "require login https://wallco.ai/designs admin/user/designer"
-// Three roles, any one grants access. Admin path is the existing dw_auth JWT
-// cookie. User + designer roles set a `dw_role` cookie via /login.
-function readRole(req) {
-  // 1) Admin path — dw_auth JWT (existing)
-  try { if (isAdmin(req)) return 'admin'; } catch (_) {}
-  // 2) Lightweight role cookie (user / designer)
-  const raw = (req.headers && req.headers.cookie) || '';
-  for (const part of String(raw).split(/;\s*/)) {
-    const eq = part.indexOf('=');
-    if (eq > -1 && part.slice(0, eq) === 'dw_role') {
-      const v = decodeURIComponent(part.slice(eq + 1));
-      if (v === 'user' || v === 'designer' || v === 'admin') return v;
-    }
-  }
-  return null;
-}
-
-// /login — 3-role landing page. User/designer paths set dw_role cookie and
-// redirect back to /designs (or whatever ?next= specifies).
-app.get('/login', (req, res) => {
-  const next = String(req.query.next || '/designs').replace(/[^\w/?=&.\-]/g, '');
-  res.type('html').send(`<!doctype html>
-<html><head><meta charset="utf-8"><title>Wallco · Sign In</title>
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<style>
-  :root{--ink:#3a2818;--bg:#faf6ec;--accent:#c9a14b;--line:#d8cdb0;--soft:#8a7c66}
-  *{box-sizing:border-box}body{margin:0;padding:0;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center}
-  .card{max-width:420px;width:90%;background:#fff;border:1px solid var(--line);border-radius:8px;padding:36px 32px;box-shadow:0 8px 24px rgba(60,40,20,.08)}
-  h1{margin:0 0 6px;font:300 32px/1.1 'Bodoni Moda',Georgia,serif;letter-spacing:.02em}
-  .sub{color:var(--soft);font-size:13px;margin-bottom:28px}
-  .row{display:flex;flex-direction:column;gap:10px;margin-top:12px}
-  button{appearance:none;padding:14px 18px;border:1px solid var(--line);background:#fff;color:var(--ink);font:600 13px/1 ui-sans-serif,system-ui;letter-spacing:.06em;text-transform:uppercase;border-radius:5px;cursor:pointer;transition:all .15s}
-  button:hover{border-color:var(--ink);background:var(--bg)}
-  button.primary{background:var(--ink);color:#fff;border-color:var(--ink)}
-  button.primary:hover{background:#0d0c0a}
-  button.accent{background:var(--accent);color:#fff;border-color:var(--accent)}
-  button.accent:hover{background:#a8843d}
-  .hint{color:var(--soft);font-size:11px;letter-spacing:.04em;margin-top:18px;text-align:center}
-  .role-label{font-size:10px;color:var(--soft);letter-spacing:.14em;text-transform:uppercase;margin-bottom:4px}
-  form{margin:0}
-</style></head>
-<body>
-<div class="card">
-  <h1>Wallco.ai</h1>
-  <div class="sub">Choose how you're accessing the design library.</div>
-  <div class="row">
-    <form method="POST" action="/login"><input type="hidden" name="role" value="designer"><input type="hidden" name="next" value="${next}"><button class="accent" type="submit" style="width:100%">★ Sign in as Designer / Trade</button></form>
-    <form method="POST" action="/login"><input type="hidden" name="role" value="user"><input type="hidden" name="next" value="${next}"><button type="submit" style="width:100%">Sign in as Customer</button></form>
-    <form method="POST" action="/login"><input type="hidden" name="role" value="admin"><input type="hidden" name="next" value="${next}"><button class="primary" type="submit" style="width:100%">Admin Login</button></form>
-  </div>
-  <div class="hint">Designer + Customer access is open during preview. Admin login routes through /_devlogin.</div>
-</div>
-</body></html>`);
-});
-app.post('/login', express.urlencoded({ extended: false, limit: '2kb' }), (req, res) => {
-  const role = String(req.body?.role || '').trim();
-  const next = String(req.body?.next || '/designs').replace(/[^\w/?=&.\-]/g, '');
-  if (role === 'admin') {
-    // Bounce to existing dev-login for real JWT mint
-    return res.redirect(`/_devlogin?next=${encodeURIComponent(next)}`);
-  }
-  if (role === 'user' || role === 'designer') {
-    res.cookie('dw_role', role, { httpOnly: false, maxAge: 30 * 24 * 3600 * 1000, sameSite: 'lax', path: '/' });
-    return res.redirect(next);
-  }
-  res.redirect('/login');
-});
-
-app.get('/designs', (req, res) => {
-  // 2026-05-20 Steve directive: NO hard server gate. Allow 1 page browsing,
-  // then a client-side scroll-triggered modal fades in requiring login.
-  // The readRole(req) result is still passed to the page (as window._dwRole)
-  // so the JS knows whether to suppress the modal for already-logged-in users.
-  const _role = readRole(req);
-  // Conditional GET: emit Last-Modified from the freshest design in the
-  // in-memory catalog. Crawlers issuing If-Modified-Since get a 304 when
-  // no new design has landed since their last crawl.
-  if (maybe304(req, res, CATALOG_LAST_MODIFIED)) return;
-  const sort = req.query.sort || 'newest';
-  const cat  = req.query.cat  || '';
-  const q    = (req.query.q   || '').toLowerCase().trim();
-  const page = Math.max(1, parseInt(req.query.page || '1', 10));
-  const PER  = 60;
-  // Admin/review-mode flag: only enabled at 127.0.0.1 / localhost / ?review=1 — public visits unchanged.
-  // Live-view override: an authenticated admin can preview the page as a
-  // customer by appending ?view=live OR by setting view_mode=live cookie via
-  // the corner-bottom toggle. `_isAdminAuth` retains the real auth state so
-  // the toggle button itself only renders for admins, but `_isAdmin` (used
-  // by every render decision below) flips to false while previewing.
-  const _isAdminAuth = isAdmin(req);
-  // Manual cookie read — cookie-parser isn't in deps so req.cookies is undefined.
-  const _viewModeCookie = (function(){
-    const raw = (req.headers && req.headers.cookie) || '';
-    for (const part of String(raw).split(/;\s*/)) {
-      const eq = part.indexOf('=');
-      if (eq > -1 && part.slice(0, eq) === 'view_mode') return decodeURIComponent(part.slice(eq + 1));
-    }
-    return '';
-  })();
-  const _viewAsLive = req.query.view === 'live' || _viewModeCookie === 'live';
-  const _isAdmin = _isAdminAuth && !_viewAsLive;
-
-  const motifQ = (req.query.motif || '').toLowerCase().trim();
-  const hueQ   = (req.query.hue   || '').toLowerCase().trim();
-  const roomQ  = (req.query.room  || '').toLowerCase().trim();  // '1' or 'all' = only designs with room previews
-  function hueBucketOf(hex) {
-    if (!hex || !/^#[0-9a-fA-F]{6}$/.test(hex)) return null;
-    const r = parseInt(hex.slice(1,3),16)/255, g = parseInt(hex.slice(3,5),16)/255, b = parseInt(hex.slice(5,7),16)/255;
-    const max = Math.max(r,g,b), min = Math.min(r,g,b);
-    if (max === min) return 'neutral';
-    let h = 0; const d = max - min;
-    if (max === r) h = (g-b)/d + (g<b?6:0);
-    else if (max === g) h = (b-r)/d + 2;
-    else h = (r-g)/d + 4;
-    h = (h * 60 + 360) % 360;
-    const l = (max + min) / 2;
-    const s = (max - min) / (l > 0.5 ? 2 - max - min : max + min);
-    if (s < 0.1) return 'neutral';
-    if (h < 15 || h >= 345) return 'rose';
-    if (h < 40)  return 'amber';
-    if (h < 60)  return 'honey';
-    if (h < 90)  return 'olive';
-    if (h < 160) return 'sage';
-    if (h < 200) return 'marine';
-    if (h < 250) return 'sapphire';
-    if (h < 290) return 'mauve';
-    return 'plum';
-  }
-  // Hard filter — never render a card whose image file isn't on disk OR whose
-  // id is in the blank-guard. /api/designs already does this; mirror it on the
-  // HTML page so missing-file rows don't paint empty cards that 404 their
-  // background-image url(). (data/generated/ is in deploy exclude list, so any
-  // var_NNNN_* PNG generated locally but not rsynced is 404 on prod.)
-  // ALSO drop any design whose category matches a DW vendor name (defined
-  // a few lines below). Same Steve directive — vendor-named cards never go
-  // to public /designs (live on /library admin-only instead).
-  const _VENDOR_DENY = /\b(thibaut|koroseal|schumacher|maya[ -]romanoff|scalamandre|arte[ -]international|cole[ -]?(and|&)[ -]?son|phillip[ -]jeffries|osborne[ -]?(and|&)?[ -]?little|de[ -]gournay|fromental|sister[ -]parish|dedar|brewster|mind[ -]the[ -]gap|hygge|designtex|wolf[ -]gordon|carnegie|coordonn|daisy[ -]bennett|china[ -]seas|york|marburg|romo|fabricut|stout|knoll|arteriors|timorous[ -]beasties|hangups)\b/i;
-  let filtered = DESIGNS.filter(d => designHasImage(d) && !_VENDOR_DENY.test(String(d.category || '')));
-  if (cat)    filtered = filtered.filter(d => d.category === cat);
-  if (motifQ) filtered = filtered.filter(d => (d.motifs || []).includes(motifQ));
-  if (hueQ)   filtered = filtered.filter(d => hueBucketOf(d.dominant_hex) === hueQ);
-  if (roomQ)  filtered = filtered.filter(d => (d.room_mockups || []).length > 0);
-  // Search-alias expansion — typing "bh" / "beverly hills" / "bh90210"
-  // still resolves the LA collection (and vice-versa). Group-of-synonyms:
-  // any token in the same row matches any other token in that row.
-  // Steve directive 2026-05-13: rename to LA, keep BH searchable via meta.
-  const SEARCH_ALIASES = [
-    ['bh', 'bh90210', 'beverly hills', 'beverlyhills', 'beverly-hills',
-     'la', 'la90210', 'los angeles', 'los-angeles', 'la collection', 'la-collection'],
-  ];
-  function expandQ(qstr) {
-    const lowered = String(qstr).toLowerCase();
-    for (const group of SEARCH_ALIASES) {
-      if (group.some(token => lowered.includes(token))) return group;
-    }
-    return [lowered];
-  }
-  if (q) {
-    const queries = expandQ(q);
-    filtered = filtered.filter(d => queries.some(qq =>
-      d.title.toLowerCase().includes(qq) ||
-      d.category.toLowerCase().includes(qq) ||
-      d.handle.toLowerCase().includes(qq) ||
-      (d.motifs || []).some(m => m.toLowerCase().includes(qq)) ||
-      (d.tags || []).some(t => t.toLowerCase().includes(qq))
-    ));
-  }
-  filtered = sortDesigns(filtered, sort);
-
-  const total = filtered.length;
-  const pages = Math.max(1, Math.ceil(total / PER));
-  const slice = filtered.slice((page-1)*PER, page*PER);
-  // Defense-in-depth: NEVER include DW vendor names in the category dropdown.
-  // Steve directive 2026-05-20 — vendor names (Thibaut/Koroseal/Fromental/etc.)
-  // leaked into the dropdown via legacy dw-shopify imports + an AI-gen track
-  // that had a vendor-named slug. PG was bulk-renamed and night-builder's track
-  // string was scrubbed, but this regex is the failsafe so a future drift can't
-  // re-leak. Pair with the bulk PG rename if a new vendor name appears.
-  const VENDOR_DENYLIST = /\b(thibaut|koroseal|schumacher|maya[ -]romanoff|scalamandre|arte[ -]international|cole[ -]?(and|&)[ -]?son|phillip[ -]jeffries|osborne[ -]?(and|&)?[ -]?little|de[ -]gournay|fromental|sister[ -]parish|dedar|brewster|mind[ -]the[ -]gap|hygge|designtex|wolf[ -]gordon|carnegie|coordonn|daisy[ -]bennett|china[ -]seas|york|marburg|romo|fabricut|stout|knoll|arteriors|timorous[ -]beasties|hangups)\b/i;
-  const cats  = [...new Set(DESIGNS.map(d => d.category))]
-    .filter(c => c && !VENDOR_DENYLIST.test(c))
-    .sort();
-
-  // Top motifs across full catalog (not filtered)
-  const motifCounts = {};
-  DESIGNS.forEach(d => (d.motifs || []).forEach(m => { motifCounts[m] = (motifCounts[m]||0) + 1; }));
-  const topMotifs = Object.entries(motifCounts).sort((a,b)=>b[1]-a[1]).slice(0,12);
-
-  const HUE_DEFS = [
-    ['rose',     '#e08291'], ['amber',   '#c97b3a'], ['honey',   '#d2a44a'],
-    ['olive',    '#8a8c4a'], ['sage',    '#8aa17a'], ['marine',  '#6aa6b6'],
-    ['sapphire', '#5a82a8'], ['mauve',   '#a08bbf'], ['plum',    '#8e5887'],
-    ['neutral',  '#b8b3a8']
-  ];
-  const hueCounts = {};
-  DESIGNS.forEach(d => { const b = hueBucketOf(d.dominant_hex); if (b) hueCounts[b] = (hueCounts[b]||0)+1; });
-
-  const cards = slice.map(d => {
-    if (!_isAdmin) {
-      const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
-                    : (d.room_mockups || []).includes('bedroom')     ? 'bedroom'
-                    : (d.room_mockups || []).includes('office')      ? 'office'
-                    : (d.room_mockups || [])[0];
-      const roomCount = (d.room_mockups || []).length;
-      const badgeText = roomCount > 1 ? `${roomCount} rooms` : (roomCount === 1 ? 'In a room' : '');
-      const roomOverlay = roomKey
-        ? `<img class="card-room" src="/designs/room/design_${d.id}_${roomKey}.png" loading="lazy" alt="" aria-hidden="true"><span class="card-room-badge">${badgeText}</span>`
-        : '';
-      // NEW badge — fires for designs created within the last NEW_BADGE_HOURS.
-      // Was 72h; tightened to 6h on 2026-05-14 after observing every card glowed
-      // gold (6010/6010 designs <72h old after recent generator burst). At 6h
-      // the cohort drops to ~450 designs (about 1 page worth of "actually new"),
-      // making the ring a real signal again. Env-overridable for tuning.
-      const NEW_BADGE_HOURS = parseFloat(process.env.NEW_BADGE_HOURS || '6');
-      const createdMs = Date.parse(d.created_at || '') || 0;
-      const isNew = createdMs && (Date.now() - createdMs < NEW_BADGE_HOURS * 3600 * 1000);
-      const newBadge = isNew ? `<span class="card-new-badge" aria-label="Newly added">NEW</span>` : '';
-      // DW-style SKU — derived from the design id with a stable two-letter prefix
-      // for the wallco.ai sub-label (WC). Designer Wallcoverings catalog convention
-      // is DW-<vendor-code>-<numeric>, so wallco.ai pieces become DW-WC-####.
-      const sku = `DW-WC-${String(d.id).padStart(4, '0')}`;
-      // Color name — interior-designer-skill style: derive a poetic color name
-      // from the dominant_hex by binning into the same hue buckets the page
-      // filter uses, then prefer the leading word of the title (Honey/Amber/
-      // Rose/Sage/…) when the generator already gave us one — that's the
-      // canonical color family per the prompt library. Falls back to the
-      // hue bucket if the title doesn't lead with one.
-      const colorNameFromTitle = (String(d.title || '').match(/^(Honey|Amber|Rose|Sage|Olive|Marine|Sapphire|Mauve|Plum|Neutral|Bone|Champagne|Ivory|Emerald|Navy|Coral|Blush|Indigo|Charcoal)/i) || [])[1];
-      const colorName = colorNameFromTitle || (hueBucketOf(d.dominant_hex) || 'neutral').replace(/^./, c => c.toUpperCase());
-      // Style tag — category is the closest thing to a style category in the
-      // data model (damask, butterfly-trellis, stoned-animals, mural-scenic).
-      const styleTag = String(d.category || 'mixed').replace(/-/g, ' ');
-      // DW Original Number (DIG_AI_xxxxxx) — auto-assigned at DB INSERT
-      // time by trg_spoon_all_designs_assign_dig so it's always present.
-      // The fallback expression matches the trigger's exact output so we
-      // can show it on the card without re-querying PG for every row.
-      const digNum = d.dig_number || `DIG_AI_${String(d.id).padStart(6, '0')}`;
-      // Wrapper is a <div> (not <a>) because a <button> + block <div> inside
-      // <a> trips the HTML5 parser's Adoption Agency Algorithm and splits the
-      // anchor into multiple sibling top-level nodes — which then become
-      // SEPARATE grid items, producing the alternating-blank-tile bug.
-      // Keep the anchor as an inner link wrapping just the visual; controls
-      // (fav, NEW, meta) sit alongside as siblings of the link.
-      // card-hover is a SIBLING of card-link (not nested inside) so the inner
-      // Sample <a> doesn't trigger the same Adoption Agency split that took
-      // out the button-in-anchor pattern (2026-05-14). card-hover positions
-      // absolute over the .design-card; clicks on its own anchors navigate
-      // directly without bubbling to card-link.
-      return `
-    <div class="design-card${roomKey ? ' has-room' : ''}${isNew ? ' is-new' : ''}" data-design-id="${d.id}" data-dig="${digNum}">
-      <button type="button" class="fav-btn" data-fav-id="${d.id}" aria-pressed="false" aria-label="Save ${d.title} to favorites" title="Favorite"><span class="fav-icon"></span></button>
-      ${newBadge}
-      <a href="/design/${d.id}" class="card-link" aria-label="${cardAriaLabel(d)}">
-        <div class="card-img" style="background-image:url('${d.image_url}')" role="img" aria-label="${d.title} pattern thumbnail">${roomOverlay}</div>
-        <div class="card-meta">
-          <span class="card-title">${d.title}</span>
-          <span class="card-sku" title="DW catalog SKU">${sku}</span>
-          <span class="card-colorname">${colorName} <span class="color-dot" style="background:${d.dominant_hex}" aria-hidden="true"></span><span class="card-hex">${(d.dominant_hex || '').toUpperCase()}</span></span>
-          <span class="card-tags">
-            <span class="card-ai-badge" title="AI-designed pattern">✦ AI-Designed</span>
-            <span class="card-style">${styleTag}</span>
-          </span>
-        </div>
-      </a>
-      <div class="card-hover" aria-hidden="true">
-        <span class="card-hover-dig" title="DW Original Number">${digNum}</span>
-        <div class="card-hover-actions">
-          <a class="chov-btn chov-view" href="/design/${d.id}">View</a>
-          <a class="chov-btn chov-sample" href="/samples?design=${d.id}">Sample</a>
-        </div>
-      </div>
-    </div>`;
-    }
-    // Admin review-mode card: sliders, K/R, chips, why-chip
-    // Show the DW Original Number (DIG_AI_xxxxxx) as the pattern pill so it
-    // matches the buyer view + the Shopify SKU written by /upload-shopify.
-    const _digAdm = d.dig_number || `DIG_AI_${String(d.id).padStart(6, '0')}`;
-    return `
-    <div class="design-card review-card" data-id="${d.id}" data-dig="${_digAdm}">
-      <a href="/design/${d.id}" target="_blank" class="card-img" style="background-image:url('${d.image_url}')" rel="noopener noreferrer">
-        <span class="decision-badge"></span>
-        <div class="card-hover-actions admin-hover-actions" aria-hidden="true">
-          <a class="chov-btn chov-view" href="/design/${d.id}" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">View</a>
-          <a class="chov-btn chov-sample" href="/samples?design=${d.id}" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">Sample</a>
-        </div>
-      </a>
-      <span class="pattern-num" aria-hidden="true">${_digAdm}</span>
-      <div class="review-flyout">
-        <div class="card-meta">
-          <span class="card-title">${d.title}</span>
-          <span class="card-cat">${d.category}</span>
-          <span class="color-dot" style="background:${d.dominant_hex}"></span>
-        </div>
-        <div class="review-controls">
-          <div class="sliders">
-            <label>Design <input type="range" min="0" max="10" value="5" data-axis="design"><span>5</span></label>
-            <label>Color  <input type="range" min="0" max="10" value="5" data-axis="color"><span>5</span></label>
-            <label>Style  <input type="range" min="0" max="10" value="5" data-axis="style"><span>5</span></label>
-          </div>
-          <div class="decision">
-            <button class="btn-keep" type="button">Keep</button>
-            <button class="btn-reject" type="button">Reject</button>
-            <button class="btn-pair" type="button" title="Suggest combinations">Pair</button>
-          </div>
-          <div class="why-chip" title="Click to chat about this verdict"></div>
-          <div class="chips"></div>
-        </div>
-      </div>
-    </div>`;
-  }).join('');
-
-  const pagerLinks = pages > 1
-    ? Array.from({length: pages}, (_,i) => i+1).map(p =>
-        `<a href="/designs?sort=${sort}&cat=${encodeURIComponent(cat)}&q=${encodeURIComponent(q)}&page=${p}" class="pager-link ${p===page?'active':''}">${p}</a>`
-      ).join(' ')
-    : '';
-
-  // Each ListItem embeds a full Product object so Google can render
-  // list-of-products rich results (not just bare URLs). Mirrors the
-  // per-design Product schema from /design/:id but trimmed to grid-card
-  // essentials: name, image, sku, category, brand, offers.
-  const _hueOf = (hex) => {
-    const h = (hex || '').toLowerCase();
-    if (!/^#[0-9a-f]{6}$/.test(h)) return '';
-    const r=parseInt(h.slice(1,3),16), g=parseInt(h.slice(3,5),16), b=parseInt(h.slice(5,7),16);
-    const max=Math.max(r,g,b), min=Math.min(r,g,b), s=max?(max-min)/max:0;
-    if (s<0.12) return 'neutral';
-    const hh = ((max===r ? (g-b)/(max-min)+(g<b?6:0) : max===g ? (b-r)/(max-min)+2 : (r-g)/(max-min)+4) * 60 + 360) % 360;
-    if (hh<15||hh>=345) return 'rose'; if (hh<40) return 'amber'; if (hh<60) return 'honey';
-    if (hh<90) return 'olive';  if (hh<160) return 'sage';  if (hh<200) return 'marine';
-    if (hh<250) return 'sapphire'; if (hh<290) return 'mauve'; return 'plum';
-  };
-
-  const itemList = {
-    "@context": "https://schema.org",
-    "@type": "ItemList",
-    "name": "Original Wallpaper Designs — wallco.ai",
-    "url": `https://wallco.ai/designs${page > 1 ? `?page=${page}` : ''}`,
-    "numberOfItems": total,
-    "itemListOrder": "https://schema.org/ItemListOrderDescending",
-    "itemListElement": slice.map((d,i) => ({
-      "@type": "ListItem",
-      "position": (page-1)*PER+i+1,
-      "item": {
-        "@type": "Product",
-        "@id": `https://wallco.ai/design/${d.id}`,
-        "name": d.title,
-        "url": `https://wallco.ai/design/${d.id}`,
-        "image": `https://wallco.ai${d.image_url}`,
-        "sku": d.handle || `wallco-${String(d.id).padStart(4,'0')}`,
-        "productID": String(d.id),
-        "category": d.category,
-        "color": _hueOf(d.dominant_hex) || undefined,
-        "material": "Wallpaper",
-        "brand": { "@type": "Brand", "name": "wallco.ai" },
-        "offers": {
-          "@type": "Offer",
-          "availability": "https://schema.org/InStock",
-          "url": `https://wallco.ai/samples?design=${d.id}`,
-          "priceCurrency": "USD",
-          "description": "Free sample available"
-        }
-      }
-    }))
-  };
-
-  const breadcrumb = {
-    "@context": "https://schema.org",
-    "@type": "BreadcrumbList",
-    "itemListElement": [
-      { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://wallco.ai/" },
-      { "@type": "ListItem", "position": 2, "name": "Designs", "item": "https://wallco.ai/designs" }
-    ].concat(cat ? [{ "@type": "ListItem", "position": 3, "name": cat, "item": `https://wallco.ai/designs?cat=${encodeURIComponent(cat)}` }] : [])
-  };
-
-  const schema = `${JSON.stringify(itemList)}</script>\n<script type="application/ld+json">${JSON.stringify(breadcrumb)}`;
-
-  // Title reflects active filter so browser-tab + search results read true.
-  // Composes parts in priority order: query > hue > motif > cat > room. Caps
-  // at the first non-empty crumb to keep the title scannable (≤60 chars).
-  const _titleCrumbs = [
-    q       && `"${q}"`,
-    hueQ    && `${hueQ.charAt(0).toUpperCase() + hueQ.slice(1)} designs`,
-    motifQ  && `${motifQ.charAt(0).toUpperCase() + motifQ.slice(1)} motif`,
-    cat     && `${cat.charAt(0).toUpperCase() + cat.slice(1)}`,
-    roomQ   && 'In-room previews'
-  ].filter(Boolean);
-  const _titleHead = _titleCrumbs.length
-    ? `${_titleCrumbs[0]} · ${total} design${total===1?'':'s'} — wallco.ai`
-    : `All Designs — wallco.ai | AI-Original Wallpaper & Murals`;
-  // When the user lands on a filtered grid (?hue=rose, ?motif=floral, ?cat=…),
-  // share-link previews on Twitter/LinkedIn/Slack should show a REAL design
-  // from that filter, not the generic og-default.png. Picks the first design
-  // in the filtered slice; unfiltered /designs keeps og-default.
-  const _isFiltered = !!(cat || motifQ || hueQ || roomQ || q);
-  const _ogImage = (_isFiltered && slice[0]?.image_url)
-    ? `https://wallco.ai${slice[0].image_url}`
-    : '';
-  res.type('html').send(`${htmlHead({
-    title: _titleHead,
-    description: `Browse ${total} wallco.ai original wallpaper designs. Every pattern unique, hand-curated, never repeated. Free samples available.`,
-    canonical: `https://wallco.ai/designs`,
-    ogImage: _ogImage,
-    ogImageAlt: _isFiltered && slice[0] ? `${slice[0].title} — sample of ${_titleCrumbs[0] || 'wallco.ai'} designs` : '',
-    preloadImages: slice.slice(0, 6).map(d => d.image_url).filter(Boolean)
-  })}
-<body>
-${htmlHeader('/designs')}
-<main>
-${cat === 'drunk-animals' ? `
-<section style="padding:48px 24px 24px;text-align:center;background:linear-gradient(180deg,#1a0f1a 0%,#2a1530 100%);color:#fff;margin-bottom:0">
-  <div style="max-width:760px;margin:0 auto">
-    <p style="font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:#ff79c6;margin:0 0 8px;font-weight:600">Limited capsule · v2 redo · floral</p>
-    <h1 style="font-family:'Playfair Display',serif;font-weight:400;font-size:clamp(34px,5vw,56px);line-height:1.05;margin:0 0 14px;color:#fff">The Drunk Animals Collection</h1>
-    <p style="font-size:15px;line-height:1.6;color:#e6d4f0;margin:0 auto;max-width:560px">A floral-pop-Warhol fever dream. Tipsy bears with cobalt vodka, hiccup giraffes with emerald lager, sloshed orangutans toasting from rosé coupes — set in fields of wisteria, sunflower, bougainvillea, ghost orchid, bleeding heart, chocolate cosmos. Strictly no leaves, no foliage.</p>
-    <div style="margin-top:18px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;font-size:12px">
-      <span style="padding:4px 10px;border:1px solid #ff79c6;border-radius:14px;color:#ff79c6"><strong>${total}</strong> designs &middot; one every 4 min</span>
-      <span style="padding:4px 10px;border:1px solid #50fa7b;border-radius:14px;color:#50fa7b">live until 06:00 PT</span>
-    </div>
-  </div>
-</section>
-` : ''}
-${cat === 'stoned-animals' ? `
-<section style="padding:48px 24px 24px;text-align:center;background:linear-gradient(180deg,#0a1810 0%,#1a2a14 60%,#3a1830 100%);color:#fff;margin-bottom:0">
-  <div style="max-width:760px;margin:0 auto">
-    <p style="font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:#ffeb3b;margin:0 0 8px;font-weight:600">Limited capsule · 4·20 Edition</p>
-    <h1 style="font-family:'Playfair Display',serif;font-weight:400;font-size:clamp(34px,5vw,56px);line-height:1.05;margin:0 0 14px;color:#fff">The Stoned Animals Collection</h1>
-    <p style="font-size:15px;line-height:1.6;color:#dfe4d0;margin:0 auto;max-width:560px">Grateful Dead meets Warhol in a woodland fog. Bloodshot bears, hazy wolves, glassy-eyed owls reclining on mushroom caps with bongs, gummies, and joints. Strictly NO palms — ferns, wildflowers, herbs, mosses only.</p>
-    <div style="margin-top:18px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;font-size:12px">
-      <span style="padding:4px 10px;border:1px solid #ffeb3b;border-radius:14px;color:#ffeb3b"><strong>${total}</strong> designs &middot; one every 4 min</span>
-      <span style="padding:4px 10px;border:1px solid #ff7c4d;border-radius:14px;color:#ff7c4d">live until 06:00 PT</span>
-    </div>
-  </div>
-</section>
-` : ''}
-${cat === 'mural-animals' ? `
-<section style="padding:56px 24px 32px;text-align:center;background:linear-gradient(180deg,#f4ece0 0%,#e8d8c0 100%);color:#3a2818;margin-bottom:0;border-bottom:1px solid #c8b89a">
-  <div style="max-width:760px;margin:0 auto">
-    <p style="font-size:11px;letter-spacing:.32em;text-transform:uppercase;color:#8a6f44;margin:0 0 10px;font-weight:500">Hand-painted · Five-color · Atelier edition</p>
-    <h1 style="font-family:'Playfair Display','Didot',serif;font-weight:400;font-style:italic;font-size:clamp(36px,5vw,58px);line-height:1.05;margin:0 0 16px;color:#2a1f10">The Mural Menagerie</h1>
-    <p style="font-size:15px;line-height:1.7;color:#5a4632;margin:0 auto;max-width:580px">Refined chinoiserie-tradition wallpaper murals — peacocks amongst peonies, sika deer in misted clearings, mandarin ducks on still water. Watercolour and gouache on antiqued grounds, five-color palettes, no cartoon, no neon. In the manner of the great hand-painted mural houses.</p>
-    <div style="margin-top:20px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;font-size:12px">
-      <span style="padding:4px 12px;border:1px solid #8a6f44;border-radius:14px;color:#8a6f44"><strong>${total}</strong> hand-painted panels</span>
-    </div>
-  </div>
-</section>
-` : ''}
-${cat === 'mural-scenic' ? `
-<section style="padding:56px 24px 32px;text-align:center;background:linear-gradient(180deg,#ece4d4 0%,#dfd1ba 50%,#c5b298 100%);color:#2a1f10;margin-bottom:0;border-bottom:1px solid #b59a72">
-  <div style="max-width:780px;margin:0 auto">
-    <p style="font-size:11px;letter-spacing:.32em;text-transform:uppercase;color:#7a5e34;margin:0 0 10px;font-weight:500">Bespoke scenic mural · 20ft × 11ft · 150 dpi · five-color</p>
-    <h1 style="font-family:'Playfair Display','Didot',serif;font-weight:400;font-style:italic;font-size:clamp(36px,5vw,60px);line-height:1.05;margin:0 0 16px;color:#1f1808">The Scenic Panoramas</h1>
-    <p style="font-size:15px;line-height:1.7;color:#4a3520;margin:0 auto;max-width:600px">Full-room wall murals in the tradition of de Gournay, Iksel, Gracie, and Zuber et Cie. Single uninterrupted scenes — French formal gardens, Chinese chinoiserie valleys, English wildflower hills, Provence lavender vistas — animals as small naturalistic accents. 20 ft wide × 11 ft tall, target print 150 dpi after upscaling.</p>
-    <div style="margin-top:20px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;font-size:12px">
-      <span style="padding:4px 12px;border:1px solid #7a5e34;border-radius:14px;color:#7a5e34"><strong>${total}</strong> panoramic panels</span>
-      <span style="padding:4px 12px;border:1px solid #8a6f44;border-radius:14px;color:#8a6f44">single-panel, no repeat</span>
-    </div>
-  </div>
-</section>
-` : ''}
-
-<!-- ── Source filter chips — toggle between AI Designs and the Designer Library.
-     Per spec ?source=ai|library|all — v1 implements ai vs library as a hard
-     link split (the two surfaces have different sort+filter shapes so a true
-     unified "all" view is deferred). "all" defaults to AI for now. TODO(steve):
-     merge LIBRARY.rows into DESIGNS for a true unified source=all listing. -->
-<div class="source-chip-row" style="max-width:1400px;margin:0 auto;padding:20px 24px 0;display:flex;gap:8px;align-items:center;flex-wrap:wrap" data-source="${(req.query.source === 'all') ? 'all' : 'ai'}">
-  <span style="font:11px var(--sans,system-ui);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint,#8a7a5e);margin-right:6px">Source</span>
-  <a href="/designs" class="src-chip" style="padding:6px 14px;border-radius:999px;border:1px solid ${(req.query.source !== 'all') ? 'var(--accent,#1a1a1a)' : 'var(--line,#d8d0c0)'};background:${(req.query.source !== 'all') ? 'var(--accent,#1a1a1a)' : 'transparent'};color:${(req.query.source !== 'all') ? 'var(--bg,#fff)' : 'var(--ink,#3a2818)'};font:11px var(--sans);letter-spacing:.04em;text-decoration:none">✦ AI Designs</a>
-  ${isAdmin(req) ? `<a href="/library" class="src-chip" style="padding:6px 14px;border-radius:999px;border:1px solid var(--line,#d8d0c0);background:transparent;color:var(--ink,#3a2818);font:11px var(--sans);letter-spacing:.04em;text-decoration:none">★ Designer Library</a>
-  <a href="/designs?source=all" class="src-chip" style="padding:6px 14px;border-radius:999px;border:1px solid ${(req.query.source === 'all') ? 'var(--accent,#1a1a1a)' : 'var(--line,#d8d0c0)'};background:${(req.query.source === 'all') ? 'var(--accent,#1a1a1a)' : 'transparent'};color:${(req.query.source === 'all') ? 'var(--bg,#fff)' : 'var(--ink,#3a2818)'};font:11px var(--sans);letter-spacing:.04em;text-decoration:none">★ All (Combined)</a>` : ''}
-  <span style="font:11px var(--sans);color:var(--ink-faint,#8a7a5e);margin-left:auto">${isAdmin(req) ? ((req.query.source === 'all') ? 'AI + designer library' : 'AI-generated patterns') : 'AI-generated patterns'}</span>
-</div>
-${(req.query.source === 'all') ? `
-<script>
-(function(){
-  // ?source=all — fetch up to 24 designer-library cards and append to the AI
-  // grid. Each card carries a "★ Designer" badge so the source is obvious.
-  fetch('/api/library?limit=24&sort=newest').then(function(r){return r.json();}).then(function(j){
-    if (!j || !j.ok || !j.rows || j.rows.length === 0) return;
-    var grid = document.querySelector('.design-grid');
-    if (!grid) return;
-    var frag = document.createDocumentFragment();
-    j.rows.forEach(function(r){
-      var d = document.createElement('div');
-      d.className = 'design-card library-card';
-      d.dataset.libId = r.id;
-      d.dataset.source = 'library';
-      var safeTitle = String(r.title||'').replace(/[<>"]/g,'');
-      var safeSku = String(r.sku||'').replace(/[<>"]/g,'');
-      d.innerHTML =
-        '<a href="/library/'+r.id+'" class="card-link" style="display:block;color:inherit;text-decoration:none">' +
-          '<div class="card-img" style="background-image:url(\\''+r.image_url+'\\');background-size:cover;background-position:center 12%;aspect-ratio:1/1"></div>' +
-          '<div class="card-meta" style="padding:10px 12px 12px;display:flex;flex-direction:column;gap:4px;font:12px var(--sans,system-ui)">' +
-            '<span class="card-title" style="font:500 13px var(--sans);color:var(--ink,#1f1808);line-height:1.2">'+safeTitle+'</span>' +
-            '<span class="card-sku" style="font:11px var(--sans);color:var(--ink-faint,#8a7a5e);letter-spacing:.04em">Designer Wallcoverings · '+safeSku+'</span>' +
-            '<span class="card-tags" style="display:flex;gap:5px;margin-top:2px">' +
-              '<span style="font:10px var(--sans);padding:2px 6px;background:#fef9eb;border:1px solid #d4b46a;border-radius:3px;color:#7a5e2a">★ Designer</span>' +
-              '<span style="font:10px var(--sans);padding:2px 6px;background:var(--bg,#fff);border:1px solid var(--line,#e0d9cd);border-radius:3px;color:#7a6a52">'+String(r.style_bucket||'Mixed').replace(/[<>"]/g,'')+'</span>' +
-            '</span>' +
-          '</div>' +
-        '</a>';
-      frag.appendChild(d);
-    });
-    grid.appendChild(frag);
-  }).catch(function(){});
-})();
-</script>` : ''}
-
-<section class="catalog-section">
-  <div class="catalog-toolbar">
-    <form method="get" action="/designs" class="filter-form">
-      <input type="hidden" name="sort" id="sort-hidden" value="${sort}">
-      <div class="toolbar-row">
-        <div class="search-wrap" style="position:relative;flex:1;min-width:200px">
-          <input type="search" name="q" value="${q}" placeholder="Search designs…" class="search-input" id="catalog-search-input" autocomplete="off" aria-autocomplete="list" aria-controls="catalog-search-suggest" aria-expanded="false" role="combobox" style="width:100%;padding-right:36px">
-          ${q
-            ? `<a href="/designs${(function(){
-                const parts = [
-                  sort && sort !== 'newest' ? 'sort=' + encodeURIComponent(sort) : '',
-                  cat    ? 'cat='   + encodeURIComponent(cat)    : '',
-                  motifQ ? 'motif=' + encodeURIComponent(motifQ) : '',
-                  hueQ   ? 'hue='   + encodeURIComponent(hueQ)   : ''
-                ].filter(Boolean);
-                return parts.length ? '?' + parts.join('&') : '';
-              })()}"
-                id="catalog-search-clear" aria-label="Clear search query"
-                title="Clear search"
-                style="position:absolute;right:8px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:var(--ink-faint,#999);color:var(--bg,#fff);text-decoration:none;font:600 13px/1 var(--sans)">✕</a>`
-            : `<kbd id="catalog-search-kbd" aria-hidden="true" title="Press / to focus search" style="position:absolute;right:8px;top:50%;transform:translateY(-50%);padding:2px 7px;font:600 11px/1 ui-monospace,Menlo,monospace;color:var(--ink-faint,#999);background:var(--card-bg,#faf8f3);border:1px solid var(--line,#e0d9cd);border-radius:4px;pointer-events:none;transition:opacity .12s ease">/</kbd>`
-          }
-          <div id="catalog-search-suggest" role="listbox" aria-label="Search suggestions" style="display:none;position:absolute;left:0;right:0;top:100%;margin-top:4px;background:var(--bg,#fff);border:1px solid var(--line,#ddd);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.12);z-index:50;max-height:60vh;overflow-y:auto"></div>
-        </div>
-        <style>
-          @media (hover: none) { #catalog-search-kbd { display:none } }
-          .search-input:focus ~ #catalog-search-kbd { opacity:0 }
-        </style>
-        <select name="cat" class="filter-select" onchange="this.form.submit()">
-          <option value="">All Categories</option>
-          ${cats.map(c => `<option value="${c}" ${c===cat?'selected':''}>${c.charAt(0).toUpperCase()+c.slice(1)}</option>`).join('')}
-        </select>
-        <div class="sort-btn-row" style="display:flex;gap:4px;flex-wrap:wrap;align-items:center" role="radiogroup" aria-label="Sort designs">
-          <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint);margin-right:4px">Sort</span>
-          ${[
-            ['newest',      'Newest'],
-            ['title',       'Title A→Z'],
-            ['category',    'Category'],
-            ['color-wheel', 'Color Wheel'],
-            ['random',      'Random'],
-          ].map(([k, label]) => {
-            const on = sort === k;
-            return `<button type="button" class="sort-btn${on?' is-on':''}" data-sort="${k}" role="radio" aria-checked="${on}"
-              onclick="document.getElementById('sort-hidden').value='${k}';this.form.submit()"
-              style="padding:5px 11px;border:1px solid ${on?'var(--accent,#1a1a1a)':'var(--line,#d8d0c0)'};background:${on?'var(--accent,#1a1a1a)':'transparent'};color:${on?'var(--bg,#fff)':'var(--ink,#3a2818)'};border-radius:999px;font:11px var(--sans,system-ui);letter-spacing:.04em;cursor:pointer;white-space:nowrap">${label}</button>`;
-          }).join('')}
-          <!-- Full "More sorts" dropdown — every typical sort, available to all users (not admin-gated). -->
-          <select aria-label="More sort options" onchange="if(this.value){document.getElementById('sort-hidden').value=this.value;this.form.submit()}"
-            style="padding:5px 11px;border:1px solid var(--line,#d8d0c0);background:transparent;color:var(--ink,#3a2818);border-radius:999px;font:11px var(--sans,system-ui);letter-spacing:.04em;cursor:pointer;margin-left:2px">
-            <option value="">More sorts…</option>
-            <optgroup label="Date">
-              <option value="newest"     ${sort==='newest'?'selected':''}>Newest first</option>
-              <option value="oldest"     ${sort==='oldest'?'selected':''}>Oldest first</option>
-            </optgroup>
-            <optgroup label="Alphabetical">
-              <option value="title"        ${sort==='title'?'selected':''}>Title A → Z</option>
-              <option value="title-desc"   ${sort==='title-desc'?'selected':''}>Title Z → A</option>
-              <option value="category"     ${sort==='category'?'selected':''}>Category A → Z</option>
-              <option value="category-desc"${sort==='category-desc'?'selected':''}>Category Z → A</option>
-            </optgroup>
-            <optgroup label="Color">
-              <option value="light-dark"  ${sort==='light-dark'?'selected':''}>Light → Dark</option>
-              <option value="dark-light"  ${sort==='dark-light'?'selected':''}>Dark → Light</option>
-              <option value="color-wheel" ${sort==='color-wheel'?'selected':''}>Color Wheel (warm → cool)</option>
-              <option value="vivid"       ${sort==='vivid'?'selected':''}>Most Vivid</option>
-              <option value="muted"       ${sort==='muted'?'selected':''}>Most Muted</option>
-            </optgroup>
-            <optgroup label="Other">
-              <option value="has-room"    ${sort==='has-room'?'selected':''}>In-Room previews first</option>
-              <option value="random"      ${sort==='random'?'selected':''}>Random shuffle</option>
-            </optgroup>
-          </select>
-        </div>
-        <label class="density-label" style="display:inline-flex;align-items:center;gap:8px;font:11px var(--sans);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint)">Density
-          <input type="range" id="density-slider" min="1" max="12" step="1" value="6" class="density-range" style="flex:0 0 140px" aria-valuemin="1" aria-valuemax="12" aria-valuenow="6">
-          <span id="density-value" aria-live="polite" style="font:600 11px var(--sans);color:var(--ink-soft);min-width:18px;text-align:right;font-variant-numeric:tabular-nums">6</span>
-        </label>${_isAdmin ? `
-        <label class="spacing-label" style="display:inline-flex;align-items:center;gap:8px;font:11px var(--sans);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint)" title="Admin-only: visual gap between design cards">Spacing
-          <input type="range" id="spacing-slider" min="0" max="40" step="2" value="2" class="density-range" style="flex:0 0 140px" aria-valuemin="0" aria-valuemax="40" aria-valuenow="2">
-          <span id="spacing-value" aria-live="polite" style="font:600 11px var(--sans);color:var(--ink-soft);min-width:22px;text-align:right;font-variant-numeric:tabular-nums">2px</span>
-        </label>` : ''}
-        <button type="button" id="surprise-btn" class="filter-select"
-          style="background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border:0;font-weight:500;padding:8px 14px;cursor:pointer;border-radius:6px;letter-spacing:.04em"
-          title="Generate one fresh design in a random palette · ~30s">✦ Surprise me</button>
-      </div>
-      <input type="hidden" name="motif" id="motif-hidden" value="${motifQ}">
-      <input type="hidden" name="hue" id="hue-hidden" value="${hueQ}">
-    </form>
-
-    <!-- Color-wheel hue swatches -->
-    <div class="hue-swatches" style="display:flex;flex-wrap:wrap;gap:6px;margin:18px 0 4px;align-items:center">
-      <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-right:6px">Hue</span>
-      <a href="/designs${cat ? '?cat='+encodeURIComponent(cat) : ''}${motifQ ? (cat?'&':'?')+'motif='+encodeURIComponent(motifQ) : ''}" class="hue-chip${!hueQ?' is-on':''}" style="font:11px var(--sans);padding:5px 11px;border:1px solid ${!hueQ?'var(--accent)':'var(--line)'};background:${!hueQ?'var(--accent)':'transparent'};color:${!hueQ?'var(--bg)':'var(--ink-soft)'};border-radius:999px;text-decoration:none">All</a>
-      ${HUE_DEFS.filter(([k]) => (hueCounts[k]||0) > 0).map(([k,hex]) => {
-        const on = hueQ === k;
-        const qs = [];
-        if (cat) qs.push('cat=' + encodeURIComponent(cat));
-        if (motifQ) qs.push('motif=' + encodeURIComponent(motifQ));
-        qs.push('hue=' + encodeURIComponent(k));
-        const href = '/designs?' + qs.join('&');
-        return `<a href="${href}" class="hue-chip${on?' is-on':''}" title="Filter by ${k} (${hex.toUpperCase()})" style="display:inline-flex;align-items:center;gap:7px;font:11px var(--sans);padding:4px 12px 4px 5px;border:1px solid ${on?'var(--accent)':'var(--line)'};background:${on?'var(--accent)':'transparent'};color:${on?'var(--bg)':'var(--ink-soft)'};border-radius:999px;text-decoration:none;min-height:32px">
-          <span aria-hidden="true" style="display:inline-block;width:20px;height:20px;border-radius:50%;background:${hex};border:1px solid rgba(0,0,0,.18);box-shadow:0 1px 2px rgba(0,0,0,.08)"></span>
-          ${k} <span style="opacity:.55">·${hueCounts[k]||0}</span>
-        </a>`;
-      }).join('')}
-    </div>
-
-    <!-- Motif chips (one-click filter) -->
-    <div class="motif-chips" style="display:flex;flex-wrap:wrap;gap:6px;margin:14px 0 6px;align-items:center">
-      <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-right:6px">Motif</span>
-      <a href="/designs${cat ? '?cat='+encodeURIComponent(cat) : ''}" class="motif-chip${!motifQ?' is-on':''}" style="font:11px var(--sans);padding:5px 12px;border:1px solid ${!motifQ?'var(--accent)':'var(--line)'};background:${!motifQ?'var(--accent)':'transparent'};color:${!motifQ?'var(--bg)':'var(--ink-soft)'};border-radius:999px;text-decoration:none">All</a>
-      ${topMotifs.map(([m,n]) => `<a href="/designs?${cat?'cat='+encodeURIComponent(cat)+'&':''}motif=${encodeURIComponent(m)}" class="motif-chip${motifQ===m?' is-on':''}" style="font:11px var(--sans);padding:5px 12px;border:1px solid ${motifQ===m?'var(--accent)':'var(--line)'};background:${motifQ===m?'var(--accent)':'transparent'};color:${motifQ===m?'var(--bg)':'var(--ink-soft)'};border-radius:999px;text-decoration:none">${m} <span style="opacity:.55">·${n}</span></a>`).join('')}
-    </div>
-
-    <!-- Room-preview filter chip (toggles ?room=1 — shows only designs that have a residential mockup) -->
-    ${(() => {
-      const roomCnt = DESIGNS.filter(d => (d.room_mockups || []).length > 0).length;
-      const on = !!roomQ;
-      const href = `/designs${[
-        cat   ? `cat=${encodeURIComponent(cat)}`     : '',
-        motifQ? `motif=${encodeURIComponent(motifQ)}`: '',
-        hueQ  ? `hue=${encodeURIComponent(hueQ)}`    : '',
-        !on   ? 'room=1'                             : ''
-      ].filter(Boolean).join('&').replace(/^/, on ? '' : '?')}`;
-      return `<div class="motif-chips" style="display:flex;flex-wrap:wrap;gap:6px;margin:0 0 6px;align-items:center">
-        <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-right:6px">Preview</span>
-        <a href="${href}" class="motif-chip${on?' is-on':''}" title="Show only designs with a room mockup preview"
-           style="font:11px var(--sans);padding:5px 12px;border:1px solid ${on?'var(--accent)':'var(--line)'};background:${on?'var(--accent)':'transparent'};color:${on?'var(--bg)':'var(--ink-soft)'};border-radius:999px;text-decoration:none">
-          In a room <span style="opacity:.55">·${roomCnt}</span>
-        </a>
-        <button type="button" id="fav-only-toggle" class="motif-chip" aria-pressed="false"
-          style="font:11px var(--sans);padding:5px 12px;border:1px solid var(--line);background:transparent;color:var(--ink-soft);border-radius:999px;cursor:pointer">
-          ♡ Favorites <span id="fav-only-count" style="opacity:.55">·0</span>
-        </button>
-        ${(cat || motifQ || hueQ || roomQ || q) ? `<a href="/designs" class="motif-chip clear-all-chip" title="Clear every active filter"
-          style="font:11px var(--sans);padding:5px 12px;border:1px solid var(--line);background:transparent;color:var(--ink-soft);border-radius:999px;text-decoration:none;display:inline-flex;align-items:center;gap:5px;margin-left:6px">
-          <span aria-hidden="true" style="font-size:13px;line-height:1">✕</span> Clear filters
-        </a>` : ''}
-      </div>`;
-    })()}
-
-    <style>
-      /* Favorite heart on each card */
-      .design-card { position:relative }
-      .fav-btn { position:absolute;top:10px;left:10px;z-index:5;width:32px;height:32px;border-radius:50%;border:0;background:rgba(20,18,15,.55);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1;transition:transform .12s,background .15s;backdrop-filter:blur(4px) }
-      .fav-btn:hover { background:rgba(20,18,15,.85);transform:scale(1.08) }
-      .fav-btn.is-fav { background:rgba(201,71,71,.92);color:#fff }
-      .fav-btn[aria-pressed="true"] .fav-icon::before { content:"♥" }
-      .fav-btn[aria-pressed="false"] .fav-icon::before { content:"♡" }
-      /* NEW badge — top-right corner, brand-gold pill on dark background.
-         Hidden at rest per Steve's rule: no text on the resting card; surfaces
-         only when the visitor commits attention (hover/focus). */
-      .card-new-badge { position:absolute;top:10px;right:10px;z-index:5;padding:4px 10px;border-radius:999px;background:var(--accent,#c9a14b);color:var(--bg,#fff);font:600 10px/1 var(--sans,system-ui);letter-spacing:.12em;text-transform:uppercase;box-shadow:0 2px 8px rgba(0,0,0,.25);opacity:0;transition:opacity .18s ease;pointer-events:none }
-      .design-card:hover .card-new-badge,
-      .design-card:focus-within .card-new-badge { opacity:1 }
-      /* Resting is-new ring kept (no text) — visual cue at low density. */
-      .design-card.is-new { box-shadow:0 0 0 1px var(--accent,#c9a14b),0 6px 22px rgba(201,161,75,.18) }
-      /* Fav heart also fades at rest so the grid reads as pure pattern. */
-      .fav-btn { opacity:0;transition:opacity .18s ease,transform .12s,background .15s }
-      .design-card:hover .fav-btn,
-      .design-card:focus-within .fav-btn,
-      .fav-btn.is-fav { opacity:1 }
-      /* Touch devices have no :hover — keep heart + NEW always visible there
-         so mobile users can actually save designs and see the NEW pill.
-         (hover: none) targets phones/tablets without an associated mouse. */
-      @media (hover: none) {
-        .fav-btn,
-        .card-new-badge { opacity:1 }
-      }
-      /* Hover layer on every catalog card — pattern number + View / Sample.
-         Sits inside .card-img so it picks up the existing aspect-ratio box.
-         pointer-events:none on the wrapper lets clicks fall through to the
-         parent <a>, except on the explicit child buttons which re-enable
-         pointer events. */
-      .design-card .card-img { position:relative }
-      .card-hover {
-        position:absolute; inset:0; z-index:4; pointer-events:none;
-        background:linear-gradient(to top, rgba(20,18,15,.78) 0%, rgba(20,18,15,.32) 45%, rgba(20,18,15,0) 70%);
-        opacity:0; transition:opacity .18s ease;
-        display:flex; flex-direction:column; justify-content:flex-end;
-        padding:10px 12px 12px;
-        color:#fff;
-      }
-      .design-card:hover .card-hover,
-      .design-card:focus-within .card-hover { opacity:1 }
-      .card-hover-dig {
-        position:absolute; top:10px; left:50%; transform:translateX(-50%);
-        background:rgba(255,255,255,.92); color:#111;
-        font:600 10px/1 ui-monospace,Menlo,monospace; letter-spacing:.06em;
-        padding:5px 9px; border-radius:3px;
-        box-shadow:0 2px 6px rgba(0,0,0,.18);
-        white-space:nowrap;
-      }
-      .card-hover-actions { display:flex; gap:8px; justify-content:center }
-      .chov-btn {
-        pointer-events:auto;
-        flex:1; max-width:120px; text-align:center;
-        padding:7px 10px; border-radius:3px;
-        font:600 11px/1 var(--sans,system-ui); letter-spacing:.08em; text-transform:uppercase;
-        text-decoration:none; transition:transform .12s, background .15s;
-      }
-      .chov-view   { background:#fff; color:#111 }
-      .chov-sample { background:rgba(255,255,255,.18); color:#fff; border:1px solid rgba(255,255,255,.55) }
-      .chov-btn:hover { transform:translateY(-1px) }
-      .chov-sample:hover { background:rgba(255,255,255,.32) }
-    </style>
-
-    <p class="result-count">${total} design${total===1?'':'s'}${cat?' in '+cat:''}${motifQ?' with '+motifQ:''}${roomQ?' with room preview':''}${q?' matching "'+q+'"':''}<span id="fav-count-extra" style="margin-left:8px;color:var(--ink-faint)"></span></p>
-
-    <!-- One-time hint pointing trade users to the /brief proposal builder. Dismissible; -->
-    <!-- once dismissed it stays hidden via localStorage('wallco.hint.brief.dismissed').    -->
-    <div id="brief-hint" style="display:none;margin:6px 0 14px;padding:9px 14px;border:1px dashed var(--line,#ddd);border-radius:6px;background:var(--card-bg,#faf8f3);font:12px/1.5 var(--sans,system-ui);color:var(--ink-soft,#555);display:flex;align-items:center;gap:10px;flex-wrap:wrap">
-      <span aria-hidden="true">📄</span>
-      <span>Building a proposal? Pick up to 6 designs and assemble a single client-ready brief.</span>
-      <a href="/brief" style="color:var(--ink,#111);font-weight:500;text-decoration:underline;text-underline-offset:2px">Open /brief →</a>
-      <a id="brief-hint-dismiss" href="javascript:void(0)" style="margin-left:auto;color:var(--ink-faint,#999);text-decoration:underline;font-size:11px">Hide</a>
-    </div>
-    <script>
-    (function(){
-      var el = document.getElementById('brief-hint');
-      if (!el) return;
-      try { if (localStorage.getItem('wallco.hint.brief.dismissed') === '1') return; } catch(e){}
-      el.style.display = 'flex';
-      var btn = document.getElementById('brief-hint-dismiss');
-      if (btn) btn.addEventListener('click', function(){
-        el.style.display = 'none';
-        try { localStorage.setItem('wallco.hint.brief.dismissed', '1'); } catch(e){}
-      });
-    })();
-    </script>
-
-    <div id="surprise-result" style="display:none;margin-bottom:18px;padding:16px;border:1px solid var(--gold);border-radius:8px;background:var(--card-bg)"></div>
-    <script>
-    (function(){
-      var btn = document.getElementById('surprise-btn');
-      if (!btn) return;
-      var palettes = [
-        ['amber and emerald','damask'],
-        ['blush and indigo','floral'],
-        ['oyster and bone','damask'],
-        ['terracotta and sage','mixed'],
-        ['plum and gold','geometric'],
-        ['marine and coral','floral'],
-        ['charcoal and brass','geometric'],
-        ['ivory and persimmon','floral'],
-        ['sapphire and pearl','damask'],
-        ['olive and rose','floral']
-      ];
-      var motifsCue = ['scrollwork','peony','lattice','vine','arabesque','asanoha','fan','blossom','toile','trellis'];
-      btn.addEventListener('click', async function(){
-        var pick = palettes[Math.floor(Math.random()*palettes.length)];
-        var motif = motifsCue[Math.floor(Math.random()*motifsCue.length)];
-        var msg = 'make me a new wallpaper in ' + pick[0] + ' with a ' + motif + ' motif — surprising me';
-        var box = document.getElementById('surprise-result');
-        box.style.display = 'block';
-        box.innerHTML = '<div style="font:13px/1.5 -apple-system,sans-serif;color:var(--ink-soft)">Generating <em>' + pick[0] + ' · ' + motif + '</em>… (~30s)</div>';
-        btn.disabled = true; var orig = btn.textContent; btn.textContent = '… generating';
-        try {
-          var r = await fetch('/api/chat/catalog', {
-            method: 'POST', headers: {'Content-Type': 'application/json'},
-            body: JSON.stringify({ message: msg })
-          });
-          var j = await r.json();
-          var d = (j.actions || []).find(function(a){ return a.type === 'new_design'; });
-          if (d && d.design) {
-            var did = d.design.id;
-            var idValid = Number.isFinite(did);
-            var idLabel = idValid ? '#' + did : '(unsaved)';
-            var nonce = Math.random().toString(36).slice(2, 9);
-            var imgUrl = d.design.image_url;
-            var hex = d.design.dominant_hex || '#888';
-            var promptText = String(d.design.prompt || '').replace(/[<>]/g, '');
-            box.innerHTML =
-              '<div style="display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:18px;align-items:start">' +
-                '<div>' +
-                  '<div style="aspect-ratio:1;background:url(\\''+imgUrl+'\\') center/cover #f2efe8;border-radius:10px;border:1px solid var(--line);max-width:520px"></div>' +
-                  '<div style="margin-top:10px;font:13px/1.5 var(--sans);color:var(--ink-soft)">' +
-                    '<span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:'+hex+';vertical-align:middle;margin-right:6px;border:1px solid rgba(0,0,0,.12)"></span>' +
-                    '<b style="color:var(--ink)">Design '+idLabel+'</b> · '+pick[0]+' · '+motif+' · '+hex +
-                  '</div>' +
-                '</div>' +
-                '<div style="display:flex;flex-direction:column;gap:14px;min-width:0">' +
-                  (promptText ? '<details><summary style="cursor:pointer;font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">Prompt ▾</summary><div style="margin-top:8px;font:11px/1.5 ui-monospace,Menlo,monospace;color:var(--ink-soft);max-height:180px;overflow-y:auto;padding:8px;background:var(--card-bg);border:1px solid var(--line);border-radius:6px">'+promptText+'</div></details>' : '') +
-                  (idValid
-                    ? '<div id="home-review-'+did+'" style="padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--card-bg)"><div style="font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">Interior-designer review · #'+did+'</div><div style="margin-top:8px;font:13px/1.5 var(--sans);color:var(--ink-soft)"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--gold);animation:rev-pulse 1.2s ease-in-out infinite;margin-right:6px;vertical-align:middle"></span>Running…</div></div>'
-                    : '<div style="padding:12px;border:1px dashed var(--line);border-radius:8px;background:#fff8e8;font:12px/1.5 var(--sans);color:#7a5f10">Image generated, but the row didn\\'t persist (DB schema drift on prod). Image is available to download — review unavailable until persisted.</div>') +
-                  '<div style="display:flex;flex-direction:column;gap:6px;margin-top:auto">' +
-                    (idValid ? '<a href="/design/'+did+'" style="display:block;text-align:center;padding:8px 14px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);text-decoration:none;font-size:13px;font-weight:500;border-radius:6px">Open full page →</a>' : '') +
-                    '<a href="'+imgUrl+'" download style="display:block;text-align:center;padding:8px 14px;border:1px solid var(--ink);color:var(--ink);text-decoration:none;font-size:13px;border-radius:6px">Download pattern</a>' +
-                    '<button id="home-retry-'+nonce+'" type="button" style="padding:8px 14px;background:transparent;color:var(--ink);border:1px solid var(--line);border-radius:6px;cursor:pointer;font:13px var(--sans)" data-prompt="'+promptText.replace(/"/g,'&quot;')+'">Try this prompt again</button>' +
-                    '<button id="home-again-'+nonce+'" type="button" style="padding:8px 14px;background:var(--ink);color:var(--bg);border:0;border-radius:6px;cursor:pointer;font:13px var(--sans)">Surprise me again</button>' +
-                  '</div>' +
-                '</div>' +
-              '</div>' +
-              '<style>@keyframes rev-pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.6)} }</style>';
-            var againBtn = document.getElementById('home-again-'+nonce);
-            if (againBtn) againBtn.addEventListener('click', function(){ btn.click(); });
-            var retryBtn = document.getElementById('home-retry-'+nonce);
-            if (retryBtn) retryBtn.addEventListener('click', async function(){
-              var p = retryBtn.getAttribute('data-prompt') || promptText;
-              if (!p) return;
-              box.innerHTML = '<div style="font:13px/1.5 -apple-system,sans-serif;color:var(--ink-soft)">Re-rendering same prompt… (~30s)</div>';
-              btn.disabled = true; btn.textContent = '… generating';
-              try {
-                var rr = await fetch('/api/chat/catalog', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({message: p}) });
-                var jj = await rr.json();
-                var dd = (jj.actions||[]).find(function(a){return a.type==='new_design';});
-                if (dd && dd.design) {
-                  var ddid = dd.design.id;
-                  var ddidValid = Number.isFinite(ddid);
-                  var u = dd.design.image_url, h = dd.design.dominant_hex || '#888';
-                  box.innerHTML = '<div style="display:flex;gap:18px;align-items:start;flex-wrap:wrap"><div style="aspect-ratio:1;width:520px;max-width:100%;background:url(\\''+u+'\\') center/cover #f2efe8;border-radius:10px;border:1px solid var(--line)"></div><div style="font:13px var(--sans);color:var(--ink-soft)"><span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:'+h+';margin-right:6px;border:1px solid rgba(0,0,0,.12);vertical-align:middle"></span>Same prompt, fresh render '+(ddidValid?'· #'+ddid:'(unsaved)')+'<br><br>'+(ddidValid?'<a href="/design/'+ddid+'" style="color:var(--gold)">Open full page →</a><br>':'')+'<a href="'+u+'" download style="color:var(--ink)">Download pattern</a></div></div>';
-                } else {
-                  box.innerHTML = '<div style="color:#b03a2e">Re-render failed: ' + (jj.error || 'unknown').slice(0,300) + '</div>';
-                }
-              } catch (e) { box.innerHTML = '<div style="color:#b03a2e">Error: ' + e.message + '</div>'; }
-              btn.disabled = false; btn.textContent = orig;
-            });
-            if (idValid) fetch('/api/studio/interior-design-review', {
-              method: 'POST', headers: {'Content-Type':'application/json'},
-              body: JSON.stringify({ design_id: did })
-            }).then(function(r){ return r.json(); }).then(function(rj){
-              var revBox = document.getElementById('home-review-'+did);
-              if (!revBox) return;
-              var esc = function(s){ return String(s||'').replace(/[<>]/g,''); };
-              if (rj.ok && rj.critique) {
-                var c = rj.critique;
-                revBox.innerHTML =
-                  '<div style="font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px">Interior-designer review · #'+did+'</div>' +
-                  (Array.isArray(c.strengths) && c.strengths.length ? '<div style="margin-bottom:10px"><div style="font:11px/1.4 var(--sans);font-weight:700;color:var(--ink);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">Strengths</div><ul style="margin:0;padding-left:18px;font:13px/1.5 var(--sans);color:var(--ink-soft)">'+ c.strengths.map(function(s){return '<li>'+esc(s)+'</li>';}).join('') +'</ul></div>' : '') +
-                  (Array.isArray(c.concerns) && c.concerns.length ? '<div style="margin-bottom:10px"><div style="font:11px/1.4 var(--sans);font-weight:700;color:var(--ink);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">Concerns</div><ul style="margin:0;padding-left:18px;font:13px/1.5 var(--sans);color:var(--ink-soft)">'+ c.concerns.map(function(s){return '<li>'+esc(s)+'</li>';}).join('') +'</ul></div>' : '') +
-                  (c.recommendation ? '<div style="margin-bottom:10px;font:13px/1.5 var(--sans);color:var(--ink)"><b>Recommendation:</b> '+esc(c.recommendation)+'</div>' : '') +
-                  '<div style="font:11px/1.5 var(--sans);color:var(--ink-faint);margin-top:8px;padding-top:8px;border-top:1px solid var(--line)">' +
-                    (Array.isArray(c.room_fit) && c.room_fit.length ? '<b>Rooms:</b> '+esc(c.room_fit.join(', '))+' · ' : '') +
-                    (c.lighting ? '<b>Lighting:</b> '+esc(c.lighting)+' · ' : '') +
-                    (c.scale ? '<b>Scale:</b> '+esc(c.scale) : '') +
-                  '</div>';
-              } else {
-                revBox.innerHTML = '<div style="font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px">Interior-designer review · #'+did+'</div><div style="font:13px/1.5 var(--sans);color:#b03a2e">Review failed: '+(rj.error||'unknown').replace(/[<>]/g,'')+'</div>';
-              }
-            }).catch(function(e){
-              var revBox = document.getElementById('home-review-'+did);
-              if (revBox) revBox.innerHTML = '<div style="font:11px/1 var(--sans);color:var(--ink-faint);margin-bottom:6px">Interior-designer review</div><div style="color:#b03a2e;font-size:13px">'+e.message+'</div>';
-            });
-          } else {
-            box.innerHTML = '<div style="color:#b03a2e">Generation failed: ' + (j.error || JSON.stringify(j).slice(0,200)) + '</div>';
-          }
-        } catch (e) {
-          box.innerHTML = '<div style="color:#b03a2e">Error: ' + e.message + '</div>';
-        }
-        btn.disabled = false; btn.textContent = orig;
-      });
-    })();
-    </script>
-    ${_isAdmin ? `<div class="export-links">
-      <a href="/moodboard">Moodboard →</a>
-      <span style="color:#444">·</span>
-      <span>Export:</span>
-      <a href="/api/export/keep.csv" download>Kept (CSV)</a>
-      <a href="/api/export/all.csv" download>All reviewed (CSV)</a>
-    </div>` : ''}
-  </div>
-
-  <div class="design-grid catalog-grid" id="catalog-grid" data-current-page="${page}" data-total-pages="${pages}" data-total="${total}">
-    ${cards || `<div class="empty-state" style="grid-column:1/-1;padding:80px 24px;text-align:center;color:var(--ink-soft,#555);font:14px/1.6 var(--sans,system-ui)">
-      <div style="font-size:42px;line-height:1;margin-bottom:14px;opacity:.55" aria-hidden="true">⌕</div>
-      <p style="margin:0 0 6px;font:300 22px/1.3 var(--serif,'Cormorant Garamond',serif);color:var(--ink,#111)">No designs match these filters</p>
-      <p style="margin:0 0 22px;color:var(--ink-faint,#999);max-width:380px;margin-left:auto;margin-right:auto">Try removing one filter, or browse the full catalog of ${DESIGNS.length.toLocaleString()} designs.</p>
-      <div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
-        <a href="/designs" style="padding:9px 18px;font:13px var(--sans);background:var(--ink,#111);color:var(--bg,#fff);text-decoration:none;border-radius:6px">Clear filters</a>
-        <a href="/designs?sort=random" style="padding:9px 18px;font:13px var(--sans);background:transparent;color:var(--ink,#111);text-decoration:none;border:1px solid var(--line,#d8d0c0);border-radius:6px">✦ Surprise me</a>
-      </div>
-    </div>`}
-  </div>
-
-  ${pages > 1 ? `
-  <div id="infinite-sentinel" style="height:1px;width:100%"></div>
-  <div id="infinite-status" style="text-align:center;padding:24px 0;font:11px/1 var(--sans,system-ui);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint,#888)">${page < pages ? `loading more · page ${page} of ${pages}` : `all ${total} designs loaded`}</div>
-  <noscript><div class="pager">${pagerLinks}</div></noscript>
-  ` : ''}
-
-  <!-- Floating scroll-to-top button — appears after the user has scrolled past two viewport heights. -->
-  <button type="button" id="scroll-top-btn" aria-label="Back to top of catalog" title="Back to top"
-    style="position:fixed;right:18px;bottom:18px;z-index:80;width:42px;height:42px;border-radius:50%;border:1px solid var(--line,#e0d9cd);background:var(--bg,#fff);color:var(--ink,#111);box-shadow:0 4px 12px rgba(0,0,0,.12);cursor:pointer;display:none;align-items:center;justify-content:center;font:600 18px/1 var(--sans,system-ui);transition:opacity .18s ease,transform .12s ease">↑</button>
-  <script>
-  (function(){
-    var btn = document.getElementById('scroll-top-btn');
-    if (!btn) return;
-    var threshold = window.innerHeight * 2;
-    function onScroll(){
-      var show = window.scrollY > threshold;
-      btn.style.display = show ? 'inline-flex' : 'none';
-    }
-    window.addEventListener('scroll', onScroll, { passive: true });
-    window.addEventListener('resize', function(){ threshold = window.innerHeight * 2; onScroll(); });
-    btn.addEventListener('click', function(){
-      // Respect prefers-reduced-motion: instant jump for those users.
-      var rm = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
-      window.scrollTo({ top: 0, behavior: rm ? 'auto' : 'smooth' });
-      // Also focus the search input on return so /-shortcut feels natural after.
-      var s = document.getElementById('catalog-search-input');
-      if (s) setTimeout(function(){ s.focus(); }, rm ? 0 : 320);
-    });
-    onScroll();
-  })();
-  </script>
-
-  <!-- Prefetch /design/:id on card hover so the click feels instant.
-       Debounced 80ms — quick mouse-moves across the grid don't fire dozens.
-       Honors save-data + 2g connections + prefers-reduced-data (skip prefetch
-       on metered/slow networks). -->
-  <script>
-  (function(){
-    var conn = navigator.connection || {};
-    if (conn.saveData || /(^|\b)(slow-)?2g\b/.test(conn.effectiveType || '')) return;
-    var grid = document.getElementById('catalog-grid');
-    if (!grid) return;
-    var done = new Set();
-    var t = null;
-    function prefetch(href){
-      if (done.has(href)) return;
-      done.add(href);
-      var link = document.createElement('link');
-      link.rel = 'prefetch';
-      link.href = href;
-      link.as = 'document';
-      document.head.appendChild(link);
-    }
-    grid.addEventListener('mouseover', function(e){
-      var a = e.target.closest && e.target.closest('a.card-link');
-      if (!a || !a.href) return;
-      clearTimeout(t);
-      t = setTimeout(function(){ prefetch(a.href); }, 80);
-    }, { passive: true });
-    grid.addEventListener('mouseout', function(){ clearTimeout(t); }, { passive: true });
-  })();
-  </script>
-</section>
-</main>
-${_isAdminAuth ? `
-<!-- Admin view-mode toggle — only rendered when authenticated as admin. -->
-<style>
-  .view-toggle{position:fixed;right:14px;bottom:14px;z-index:9999;display:flex;gap:6px;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(20,18,16,0.88);color:#f2eadb;font:11px/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;box-shadow:0 4px 14px rgba(0,0,0,.35);backdrop-filter:blur(6px);user-select:none}
-  .view-toggle button{appearance:none;border:1px solid rgba(242,234,219,0.18);background:transparent;color:inherit;padding:5px 10px;border-radius:999px;font:inherit;cursor:pointer;transition:all .12s ease}
-  .view-toggle button.is-active{background:#f2eadb;color:#141210;border-color:#f2eadb}
-  .view-toggle button:hover:not(.is-active){background:rgba(242,234,219,0.12)}
-  .view-toggle-label{opacity:.55;margin-right:4px}
-  body.view-as-live .admin-only{display:none !important}
-</style>
-<div class="view-toggle" id="viewToggle" role="group" aria-label="View mode (admin only)">
-  <span class="view-toggle-label">View:</span>
-  <button type="button" data-mode="admin" id="vmAdmin">Admin</button>
-  <button type="button" data-mode="live"  id="vmLive">Live</button>
-</div>
-<script>
-(function(){
-  // Persisted view-mode toggle for admins.
-  // - "admin"  → server renders the full admin chrome (sliders/K/R/chips)
-  // - "live"   → server renders the public customer view, even though
-  //              admin auth cookie/token is still valid
-  // Mechanism: set cookie view_mode=admin|live then reload. Server reads it
-  // and forces _isAdmin=false when view_mode=live.
-  function setCookie(name, val, days){
-    var d = new Date(); d.setTime(d.getTime() + (days*86400000));
-    document.cookie = name + '=' + val + ';path=/;expires=' + d.toUTCString() + ';samesite=lax';
-  }
-  function readCookie(name){
-    var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]+)'));
-    return m ? decodeURIComponent(m[1]) : '';
-  }
-  function getMode(){
-    var url = new URL(window.location.href);
-    if (url.searchParams.get('view') === 'live') return 'live';
-    return readCookie('view_mode') === 'live' ? 'live' : 'admin';
-  }
-  function setActiveButton(mode){
-    var a = document.getElementById('vmAdmin');
-    var l = document.getElementById('vmLive');
-    if (!a || !l) return;
-    a.classList.toggle('is-active', mode === 'admin');
-    l.classList.toggle('is-active', mode === 'live');
-  }
-  function applyMode(mode){
-    var prev = getMode();
-    if (mode === prev) return;
-    setCookie('view_mode', mode, 365);
-    // Strip any ?view= from URL so it doesn't conflict with the cookie state.
-    var url = new URL(window.location.href);
-    url.searchParams.delete('view');
-    window.location.replace(url.toString());
-  }
-  setActiveButton(getMode());
-  var a = document.getElementById('vmAdmin');
-  var l = document.getElementById('vmLive');
-  if (a) a.addEventListener('click', function(){ applyMode('admin'); });
-  if (l) l.addEventListener('click', function(){ applyMode('live'); });
-})();
-</script>
-` : ''}
-${FOOTER}
-
-<!-- 2026-05-20 Steve directive: allow 1 page browsing, then scroll-triggered
-     login modal for general (un-roled) users. Suppressed for admin/user/designer. -->
-<div id="dw-login-veil" style="display:none;position:fixed;inset:0;background:rgba(20,18,15,.42);backdrop-filter:blur(6px);z-index:9998;opacity:0;transition:opacity .4s ease;pointer-events:none"></div>
-<div id="dw-login-modal" style="display:none;position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:9999;background:#fff;border:1px solid #d8cdb0;border-radius:10px;padding:36px 32px;width:92%;max-width:440px;box-shadow:0 16px 48px rgba(60,40,20,.22);opacity:0;transition:opacity .4s ease .1s;pointer-events:none">
-  <h2 style="margin:0 0 6px;font:300 30px/1.1 'Bodoni Moda',Georgia,serif;letter-spacing:.02em;color:#3a2818">Continue browsing</h2>
-  <div style="color:#8a7c66;font:13px/1.5 ui-sans-serif,system-ui;margin-bottom:24px">Sign in to access the full 12,000+ pattern library, save favorites, and place orders.</div>
-  <div style="display:flex;flex-direction:column;gap:10px">
-    <form method="POST" action="/login" style="margin:0"><input type="hidden" name="role" value="designer"><input type="hidden" name="next" value="/designs"><button type="submit" style="width:100%;appearance:none;padding:14px 18px;border:1px solid #c9a14b;background:#c9a14b;color:#fff;font:600 13px/1 ui-sans-serif,system-ui;letter-spacing:.06em;text-transform:uppercase;border-radius:5px;cursor:pointer">★ Sign in as Designer / Trade</button></form>
-    <form method="POST" action="/login" style="margin:0"><input type="hidden" name="role" value="user"><input type="hidden" name="next" value="/designs"><button type="submit" style="width:100%;appearance:none;padding:14px 18px;border:1px solid #d8cdb0;background:#fff;color:#3a2818;font:600 13px/1 ui-sans-serif,system-ui;letter-spacing:.06em;text-transform:uppercase;border-radius:5px;cursor:pointer">Sign in as Customer</button></form>
-    <form method="POST" action="/login" style="margin:0"><input type="hidden" name="role" value="admin"><input type="hidden" name="next" value="/designs"><button type="submit" style="width:100%;appearance:none;padding:14px 18px;border:1px solid #3a2818;background:#3a2818;color:#fff;font:600 13px/1 ui-sans-serif,system-ui;letter-spacing:.06em;text-transform:uppercase;border-radius:5px;cursor:pointer">Admin Login</button></form>
-  </div>
-  <div style="color:#8a7c66;font:11px/1.4 ui-sans-serif;letter-spacing:.04em;margin-top:18px;text-align:center">Designer + Customer access is open during preview.</div>
-</div>
-<script>
-(function(){
-  var role = ${JSON.stringify(_role || '')};
-  if (role) return;  // logged-in users skip modal entirely
-  var veil = document.getElementById('dw-login-veil');
-  var modal = document.getElementById('dw-login-modal');
-  if (!veil || !modal) return;
-  var shown = false;
-  function show() {
-    if (shown) return;
-    shown = true;
-    veil.style.display = 'block';
-    modal.style.display = 'block';
-    requestAnimationFrame(function(){
-      veil.style.opacity = '1';
-      modal.style.opacity = '1';
-      veil.style.pointerEvents = 'auto';
-      modal.style.pointerEvents = 'auto';
-      document.body.style.overflow = 'hidden';
-    });
-    window.removeEventListener('scroll', onScroll);
-  }
-  function onScroll() {
-    // Trigger after user scrolls past ~1.2× viewport height (= roughly 1 page of designs)
-    if (window.scrollY > window.innerHeight * 1.2) show();
-  }
-  window.addEventListener('scroll', onScroll, { passive: true });
-  // Failsafe: also show after 30s on the page if they haven't scrolled
-  setTimeout(function(){ if (!shown) show(); }, 30000);
-})();
-</script>
-
-<script type="application/ld+json">${schema}</script>
-<script>
-// ── Infinite scroll — fetch successive /designs?page=N HTML pages, extract
-// just the design-card children, append to the grid when the sentinel
-// approaches the viewport. Single-flight + page cursor + done-state.
-(function(){
-  var grid = document.getElementById('catalog-grid');
-  var sentinel = document.getElementById('infinite-sentinel');
-  var statusEl = document.getElementById('infinite-status');
-  if (!grid || !sentinel || !statusEl) return;
-  var currentPage = parseInt(grid.dataset.currentPage || '1', 10);
-  var totalPages  = parseInt(grid.dataset.totalPages || '1', 10);
-  if (currentPage >= totalPages) return;  // already on last page
-  var loading = false;
-
-  function nextUrl() {
-    var u = new URL(window.location.href);
-    u.searchParams.set('page', String(currentPage + 1));
-    return u.toString();
-  }
-
-  function appendCards(html) {
-    var doc = new DOMParser().parseFromString(html, 'text/html');
-    var nextGrid = doc.getElementById('catalog-grid');
-    if (!nextGrid) return 0;
-    var added = 0;
-    Array.from(nextGrid.children).forEach(function(node){
-      grid.appendChild(node);
-      added++;
-    });
-    return added;
-  }
-
-  function loadMore() {
-    if (loading) return;
-    if (currentPage >= totalPages) {
-      statusEl.textContent = 'all ' + (grid.dataset.total||'') + ' designs loaded';
-      return;
-    }
-    loading = true;
-    statusEl.textContent = 'loading page ' + (currentPage + 1) + ' of ' + totalPages + '…';
-    fetch(nextUrl(), { credentials: 'same-origin' })
-      .then(function(r){ return r.text(); })
-      .then(function(html){
-        var n = appendCards(html);
-        currentPage++;
-        if (n === 0) {
-          // no more rows — stop polling
-          statusEl.textContent = 'all ' + (grid.dataset.total||'') + ' designs loaded';
-          io.disconnect();
-        } else if (currentPage >= totalPages) {
-          statusEl.textContent = 'all ' + (grid.dataset.total||'') + ' designs loaded';
-          io.disconnect();
-        } else {
-          statusEl.textContent = 'scroll for page ' + (currentPage + 1) + ' of ' + totalPages;
-        }
-        loading = false;
-      })
-      .catch(function(){ statusEl.textContent = 'load error — scroll to retry'; loading = false; });
-  }
-
-  var io = new IntersectionObserver(function(entries){
-    entries.forEach(function(e){ if (e.isIntersecting) loadMore(); });
-  }, { rootMargin: '600px 0px 600px 0px' });
-  io.observe(sentinel);
-})();
-</script>
-<script>
-(function(){
-  var slider = document.getElementById('density-slider');
-  var grid   = document.getElementById('catalog-grid');
-  var label  = document.getElementById('density-value');
-  var saved  = localStorage.getItem('wallco-density');
-  if (saved) slider.value = saved;
-  // Clamp inline --cols for narrow viewports so a desktop "density=8" pref
-  // doesn't crush mobile to 45px tiles. Inline style beats stylesheet
-  // specificity, so the @media (max-width: 600px) {--cols:2} rule otherwise
-  // gets ignored. clamp() also re-evaluates on resize via the listener below.
-  function applyCols(v){
-    var w = window.innerWidth, n = parseInt(v, 10);
-    if (w <= 600)      n = Math.min(n, 2);
-    else if (w <= 900) n = Math.min(n, 3);
-    grid.style.setProperty('--cols', String(n));
-    if (label) label.textContent = String(n);
-    slider.setAttribute('aria-valuenow', String(n));
-  }
-  applyCols(slider.value);
-  slider.addEventListener('input', function() {
-    applyCols(this.value);
-    localStorage.setItem('wallco-density', this.value);
-  });
-  window.addEventListener('resize', function(){ applyCols(slider.value); });
-
-  // Admin-only spacing slider — controls --grid-gap CSS var.
-  // Loosens visual tightness of the grid; persists in localStorage.
-  var spSlider = document.getElementById('spacing-slider');
-  var spLabel  = document.getElementById('spacing-value');
-  if (spSlider) {
-    var spSaved = localStorage.getItem('wallco-grid-gap');
-    if (spSaved !== null) spSlider.value = spSaved;
-    function applyGap(v) {
-      var n = parseInt(v, 10);
-      if (isNaN(n) || n < 0) n = 0;
-      grid.style.setProperty('--grid-gap', n + 'px');
-      if (spLabel) spLabel.textContent = n + 'px';
-      spSlider.setAttribute('aria-valuenow', String(n));
-    }
-    applyGap(spSlider.value);
-    spSlider.addEventListener('input', function() {
-      applyGap(this.value);
-      localStorage.setItem('wallco-grid-gap', this.value);
-    });
-  }
-
-  // Keyboard shortcut: "/" focuses search (GitHub/X/Slack pattern). Esc clears
-  // focus and returns to grid. Skipped when user is already typing in an input.
-  var search = document.getElementById('catalog-search-input');
-  if (search) {
-    document.addEventListener('keydown', function(e){
-      var t = e.target, tag = (t && t.tagName) || '';
-      var inField = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (t && t.isContentEditable);
-      if (e.key === '/' && !inField) {
-        e.preventDefault();
-        search.focus();
-        search.select && search.select();
-      } else if (e.key === 'Escape' && document.activeElement === search) {
-        search.blur();
-      }
-    });
-  }
-  var sortSel = document.getElementById('sort-select');
-  var sortH   = document.getElementById('sort-hidden');
-  if (sortSel && sortH) {
-    var savedSort = localStorage.getItem('wallco-sort');
-    if (savedSort && savedSort !== '${sort}') {
-      sortH.value = savedSort;
-    }
-    sortSel.addEventListener('change', function() {
-      localStorage.setItem('wallco-sort', this.value);
-    });
-  }
-
-  // ── Favorites — heart button per card, "Favorites only" filter chip.
-  //    Persists in localStorage('wallco.favorites') as a JSON array of design ids.
-  (function favorites(){
-    var FKEY = 'wallco.favorites';
-    function readSet(){
-      try { return new Set(JSON.parse(localStorage.getItem(FKEY) || '[]').map(String)); }
-      catch(e) { return new Set(); }
-    }
-    function writeSet(set){
-      localStorage.setItem(FKEY, JSON.stringify(Array.from(set).map(Number)));
-    }
-    var favs = readSet();
-    function setBtnState(btn, isFav){
-      btn.setAttribute('aria-pressed', isFav ? 'true' : 'false');
-      btn.classList.toggle('is-fav', isFav);
-    }
-    function hydrateButtons(){
-      document.querySelectorAll('.fav-btn').forEach(function(btn){
-        var id = btn.getAttribute('data-fav-id');
-        setBtnState(btn, favs.has(id));
-      });
-      updateFavCount();
-    }
-    function updateFavCount(){
-      var n = favs.size;
-      var c = document.getElementById('fav-only-count');
-      if (c) c.textContent = '·' + n;
-      var extra = document.getElementById('fav-count-extra');
-      if (extra) extra.textContent = n ? '· ' + n + ' favorited' : '';
-    }
-    function applyFavFilter(showOnlyFavs){
-      var cards = document.querySelectorAll('.design-card[data-design-id]');
-      var anyVisible = false;
-      cards.forEach(function(card){
-        var id = card.getAttribute('data-design-id');
-        var match = !showOnlyFavs || favs.has(id);
-        card.style.display = match ? '' : 'none';
-        if (match) anyVisible = true;
-      });
-      var grid = document.getElementById('catalog-grid');
-      var empty = document.getElementById('fav-empty-state');
-      if (showOnlyFavs && !anyVisible) {
-        if (!empty && grid) {
-          empty = document.createElement('div');
-          empty.id = 'fav-empty-state';
-          empty.style.cssText = 'grid-column:1/-1;padding:48px 24px;text-align:center;color:var(--ink-soft,#666);font:14px/1.6 var(--sans,system-ui)';
-          empty.innerHTML = '<div style="font-size:32px;margin-bottom:8px">♡</div><strong>No favorites yet.</strong><br>Tap the heart on any design to save it here.';
-          grid.appendChild(empty);
-        }
-      } else if (empty) {
-        empty.remove();
-      }
-    }
-    document.addEventListener('click', function(e){
-      var btn = e.target.closest && e.target.closest('.fav-btn');
-      if (!btn) return;
-      e.preventDefault();
-      e.stopPropagation();
-      var id = btn.getAttribute('data-fav-id');
-      if (favs.has(id)) favs.delete(id); else favs.add(id);
-      writeSet(favs);
-      setBtnState(btn, favs.has(id));
-      updateFavCount();
-      var toggle = document.getElementById('fav-only-toggle');
-      if (toggle && toggle.getAttribute('aria-pressed') === 'true') {
-        applyFavFilter(true);
-      }
-    });
-    var favToggle = document.getElementById('fav-only-toggle');
-    if (favToggle) {
-      favToggle.addEventListener('click', function(){
-        var on = this.getAttribute('aria-pressed') !== 'true';
-        this.setAttribute('aria-pressed', on ? 'true' : 'false');
-        this.style.background = on ? 'var(--accent,#c9a14b)' : 'transparent';
-        this.style.color = on ? 'var(--bg,#fff)' : 'var(--ink-soft)';
-        this.style.borderColor = on ? 'var(--accent,#c9a14b)' : 'var(--line)';
-        this.innerHTML = (on ? '♥' : '♡') + ' Favorites <span id="fav-only-count" style="opacity:.55">·' + favs.size + '</span>';
-        applyFavFilter(on);
-      });
-    }
-    hydrateButtons();
-  })();
-
-  // Live search dropdown — hits /api/designs/search, falls back to form submit on Enter / no-JS.
-  var sIn = document.getElementById('catalog-search-input');
-  var sBox = document.getElementById('catalog-search-suggest');
-  if (sIn && sBox) {
-    var sTimer = null, sCtl = null, sIdx = -1, sItems = [];
-    var esc = function(s){ return String(s||'').replace(/[&<>"]/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); };
-    var hide = function(){ sBox.style.display='none'; sIn.setAttribute('aria-expanded','false'); sIdx=-1; };
-    var render = function(results, qStr){
-      if (!results || !results.length) {
-        var dym = (window._didYouMean || []);
-        var dymRow = '';
-        if (dym.length) {
-          dymRow = '<div style="padding:10px 14px 12px;border-top:1px solid var(--line,#f0eee9);background:#fafaf7"><div style="font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);margin-bottom:6px">Did you mean</div><div style="display:flex;flex-wrap:wrap;gap:5px">'
-            + dym.slice(0,5).map(function(s){
-              return '<a href="/designs?q='+encodeURIComponent(s.word)+'" style="display:inline-flex;align-items:center;padding:4px 10px;font:11px var(--sans,system-ui);background:var(--bg,#fff);color:var(--ink,#111);border:1px solid var(--line,#e6e2d8);border-radius:999px;text-decoration:none">'+esc(s.word)+'</a>';
-            }).join('')
-            + '</div></div>';
-        }
-        sBox.innerHTML = '<div style="padding:14px 16px;font:13px var(--sans,system-ui);color:var(--ink-faint,#888)">No matches for &ldquo;'+esc(qStr)+'&rdquo;</div>' + dymRow;
-        sBox.style.display='block'; sIn.setAttribute('aria-expanded','true');
-        sItems = []; sIdx = -1;
-        return;
-      }
-      var savedSet = window._savedIds || null;
-      var rows = results.slice(0, 8).map(function(r, i){
-        var img = r.image_url ? '<div style="width:44px;height:44px;flex-shrink:0;background:#f2efe8 url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:4px;border:1px solid var(--line,#eee)"></div>' : '<div style="width:44px;height:44px;flex-shrink:0;background:#f2efe8;border-radius:4px"></div>';
-        var dot = r.dominant_hex ? '<span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
-        var roomBadge = (r.room_mockups && r.room_mockups.length) ? '<span style="font:10px var(--sans,system-ui);color:var(--gold,#c9a14b);margin-left:6px">·room</span>' : '';
-        var heart = (savedSet && savedSet.has(r.id)) ? '<span title="Saved" style="font:11px var(--sans,system-ui);color:var(--gold,#c9a14b);margin-left:6px" aria-label="saved">♥</span>' : '';
-        return '<a href="'+esc(r.url)+'" role="option" data-idx="'+i+'" style="display:flex;align-items:center;gap:10px;padding:8px 12px;text-decoration:none;color:var(--ink,#111);border-bottom:1px solid var(--line,#f0eee9);font:13px var(--sans,system-ui)">'
-          + img
-          + '<div style="min-width:0;flex:1"><div style="font-weight:500;color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title||('Design #'+r.id))+heart+'</div>'
-          + '<div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#888);margin-top:2px">'+dot+esc(r.category||'')+roomBadge+'</div></div>'
-        + '</a>';
-      }).join('');
-      var more = results.length > 8
-        ? '<a href="/designs?q='+encodeURIComponent(qStr)+'" style="display:block;padding:8px 12px;font:12px var(--sans,system-ui);color:var(--ink-soft,#555);text-align:center;background:var(--card-bg,#fafafa);text-decoration:none">See all '+results.length+' results →</a>'
-        : '';
-      // Refine chips — top motifs from the matched designs (additive, harmless if absent)
-      var facetRow = '';
-      if (window._facets && window._facets.motifs && window._facets.motifs.length) {
-        var chips = window._facets.motifs.slice(0, 6).map(function(f){
-          return '<a href="/designs?motif='+encodeURIComponent(f.motif)+'" style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;font:11px var(--sans,system-ui);background:transparent;color:var(--ink-soft,#555);border:1px solid var(--line,#e6e2d8);border-radius:999px;text-decoration:none;white-space:nowrap">'+esc(f.motif)+' <span style="color:var(--ink-faint,#999);font-size:10px">·'+f.count+'</span></a>';
-        }).join('');
-        facetRow = '<div style="display:flex;flex-wrap:wrap;gap:5px;padding:8px 12px;border-top:1px solid var(--line,#f0eee9);background:#fafaf7"><span style="font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint,#999);align-self:center;margin-right:4px">Refine</span>'+chips+'</div>';
-      }
-      sBox.innerHTML = rows + facetRow + more;
-      sBox.style.display='block'; sIn.setAttribute('aria-expanded','true');
-      sItems = Array.prototype.slice.call(sBox.querySelectorAll('a[role=option]'));
-      sIdx = -1;
-    };
-    var fetchSuggest = function(qStr){
-      if (sCtl) sCtl.abort();
-      sCtl = new AbortController();
-      fetch('/api/designs/search?q='+encodeURIComponent(qStr)+'&limit=24', { signal: sCtl.signal })
-        .then(function(r){ return r.json(); })
-        .then(function(j){ window._facets = j.facets || null; window._didYouMean = j.did_you_mean || []; render(j.results||[], qStr); })
-        .catch(function(){ /* aborted or network — ignore */ });
-    };
-    // Lazy-fetch saved-ids once on first input so the dropdown can render ♥ on already-saved designs.
-    // Anonymous users get authenticated:false + empty ids; we cache the result either way.
-    var savedFetched = false;
-    function ensureSavedIds(){
-      if (savedFetched || window._savedIds) return;
-      savedFetched = true;
-      fetch('/api/me/saved', { credentials: 'same-origin' })
-        .then(function(r){ return r.json(); })
-        .then(function(j){ window._savedIds = new Set(j.ids || []); })
-        .catch(function(){ window._savedIds = new Set(); });
-    }
-    sIn.addEventListener('input', function(){
-      var v = this.value.trim();
-      if (sTimer) clearTimeout(sTimer);
-      if (v.length < 2) { hide(); return; }
-      ensureSavedIds();
-      sTimer = setTimeout(function(){ fetchSuggest(v); }, 180);
-    });
-    sIn.addEventListener('keydown', function(e){
-      if (e.key === 'Escape') { hide(); return; }
-      if (sBox.style.display === 'none' || !sItems.length) return;
-      if (e.key === 'ArrowDown') {
-        e.preventDefault();
-        sIdx = (sIdx + 1) % sItems.length;
-        sItems.forEach(function(a,i){ a.style.background = i===sIdx ? 'var(--card-bg,#fafafa)' : ''; });
-        sItems[sIdx].scrollIntoView({ block:'nearest' });
-      } else if (e.key === 'ArrowUp') {
-        e.preventDefault();
-        sIdx = (sIdx - 1 + sItems.length) % sItems.length;
-        sItems.forEach(function(a,i){ a.style.background = i===sIdx ? 'var(--card-bg,#fafafa)' : ''; });
-        sItems[sIdx].scrollIntoView({ block:'nearest' });
-      } else if (e.key === 'Enter' && sIdx >= 0) {
-        e.preventDefault();
-        window.location = sItems[sIdx].href;
-      }
-    });
-    document.addEventListener('click', function(e){
-      if (!sIn.contains(e.target) && !sBox.contains(e.target)) hide();
-    });
-    sIn.addEventListener('focus', function(){
-      if (this.value.trim().length >= 2 && sBox.innerHTML) {
-        sBox.style.display='block'; sIn.setAttribute('aria-expanded','true');
-      }
-    });
-  }
-})();
-</script>
-
-${_isAdmin ? `
-<style>
-  /* Review-mode controls (admin only — 127.0.0.1 / ?review=1) */
-  :root { --keep:#5fbf6e; --reject:#d2554a; --chip-bg:#3a3631; --chip-hot:#d2b15c; }
-  .design-card.review-card { display:block; background:transparent; border:1px solid #2a2825; border-radius:3px; overflow:hidden; transition: border-color .15s; padding:0; position:relative; }
-  /* Card IS the image. Meta + controls live in a slide-up flyout pinned to the
-     bottom of the image — no black void below the image when the card is idle.
-     Hover or focus-within expands the flyout; a pattern-# pill also surfaces
-     at top-left of the image on hover. */
-  .design-card.review-card .review-flyout {
-    position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
-    background: linear-gradient(to top, rgba(15,14,12,0.96) 0%, rgba(15,14,12,0.86) 70%, rgba(15,14,12,0) 100%);
-    color: #e8e2d6;
-    max-height: 0; overflow: hidden;
-    transition: max-height .26s ease;
-    pointer-events: none;
-  }
-  .design-card.review-card:hover .review-flyout,
-  .design-card.review-card:focus-within .review-flyout {
-    max-height: 300px;
-    pointer-events: auto;
-  }
-  /* Pattern # pill — visible only on hover, top-left of the image. */
-  .design-card.review-card .pattern-num {
-    position: absolute; top: 8px; left: 8px; z-index: 3;
-    padding: 3px 8px; font-size: 10px; font-weight: 600; letter-spacing: .06em;
-    background: rgba(15,14,12,0.78); color: #d2b15c;
-    border: 1px solid rgba(210,177,92,0.35); border-radius: 2px;
-    font-family: 'JetBrains Mono', Menlo, monospace;
-    opacity: 0; transform: translateY(-2px);
-    transition: opacity .18s ease, transform .18s ease;
-    pointer-events: none;
-  }
-  .design-card.review-card:hover .pattern-num,
-  .design-card.review-card:focus-within .pattern-num { opacity: 1; transform: translateY(0); }
-  /* Keep decision border + opacity dim visible regardless (so kept/rejected */
-  /* state shows at a glance without hovering) */
-  .design-card.review-card[data-decision]:not([data-decision=""]) .decision-badge { opacity:1 !important; }
-  .design-card.review-card[data-decision=keep]   { border-color: var(--keep); }
-  .design-card.review-card[data-decision=reject] { border-color: var(--reject); opacity:.55; }
-  .design-card.review-card .card-img { position:relative; display:block; aspect-ratio:1; background:#0d0c0a no-repeat center/cover; }
-  .design-card.review-card .card-img .decision-badge { position:absolute; top:8px; right:8px; padding:2px 8px; font-size:10px; border-radius:2px; text-transform:uppercase; letter-spacing:.06em; display:none; pointer-events:none; }
-  /* Admin-card hover actions — View / Sample buttons rendered inside the
-     image; hidden by default, appear on hover alongside the pattern-num pill.
-     Sits at bottom of the image; pointer-events on the wrapper let clicks
-     on the underlying link work, but the buttons re-enable their own. */
-  .design-card.review-card .admin-hover-actions {
-    position:absolute; left:8px; right:8px; bottom:8px; z-index:6;
-    display:flex; gap:6px; justify-content:center;
-    opacity:0; transition:opacity .15s ease;
-    pointer-events:none;
-  }
-  .design-card.review-card:hover .admin-hover-actions,
-  .design-card.review-card:focus-within .admin-hover-actions { opacity:1 }
-  .design-card.review-card .admin-hover-actions .chov-btn { pointer-events:auto; max-width:100px; font-size:10px; padding:5px 8px }
-  .design-card.review-card[data-decision=keep]   .decision-badge { display:inline-block; background:var(--keep); color:#0f0e0c; }
-  .design-card.review-card[data-decision=keep]   .decision-badge::after { content:'KEEP'; }
-  .design-card.review-card[data-decision=reject] .decision-badge { display:inline-block; background:var(--reject); color:#fff; }
-  .design-card.review-card[data-decision=reject] .decision-badge::after { content:'REJECT'; }
-  .design-card.review-card .card-meta { padding:10px 12px 6px; display:flex; flex-direction:column; gap:2px; }
-  .design-card.review-card .card-title { font-size:12px; line-height:1.3; color:#e8e2d6; max-height:2.6em; overflow:hidden; }
-  .design-card.review-card .card-cat   { font-size:10px; color:#888; }
-  .design-card.review-card .color-dot  { width:9px; height:9px; border-radius:50%; border:1px solid #00000060; display:inline-block; margin-right:5px; }
-  .review-controls { padding:0 12px 14px; }
-  .sliders { display:flex; flex-direction:column; gap:4px; margin: 8px 0 10px; }
-  .sliders label { display:grid; grid-template-columns:48px 1fr 22px; gap:6px; align-items:center; font-size:10px; color:#bba; text-transform:uppercase; letter-spacing:.06em; }
-  .sliders label span { text-align:right; color:#d2b15c; font-variant-numeric:tabular-nums; }
-  .sliders input[type=range] { width:100%; -webkit-appearance:none; height:2px; background:#3a3631; border-radius:2px; outline:none; }
-  .sliders input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; width:11px; height:11px; border-radius:50%; background:#d2b15c; cursor:pointer; border:none; }
-  .decision { display:flex; gap:6px; margin-bottom:9px; }
-  .decision button { flex:1; padding:5px 8px; font-size:11px; border:1px solid #3a3631; background:#16140f; color:#bba; cursor:pointer; border-radius:2px; transition:all .12s; text-transform:uppercase; letter-spacing:.06em; }
-  .decision button:hover { background:#1f1c18; color:#fff; }
-  .design-card.review-card[data-decision=keep]   .btn-keep   { background:var(--keep);   color:#0f0e0c; border-color:var(--keep); }
-  .design-card.review-card[data-decision=reject] .btn-reject { background:var(--reject); color:#fff;    border-color:var(--reject); }
-  .decision .btn-pair { background:#16140f; color:#d2b15c; border-color:#3a3631; }
-  .decision .btn-pair:hover { background:#d2b15c; color:#0f0e0c; border-color:#d2b15c; }
-  /* Pair modal */
-  /* Right-pinned drawers — pair + chat. Side-by-side when both open. */
-  :root { --modal-pair-w: 480px; --modal-chat-w: 420px; }
-  #pair-modal {
-    position:fixed; top:0; right:0; bottom:0;
-    width: min(var(--modal-pair-w), 92vw);
-    background:#16140f; border-left:1px solid #2a2825;
-    display:flex; flex-direction:column; z-index:1001;
-    box-shadow:-8px 0 20px rgba(0,0,0,.4);
-    color:#e8e2d6; font:13px/1.4 -apple-system, system-ui, sans-serif;
-    transform: translateX(100%);
-    transition: transform .26s cubic-bezier(.4,0,.2,1), right .26s cubic-bezier(.4,0,.2,1);
-    visibility: hidden;
-  }
-  #pair-modal.open { transform: translateX(0); visibility: visible; }
-  /* Side-by-side when both right-pinned modals are open — chat slides left by pair's width */
-  body:has(#pair-modal.open):has(#chat-modal.open) #chat-modal { right: var(--modal-pair-w); }
-  /* When chat is to the left of pair, drop pair's left-shadow (it overlapped chat) and add a clean seam */
-  body:has(#chat-modal.open):has(#pair-modal.open) #pair-modal { box-shadow: none; border-left-color: #3a3631; }
-  body:has(#chat-modal.open):has(#pair-modal.open) #chat-modal { border-right: 1px solid #2a2825; }
-  /* Narrow viewport — can't fit both. Hide chat while pair is open; user toggles between them. */
-  @media (max-width: 920px) {
-    body:has(#pair-modal.open):has(#chat-modal.open) #chat-modal { right: 0; opacity: 0; visibility: hidden; pointer-events: none; }
-    #pair-modal { width: min(var(--modal-pair-w), 100vw); }
-    #chat-modal { width: min(var(--modal-chat-w), 100vw); }
-  }
-  #pair-modal header { padding:14px 18px; border-bottom:1px solid #2a2825; display:flex; gap:10px; align-items:center; }
-  #pair-modal header .ctx-main { flex:1; font-size:12px; color:#d2b15c; }
-  #pair-modal header .ctx-sub  { font-size:10px; color:#888; margin-top:2px; }
-  #pair-modal header .refresh,#pair-modal header .close { background:none; border:1px solid #3a3631; color:#bba; font-size:13px; cursor:pointer; padding:4px 9px; border-radius:2px; }
-  #pair-modal header .refresh:hover,#pair-modal header .close:hover { color:#fff; border-color:#d2b15c; }
-  #pair-modal header .close { font-size:18px; line-height:1; padding:2px 8px; }
-  #pair-body { flex:1; overflow-y:auto; padding:14px 18px; display:flex; flex-direction:column; gap:12px; }
-  #pair-body .pair-empty { color:#888; font-style:italic; padding:24px 0; text-align:center; }
-  #pair-body .pair-loading { color:#888; font-style:italic; padding:30px 0; text-align:center; }
-  .pair-card { display:flex; gap:12px; align-items:flex-start; padding:10px 12px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; }
-  .pair-card .pair-thumb { flex:0 0 64px; width:64px; height:64px; border-radius:3px; background:#0d0c0a no-repeat center/cover; display:flex; align-items:center; justify-content:center; font-size:22px; color:#0f0e0c; }
-  .pair-card .pair-thumb.material { background:#2a2825; color:#d2b15c; font-size:22px; }
-  .pair-card .pair-body { flex:1; min-width:0; }
-  .pair-card .pair-title { font-size:12px; color:#e8e2d6; margin-bottom:3px; line-height:1.3; }
-  .pair-card .pair-type  { display:inline-block; font-size:9px; text-transform:uppercase; letter-spacing:.08em; padding:1px 6px; border-radius:2px; background:#2a2825; color:#d2b15c; margin-bottom:5px; }
-  .pair-card .pair-reason { font-size:11px; color:#bba; line-height:1.4; }
-  .pair-card .pair-hexcode { font-family: ui-monospace, monospace; font-size:10px; color:#888; margin-left:6px; }
-  .pair-card a.pair-link { color:inherit; text-decoration:none; display:flex; gap:12px; align-items:flex-start; flex:1; }
-  .pair-card a.pair-link:hover .pair-title { color:#d2b15c; }
-  .pair-card .pair-pin { background:none; border:1px solid #3a3631; color:#666; font-size:14px; cursor:pointer; padding:3px 8px; border-radius:14px; transition:all .12s; flex-shrink:0; align-self:flex-start; }
-  .pair-card .pair-pin:hover { color:#d2b15c; border-color:#d2b15c; }
-  .pair-card.pinned { border-color:var(--chip-hot); background:#1f1b13; }
-  .pair-card.pinned .pair-pin { color:#d2b15c; background:#d2b15c33; border-color:#d2b15c; }
-  /* Paint-chip variant — left thumb shows the chip color with brand code laid */
-  /* on top in mini-fan-deck style. Brand stripe accents on the left edge.    */
-  .pair-card.paint-card .paint-thumb { position:relative; box-shadow: inset 0 0 0 1px rgba(0,0,0,.18); }
-  .pair-card.paint-card .paint-code { position:absolute; left:4px; bottom:3px; font-family:ui-monospace,monospace; font-size:9px; padding:1px 4px; background:rgba(0,0,0,.55); color:#fff; border-radius:2px; letter-spacing:.04em; }
-  .pair-card.paint-card.paint-de { border-left:3px solid #c84a3a; }
-  .pair-card.paint-card.paint-sw { border-left:3px solid #1a4f9c; }
-  .pair-card .pair-vision-badge { display:inline-block; font-size:9px; padding:0 5px; border:1px solid #3a3631; border-radius:2px; color:#888; margin-left:6px; text-transform:uppercase; letter-spacing:.05em; }
-  #pair-vision-read { background:#1a1816; padding:8px 12px; margin:0 18px 8px; border:1px solid #2a2825; border-radius:2px; font-size:11px; color:#bba; line-height:1.45; font-style:italic; }
-  #pair-vision-read.hidden { display:none; }
-  .why-chip { display:none; padding:8px 12px; background:rgba(210,177,92,.08); border:1px solid #d2b15c; border-left:3px solid #d2b15c; border-radius:3px; font-size:12px; font-weight:500; color:#e8c46d; margin-bottom:10px; cursor:pointer; line-height:1.4; position:relative; }
-  .why-chip.visible { display:block; }
-  .why-chip.loading::after { content:' …'; opacity:.6; }
-  .why-chip::before { content:'CURATOR'; position:absolute; top:-8px; left:8px; padding:1px 6px; background:#0f0e0c; font:8px/1 ui-monospace, Menlo, monospace; color:#d2b15c; letter-spacing:.1em; }
-  .why-chip:hover { background:rgba(210,177,92,.14); border-color:#e8c46d; }
-  .why-chip:hover::after { content:'tap to open chat →'; position:absolute; right:8px; bottom:-1px; font:9px/1.5 -apple-system, system-ui, sans-serif; color:#888; font-weight:normal; font-style:italic; }
-  .chips { display:flex; flex-wrap:wrap; gap:5px; }
-  .chip { padding:3px 8px; background:var(--chip-bg); color:#bba; border-radius:10px; font-size:10px; cursor:pointer; border:1px solid transparent; transition:all .12s; }
-  .chip:hover { background:#4a443d; color:#fff; }
-  .chip.has-chat { border-color: var(--chip-hot); color: var(--chip-hot); }
-  /* chat side panel */
-  #chat-modal {
-    position:fixed; top:0; right:0; bottom:0;
-    width: min(var(--modal-chat-w), 92vw);
-    background:#16140f; border-left:1px solid #2a2825;
-    display:flex; flex-direction:column; z-index:1000;
-    box-shadow:-8px 0 20px rgba(0,0,0,.4);
-    color:#e8e2d6; font:13px/1.4 -apple-system, system-ui, sans-serif;
-    transform: translateX(100%);
-    transition: transform .26s cubic-bezier(.4,0,.2,1), right .26s cubic-bezier(.4,0,.2,1), opacity .2s;
-    visibility: hidden;
-  }
-  #chat-modal.open { transform: translateX(0); visibility: visible; }
-  #chat-modal header { padding:14px 18px; border-bottom:1px solid #2a2825; display:flex; gap:10px; align-items:center; }
-  #chat-modal header .ctx-main { flex:1; font-size:12px; color:#d2b15c; }
-  #chat-modal header .ctx-sub  { font-size:10px; color:#888; }
-  #chat-modal header .close { background:none; border:none; color:#999; font-size:22px; cursor:pointer; padding:0 4px; }
-  #chat-history { flex:1; overflow-y:auto; padding:16px 18px; display:flex; flex-direction:column; gap:10px; }
-  #chat-history .msg { padding:9px 12px; border-radius:10px; max-width:88%; font-size:12px; line-height:1.45; white-space:pre-wrap; word-wrap:break-word; }
-  #chat-history .msg.user { background:#2a2825; color:#fff; align-self:flex-end; }
-  #chat-history .msg.assistant { background:#1f1c18; color:#e8e2d6; align-self:flex-start; border:1px solid #2a2825; }
-  #chat-history .msg.assistant.loading { color:#888; font-style:italic; }
-  #chat-form { display:flex; padding:12px 14px; border-top:1px solid #2a2825; gap:8px; }
-  #chat-form input { flex:1; padding:8px 12px; background:#0f0e0c; border:1px solid #2a2825; color:#fff; font:inherit; border-radius:2px; outline:none; }
-  #chat-form input:focus { border-color:#d2b15c; }
-  #chat-form button { padding:8px 14px; background:#d2b15c; color:#0f0e0c; border:none; cursor:pointer; font:inherit; font-weight:500; border-radius:2px; }
-  #chat-form button:disabled { opacity:.5; cursor:not-allowed; }
-  /* Hide the global wallco-chat FAB whenever the design-review chat /
-     pairings / isolate modals are open — otherwise the FAB sits on top
-     of the modal's Send button and intercepts pointer events. Caught by
-     Playwright review.spec.js #4. */
-  body:has(#chat-modal.open) #wallco-chat-fab,
-  body:has(#pair-modal.open) #wallco-chat-fab,
-  body:has(#iso-modal.open)  #wallco-chat-fab { display: none !important; }
-  .admin-banner { background:#2a2825; color:#d2b15c; padding:6px 14px; font-size:11px; letter-spacing:.06em; text-transform:uppercase; }
-  /* Isolate modal — left-pinned drawer; mirrors pair-modal styling */
-  :root { --modal-iso-w: 520px; }
-  #iso-modal {
-    position:fixed; top:0; left:0; bottom:0;
-    width: min(var(--modal-iso-w), 96vw);
-    background:#16140f; border-right:1px solid #2a2825;
-    display:flex; flex-direction:column; z-index:1001;
-    box-shadow:8px 0 20px rgba(0,0,0,.4);
-    color:#e8e2d6; font:13px/1.4 -apple-system, system-ui, sans-serif;
-    transform: translateX(-100%);
-    transition: transform .26s cubic-bezier(.4,0,.2,1);
-    visibility: hidden;
-  }
-  #iso-modal.open { transform: translateX(0); visibility: visible; }
-  #iso-modal header { padding:14px 18px; border-bottom:1px solid #2a2825; display:flex; gap:10px; align-items:center; }
-  #iso-modal header .ctx-main { flex:1; font-size:12px; color:#d2b15c; }
-  #iso-modal header .ctx-sub  { font-size:10px; color:#888; margin-top:2px; }
-  #iso-modal header .close { background:none; border:1px solid #3a3631; color:#bba; font-size:18px; line-height:1; padding:2px 8px; cursor:pointer; border-radius:2px; }
-  #iso-modal header .close:hover { color:#fff; border-color:#d2b15c; }
-  #iso-body { flex:1; overflow-y:auto; padding:14px 18px; display:flex; flex-direction:column; gap:14px; }
-  #iso-hero { width:100%; aspect-ratio:1/1; background:#0d0c0a no-repeat center/cover; border-radius:3px; border:1px solid #2a2825; }
-  .iso-palette { display:flex; gap:8px; flex-wrap:wrap; }
-  .iso-sw { flex:1 1 90px; min-width:80px; display:flex; flex-direction:column; align-items:stretch; gap:5px; padding:8px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; }
-  .iso-sw .dot { width:100%; aspect-ratio:1/1; border-radius:50%; border:1px solid rgba(255,255,255,.06); }
-  .iso-sw .hex { font-family: ui-monospace, monospace; font-size:11px; color:#e8e2d6; text-align:center; letter-spacing:.04em; }
-  .iso-sw .sw-code { font-size:10px; color:#d2b15c; text-align:center; letter-spacing:.04em; }
-  .iso-sw .sw-name { font-size:10px; color:#888; text-align:center; line-height:1.2; }
-  .iso-sw .sw-empty { font-size:10px; color:#555; text-align:center; font-style:italic; }
-  .iso-dominant-row { margin-bottom:14px; padding-bottom:14px; border-bottom:1px dashed #2a2825; }
-  .iso-dominant-card { display:flex; align-items:center; gap:14px; padding:10px 14px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; }
-  .iso-dominant-card .dot { width:46px; height:46px; border-radius:3px; border:1px solid rgba(255,255,255,.15); flex-shrink:0; }
-  .iso-dominant-card .info { flex:1; min-width:0; }
-  .iso-dominant-card .info .hex { font:13px/1.2 ui-monospace, Menlo, monospace; color:#e8e2d6; letter-spacing:.02em; }
-  .iso-dominant-card .info .sw  { font:11px/1.3 -apple-system, system-ui, sans-serif; color:#d2b15c; margin-top:3px; }
-  .iso-dominant-card .info .sw .sw-name { color:#bba; }
-  .iso-dominant-card .info .nosw { font-size:11px; color:#666; font-style:italic; }
-  .iso-sw .sw-closeness { display:inline-block; margin-top:4px; padding:1px 6px; border-radius:8px; font:9px/1.2 ui-monospace, Menlo, monospace; text-transform:uppercase; letter-spacing:.05em; }
-  .sw-close-exact       { background:#1f3a1f; color:#5fbf6e; }
-  .sw-close-close       { background:#1f2f3a; color:#5fa8bf; }
-  .sw-close-approximate { background:#3a2e1f; color:#d2b15c; }
-  .sw-close-weak        { background:#3a1f1f; color:#d2554a; }
-  .iso-grid-wallpaper .iso-de.iso-de-exact       { background:rgba(95,191,110,.85); color:#0a1a0d; }
-  .iso-grid-wallpaper .iso-de.iso-de-close       { background:rgba(95,168,191,.85); color:#0d1a1f; }
-  .iso-grid-wallpaper .iso-de.iso-de-approximate { background:rgba(210,177,92,.85); color:#1f1a0d; }
-  .iso-grid-wallpaper .iso-de.iso-de-weak        { background:rgba(210,85,74,.75); color:#1f0d0d; }
-  .iso-action { display:flex; gap:10px; align-items:center; }
-  .iso-btn-isolate { flex:1; padding:10px 16px; background:#d2b15c; color:#0f0e0c; border:none; cursor:pointer; font:13px/1.2 -apple-system, system-ui, sans-serif; font-weight:600; border-radius:3px; letter-spacing:.04em; text-transform:uppercase; }
-  .iso-btn-isolate:hover { background:#e8c46d; }
-  .iso-btn-isolate:disabled { opacity:.55; cursor:default; }
-  .iso-mode-toggle { display:none; gap:0; border:1px solid #3a3631; border-radius:3px; overflow:hidden; }
-  .iso-mode-toggle.visible { display:flex; }
-  .iso-mode-toggle button { flex:1; padding:8px 14px; background:#16140f; color:#bba; border:none; cursor:pointer; font:12px/1.2 -apple-system, system-ui, sans-serif; letter-spacing:.06em; text-transform:uppercase; }
-  .iso-mode-toggle button.active { background:#d2b15c; color:#0f0e0c; }
-  .iso-mode-toggle button + button { border-left:1px solid #3a3631; }
-  .iso-section { display:none; }
-  .iso-section.visible { display:block; }
-  .iso-section h4 { font-size:11px; color:#d2b15c; letter-spacing:.08em; text-transform:uppercase; margin:0 0 8px; }
-  .iso-grid-wallpaper { display:grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap:8px; }
-  .iso-grid-wallpaper a { display:block; aspect-ratio:1/1; background:#0d0c0a no-repeat center/cover; border-radius:3px; border:1px solid #2a2825; position:relative; text-decoration:none; }
-  .iso-grid-wallpaper a:hover { border-color:#d2b15c; }
-  .iso-grid-wallpaper .iso-de { position:absolute; bottom:2px; right:3px; font:9px/1 ui-monospace, monospace; color:#fff; background:rgba(0,0,0,.6); padding:1px 4px; border-radius:2px; }
-  .iso-grid-paint { display:grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap:10px; }
-  .iso-grid-paint .iso-paint-card { display:flex; flex-direction:column; gap:5px; padding:8px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; }
-  .iso-grid-paint .iso-paint-card .swatch { width:100%; aspect-ratio:1/1; border-radius:3px; border:1px solid rgba(255,255,255,.06); }
-  .iso-grid-paint .iso-paint-card .role { font-size:9px; color:#888; text-transform:uppercase; letter-spacing:.08em; }
-  .iso-grid-paint .iso-paint-card .hex { font-family: ui-monospace, monospace; font-size:11px; color:#e8e2d6; }
-  .iso-grid-paint .iso-paint-card .sw-code { font-size:10px; color:#d2b15c; }
-  .iso-grid-paint .iso-paint-card .sw-name { font-size:10px; color:#bba; line-height:1.2; }
-  .iso-grid-paint .iso-paint-card button { margin-top:4px; padding:4px 6px; background:#0f0e0c; border:1px solid #3a3631; color:#bba; font:10px/1 -apple-system, system-ui, sans-serif; cursor:pointer; border-radius:2px; letter-spacing:.04em; }
-  .iso-grid-paint .iso-paint-card button:hover { color:#d2b15c; border-color:#d2b15c; }
-  .iso-loading { color:#888; font-style:italic; padding:20px 0; text-align:center; font-size:12px; }
-  .design-card.review-card .img-wrap { cursor: zoom-in; }
-</style>
-
-<div id="chat-modal">
-  <header>
-    <div style="flex:1">
-      <div class="ctx-main" id="chat-chip-label">chip</div>
-      <div class="ctx-sub" id="chat-design-label">design</div>
-    </div>
-    <button class="close" id="chat-close" title="Close (Esc)">&times;</button>
-  </header>
-  <div id="chat-history"></div>
-  <form id="chat-form">
-    <input type="text" id="chat-input" placeholder="Ask the panel about this chip…" autocomplete="off">
-    <button type="submit" id="chat-send">Send</button>
-  </form>
-</div>
-
-<div id="pair-modal">
-  <header>
-    <div style="flex:1">
-      <div class="ctx-main" id="pair-design-title">Pairs well with…</div>
-      <div class="ctx-sub" id="pair-design-sub"></div>
-    </div>
-    <button class="refresh" id="pair-refresh" title="Force regenerate (Ollama + LLaVA)">↻</button>
-    <button class="close" id="pair-close" title="Close (Esc)">&times;</button>
-  </header>
-  <div id="pair-vision-read" class="hidden"></div>
-  <div id="pair-body"></div>
-</div>
-
-<div id="iso-modal" aria-hidden="true" role="dialog">
-  <header>
-    <div style="flex:1">
-      <div class="ctx-main" id="iso-design-title">Color isolate</div>
-      <div class="ctx-sub" id="iso-design-sub"></div>
-    </div>
-    <button class="close" id="iso-close" title="Close (Esc)">&times;</button>
-  </header>
-  <div id="iso-body">
-    <div id="iso-hero"></div>
-    <div id="iso-dominant" class="iso-dominant-row" style="display:none">
-      <h4 style="font-size:11px;color:#d2b15c;letter-spacing:.08em;text-transform:uppercase;margin:0 0 8px">Design's dominant color <span style="color:#666;font-weight:normal;text-transform:none;letter-spacing:0">(stored hex; may differ from extracted)</span></h4>
-      <div class="iso-dominant-card" id="iso-dominant-card"></div>
-    </div>
-    <div>
-      <h4 style="font-size:11px;color:#d2b15c;letter-spacing:.08em;text-transform:uppercase;margin:0 0 8px">Extracted palette <span style="color:#666;font-weight:normal;text-transform:none;letter-spacing:0">(top-6 from image pixels)</span></h4>
-      <div class="iso-palette" id="iso-palette"><div class="iso-loading">extracting…</div></div>
-    </div>
-    <div class="iso-action">
-      <button class="iso-btn-isolate" id="iso-btn-isolate" type="button">Isolate this design</button>
-    </div>
-    <div class="iso-mode-toggle" id="iso-mode-toggle">
-      <button type="button" data-mode="wallpaper" class="active">Wallpaper</button>
-      <button type="button" data-mode="paint">Paint</button>
-    </div>
-    <section class="iso-section visible" id="iso-section-wallpaper">
-      <h4>Coordinating wallpapers</h4>
-      <div class="iso-grid-wallpaper" id="iso-grid-wallpaper"><div class="iso-loading">loading…</div></div>
-    </section>
-    <section class="iso-section" id="iso-section-paint">
-      <h4>Coordinating paint colors</h4>
-      <div class="iso-grid-paint" id="iso-grid-paint"></div>
-    </section>
-  </div>
-</div>
-
-<script>
-(function(){
-  // ── State ──────────────────────────────────────────────────────
-  const REVIEWS = {};            // designId -> review object
-  const CHIPS = {};              // designId -> [{key,label,kind}]
-  let CURRENT_CHIP = null;
-
-  const $ = (s, el=document) => el.querySelector(s);
-  const $$ = (s, el=document) => Array.from(el.querySelectorAll(s));
-
-  // ── Boot: fetch reviews + chips, then wire UI ──────────────────
-  async function boot() {
-    try {
-      const r = await fetch('/api/reviews/all');
-      Object.assign(REVIEWS, await r.json());
-    } catch {}
-    $$('.design-card.review-card').forEach(card => {
-      applyReviewToCard(card);
-      wireCard(card);
-    });
-    loadChipsForVisibleCards();
-  }
-
-  function applyReviewToCard(card) {
-    const id = card.dataset.id;
-    const r = REVIEWS[id];
-    if (!r) return;
-    card.dataset.decision = r.decision || '';
-    const inputs = card.querySelectorAll('.sliders input[type=range]');
-    if (r.design != null) { inputs[0].value = r.design; inputs[0].nextElementSibling.textContent = r.design; }
-    if (r.color  != null) { inputs[1].value = r.color;  inputs[1].nextElementSibling.textContent = r.color;  }
-    if (r.style  != null) { inputs[2].value = r.style;  inputs[2].nextElementSibling.textContent = r.style;  }
-    if (r.why) {
-      const why = card.querySelector('.why-chip');
-      why.textContent = r.why;
-      why.classList.add('visible');
-    }
-  }
-
-  function wireCard(card) {
-    const id = card.dataset.id;
-    // sliders
-    card.querySelectorAll('.sliders input[type=range]').forEach(slider => {
-      slider.addEventListener('input', () => { slider.nextElementSibling.textContent = slider.value; });
-      slider.addEventListener('change', () => saveReview(card));
-    });
-    // K/R
-    card.querySelector('.btn-keep').addEventListener('click', e => {
-      e.preventDefault(); e.stopPropagation();
-      card.dataset.decision = card.dataset.decision === 'keep' ? '' : 'keep';
-      saveReview(card);
-    });
-    card.querySelector('.btn-reject').addEventListener('click', e => {
-      e.preventDefault(); e.stopPropagation();
-      card.dataset.decision = card.dataset.decision === 'reject' ? '' : 'reject';
-      saveReview(card);
-    });
-    // Pair button → open pairing modal
-    const pairBtn = card.querySelector('.btn-pair');
-    if (pairBtn) pairBtn.addEventListener('click', e => {
-      e.preventDefault(); e.stopPropagation();
-      openPairings(id, card.querySelector('.card-title').textContent || '#'+id);
-    });
-    // why-chip → chat
-    card.querySelector('.why-chip').addEventListener('click', () => {
-      const why = card.querySelector('.why-chip');
-      if (!why.classList.contains('visible')) return;
-      openChat(id, '_verdict', '"' + why.textContent + '"');
-    });
-  }
-
-  async function saveReview(card) {
-    const id = card.dataset.id;
-    const inputs = card.querySelectorAll('.sliders input[type=range]');
-    const payload = {
-      design: parseInt(inputs[0].value, 10),
-      color:  parseInt(inputs[1].value, 10),
-      style:  parseInt(inputs[2].value, 10),
-      decision: card.dataset.decision || null,
-    };
-    const why = card.querySelector('.why-chip');
-    if (payload.decision) { why.classList.add('visible', 'loading'); why.textContent = 'thinking'; }
-    else                  { why.classList.remove('visible', 'loading'); why.textContent = ''; }
-    try {
-      const r = await fetch('/api/review/' + id, {
-        method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)
-      });
-      const j = await r.json();
-      REVIEWS[id] = j;
-      why.classList.remove('loading');
-      if (j.why) { why.textContent = j.why; why.classList.add('visible'); }
-    } catch (e) {
-      why.textContent = '(error: ' + e.message + ')';
-      why.classList.remove('loading');
-    }
-  }
-
-  async function loadChipsForVisibleCards() {
-    const cards = $$('.design-card.review-card');
-    const ids = cards.map(c => c.dataset.id).filter(id => !CHIPS[id]);
-    await Promise.all(ids.map(async id => {
-      try { const r = await fetch('/api/chips/' + id); const j = await r.json(); CHIPS[id] = j.chips || []; }
-      catch { CHIPS[id] = []; }
-    }));
-    cards.forEach(card => {
-      const id = card.dataset.id;
-      const chipsHTML = (CHIPS[id]||[]).map(c => '<span class="chip" data-chip="' + c.key + '">' + c.label + '</span>').join('');
-      card.querySelector('.chips').innerHTML = chipsHTML;
-      card.querySelectorAll('.chip').forEach(chip => {
-        chip.addEventListener('click', () => openChat(id, chip.dataset.chip, chip.textContent));
-      });
-    });
-  }
-
-  // ── Chat panel ─────────────────────────────────────────────────
-  const modal = $('#chat-modal');
-  const history = $('#chat-history');
-  const input = $('#chat-input');
-  const send  = $('#chat-send');
-  $('#chat-close').addEventListener('click', () => modal.classList.remove('open'));
-  document.addEventListener('keydown', e => { if (e.key === 'Escape' && modal.classList.contains('open')) modal.classList.remove('open'); });
-
-  async function openChat(designId, chipKey, chipLabel) {
-    CURRENT_CHIP = { designId, chipKey, chipLabel };
-    const card = document.querySelector('.design-card.review-card[data-id="' + designId + '"]');
-    const title = card ? (card.querySelector('.card-title').textContent || '#'+designId) : '#'+designId;
-    $('#chat-chip-label').textContent = chipLabel;
-    $('#chat-design-label').textContent = '#' + designId + ' · ' + title.slice(0,60);
-    history.innerHTML = '<div class="msg assistant loading">loading…</div>';
-    modal.classList.add('open');
-    setTimeout(() => input.focus(), 100);
-    try {
-      const r = await fetch('/api/chip/' + designId + '/' + encodeURIComponent(chipKey) + '/chat');
-      const j = await r.json();
-      renderHistory(j.history || []);
-    } catch (e) {
-      history.innerHTML = '<div class="msg assistant">load error: ' + e.message + '</div>';
-    }
-  }
-  function renderHistory(msgs) {
-    history.innerHTML = msgs.length
-      ? msgs.map(m => '<div class="msg ' + m.role + '">' + escapeHTML(m.content) + '</div>').join('')
-      : '<div class="msg assistant" style="opacity:.5">Ask the panel about this chip.</div>';
-    history.scrollTop = history.scrollHeight;
-  }
-  function escapeHTML(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[c])); }
-
-  $('#chat-form').addEventListener('submit', async e => {
-    e.preventDefault();
-    if (!CURRENT_CHIP) return;
-    const message = input.value.trim();
-    if (!message) return;
-    input.value = '';
-    send.disabled = true;
-    const userMsg = document.createElement('div'); userMsg.className = 'msg user'; userMsg.textContent = message;
-    const loading = document.createElement('div'); loading.className = 'msg assistant loading'; loading.textContent = 'thinking…';
-    const first = history.firstElementChild;
-    if (first && first.style && first.style.opacity) history.innerHTML = '';
-    history.appendChild(userMsg); history.appendChild(loading);
-    history.scrollTop = history.scrollHeight;
-    try {
-      const r = await fetch('/api/chip/' + CURRENT_CHIP.designId + '/' + encodeURIComponent(CURRENT_CHIP.chipKey) + '/chat', {
-        method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ message })
-      });
-      const j = await r.json();
-      loading.classList.remove('loading');
-      loading.textContent = j.reply || '(no reply)';
-      document.querySelectorAll('.design-card.review-card[data-id="' + CURRENT_CHIP.designId + '"] .chip[data-chip="' + CURRENT_CHIP.chipKey + '"]').forEach(c => c.classList.add('has-chat'));
-    } catch (e) {
-      loading.classList.remove('loading');
-      loading.textContent = 'error: ' + e.message;
-    }
-    send.disabled = false; input.focus();
-  });
-
-  // ── Pair modal ─────────────────────────────────────────────────
-  const pairModal = $('#pair-modal');
-  const pairBody = $('#pair-body');
-  let CURRENT_PAIR_ID = null;
-  let CURRENT_PAIR_TITLE = '';
-  $('#pair-close').addEventListener('click', () => pairModal.classList.remove('open'));
-  $('#pair-refresh').addEventListener('click', () => { if (CURRENT_PAIR_ID) loadPairings(CURRENT_PAIR_ID, CURRENT_PAIR_TITLE, true); });
-  document.addEventListener('keydown', e => { if (e.key === 'Escape' && pairModal.classList.contains('open')) pairModal.classList.remove('open'); });
-
-  function openPairings(designId, title) { loadPairings(designId, title, false); }
-
-  async function loadPairings(designId, title, forceRefresh) {
-    CURRENT_PAIR_ID = designId; CURRENT_PAIR_TITLE = title;
-    $('#pair-design-title').textContent = 'Pairs well with this design';
-    $('#pair-design-sub').textContent = '#' + designId + ' · ' + title.slice(0,80);
-    $('#pair-vision-read').classList.add('hidden');
-    $('#pair-vision-read').textContent = '';
-    pairBody.innerHTML = '<div class="pair-loading">' + (forceRefresh ? 'regenerating (vision + Ollama, ~10s)…' : 'loading…') + '</div>';
-    pairModal.classList.add('open');
-    try {
-      const url = '/api/pairings/' + designId + (forceRefresh ? '/refresh' : '');
-      const r = await fetch(url, { method: forceRefresh ? 'POST' : 'GET' });
-      const j = await r.json();
-      if (j.vision_description) {
-        const vr = $('#pair-vision-read');
-        vr.textContent = '👁  ' + j.vision_description;
-        vr.classList.remove('hidden');
-      }
-      renderPairings(j);
-    } catch (e) {
-      pairBody.innerHTML = '<div class="pair-empty">error loading: ' + e.message + '</div>';
-    }
-  }
-
-  async function togglePin(designId, suggestion, cardEl) {
-    cardEl.classList.toggle('pinned');
-    try {
-      await fetch('/api/moodboard/' + designId + '/pin', {
-        method: 'POST', headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({ pin_key: suggestion.pin_key, suggestion })
-      });
-    } catch (e) {
-      cardEl.classList.toggle('pinned'); // revert on error
-    }
-  }
-
-  function renderPairings(data) {
-    if (!data.suggestions || !data.suggestions.length) {
-      pairBody.innerHTML = '<div class="pair-empty">No suggestions available.</div>';
-      return;
-    }
-    pairBody.innerHTML = data.suggestions.map(s => {
-      const pinned = s.pinned ? ' pinned' : '';
-      const pinTitle = s.pinned ? 'Unpin from moodboard' : 'Pin to moodboard';
-      const visionBadge = s.vision_grounded ? '<span class="pair-vision-badge" title="Grounded in LLaVA visual read">👁 vision</span>' : '';
-      const pinBtn = '<button class="pair-pin" type="button" data-pin-key="' + escapeAttr(s.pin_key) + '" title="' + pinTitle + '">★</button>';
-      if (s.type === 'design') {
-        const thumb = s.thumb_url
-          ? '<div class="pair-thumb" style="background-image:url('+s.thumb_url+')"></div>'
-          : '<div class="pair-thumb material">◧</div>';
-        return '<div class="pair-card' + pinned + '" data-pin-key="' + escapeAttr(s.pin_key) + '">' +
-               '<a class="pair-link" href="/design/' + s.design_id + '" target="_blank" rel="noopener noreferrer">' + thumb +
-               '<div class="pair-body"><span class="pair-type">design · #' + s.design_id + '</span>' +
-               '<div class="pair-title">' + escapeHTML(s.label) + (s.hex ? '<span class="pair-hexcode">'+s.hex+'</span>' : '') + '</div>' +
-               '<div class="pair-reason">' + escapeHTML(s.reason) + '</div></div></a>' + pinBtn + '</div>';
-      }
-      if (s.type === 'color') {
-        return '<div class="pair-card' + pinned + '" data-pin-key="' + escapeAttr(s.pin_key) + '">' +
-               '<div class="pair-thumb" style="background:' + (s.hex||'#888') + '"></div>' +
-               '<div class="pair-body"><span class="pair-type">color swatch</span>' +
-               '<div class="pair-title">' + escapeHTML(s.label) + '</div>' +
-               '<div class="pair-reason">' + escapeHTML(s.reason) + '</div></div>' + pinBtn + '</div>';
-      }
-      if (s.type === 'paint') {
-        // Paint chip — left half is the chip color block with the brand-style
-        // label tucked underneath. Lets the room be specified end-to-end
-        // (wallcovering + matching wall paint).
-        const brandClass = (s.brand || '').toLowerCase().includes('dunn') ? 'paint-de' : 'paint-sw';
-        return '<div class="pair-card paint-card ' + brandClass + pinned + '" data-pin-key="' + escapeAttr(s.pin_key) + '">' +
-               '<div class="pair-thumb paint-thumb" style="background:' + (s.hex||'#888') + '">' +
-                 '<span class="paint-code">' + escapeHTML(s.code||'') + '</span></div>' +
-               '<div class="pair-body"><span class="pair-type">paint · ' + escapeHTML(s.brand||'') + '</span>' +
-               '<div class="pair-title">' + escapeHTML(s.name||'') + '<span class="pair-hexcode">'+escapeHTML(s.hex||'')+'</span></div>' +
-               '<div class="pair-reason">' + escapeHTML(s.reason||'') + '</div></div>' + pinBtn + '</div>';
-      }
-      // material / object
-      return '<div class="pair-card' + pinned + '" data-pin-key="' + escapeAttr(s.pin_key) + '">' +
-             '<div class="pair-thumb material">' + (s.icon || '✦') + '</div>' +
-             '<div class="pair-body"><span class="pair-type">material' + visionBadge + '</span>' +
-             '<div class="pair-title">' + escapeHTML(s.label) + '</div>' +
-             '<div class="pair-reason">' + escapeHTML(s.reason) + '</div></div>' + pinBtn + '</div>';
-    }).join('') + '<div style="text-align:right; font-size:10px; color:#666; margin-top:6px">generated ' + (data.generated_at ? new Date(data.generated_at).toLocaleString() : '—') + '</div>';
-    // wire pin buttons
-    pairBody.querySelectorAll('.pair-pin').forEach(btn => {
-      btn.addEventListener('click', e => {
-        e.preventDefault(); e.stopPropagation();
-        const pinKey = btn.dataset.pinKey;
-        const suggestion = data.suggestions.find(s => s.pin_key === pinKey);
-        const card = btn.closest('.pair-card');
-        if (suggestion && card) togglePin(CURRENT_PAIR_ID, suggestion, card);
-      });
-    });
-  }
-  function escapeAttr(s) { return String(s||'').replace(/"/g, '&quot;').replace(/</g,'&lt;'); }
-
-  // ── Keyboard shortcuts ─────────────────────────────────────────
-  //   K/R  → keep/reject focused card
-  //   P    → open Pair modal for focused card
-  //   ←/→  → navigate cards left/right
-  //   1-9  → set focused slider to that value (Design when no slider focused; else current slider)
-  //   ?    → show keymap toast
-  let FOCUSED_CARD = null;
-  function setFocused(card) {
-    if (FOCUSED_CARD === card) return;
-    if (FOCUSED_CARD) FOCUSED_CARD.classList.remove('kbd-focus');
-    FOCUSED_CARD = card;
-    if (card) {
-      card.classList.add('kbd-focus');
-      card.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
-    }
-  }
-  // mouse hover sets focus
-  document.addEventListener('mouseover', e => {
-    const c = e.target.closest('.design-card.review-card');
-    if (c) setFocused(c);
-  });
-
-  function showToast(msg, ms = 1800) {
-    let t = document.getElementById('kbd-toast');
-    if (!t) {
-      t = document.createElement('div');
-      t.id = 'kbd-toast';
-      t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);padding:8px 16px;background:#1a1816;border:1px solid #d2b15c;border-radius:3px;color:#d2b15c;font:12px/1.4 -apple-system,system-ui,sans-serif;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,.4);transition:opacity .2s;';
-      document.body.appendChild(t);
-    }
-    t.textContent = msg;
-    t.style.opacity = '1';
-    clearTimeout(t._fade);
-    t._fade = setTimeout(() => { t.style.opacity = '0'; }, ms);
-  }
-
-  document.addEventListener('keydown', e => {
-    // ignore when typing in input/textarea or modals open with input focused
-    if (e.target.matches('input, textarea, select')) return;
-    if (document.querySelector('#chat-modal.open, #pair-modal.open, #iso-modal.open')) return; // modal handles its own keys
-    const cards = Array.from(document.querySelectorAll('.design-card.review-card'));
-    if (!cards.length) return;
-    let idx = FOCUSED_CARD ? cards.indexOf(FOCUSED_CARD) : 0;
-    const key = e.key.toLowerCase();
-    if (key === 'k' && FOCUSED_CARD) {
-      e.preventDefault();
-      FOCUSED_CARD.querySelector('.btn-keep').click();
-      showToast('Keep — ' + (FOCUSED_CARD.querySelector('.card-title').textContent || '').slice(0,40));
-    } else if (key === 'r' && FOCUSED_CARD) {
-      e.preventDefault();
-      FOCUSED_CARD.querySelector('.btn-reject').click();
-      showToast('Reject — ' + (FOCUSED_CARD.querySelector('.card-title').textContent || '').slice(0,40));
-    } else if (key === 'p' && FOCUSED_CARD) {
-      e.preventDefault();
-      FOCUSED_CARD.querySelector('.btn-pair').click();
-    } else if (key === 'arrowright' || key === 'arrowdown') {
-      e.preventDefault();
-      setFocused(cards[Math.min(cards.length - 1, idx + 1)]);
-    } else if (key === 'arrowleft' || key === 'arrowup') {
-      e.preventDefault();
-      setFocused(cards[Math.max(0, idx - 1)]);
-    } else if (/^[1-9]$/.test(e.key) && FOCUSED_CARD) {
-      e.preventDefault();
-      const designSlider = FOCUSED_CARD.querySelector('.sliders input[type=range][data-axis=design]');
-      designSlider.value = e.key;
-      designSlider.dispatchEvent(new Event('input'));
-      designSlider.dispatchEvent(new Event('change'));
-      showToast('Design score = ' + e.key);
-    } else if (key === '?' || (e.shiftKey && key === '/')) {
-      e.preventDefault();
-      showToast('K=keep · R=reject · P=pair · ←→=nav · 1-9=design score · Esc=close', 4000);
-    }
-  });
-  // Add focus ring style
-  const kbdStyle = document.createElement('style');
-  kbdStyle.textContent = '.design-card.review-card.kbd-focus { outline: 2px solid #d2b15c; outline-offset: 2px; }';
-  document.head.appendChild(kbdStyle);
-
-  // ── Isolate modal ─────────────────────────────────────────────
-  // Sibling to pair-modal. Click any image (or press I) → open left drawer
-  // with palette + SW codes; "Isolate" reveals Wallpaper/Paint mode toggle.
-  const isoModal = $('#iso-modal');
-  const isoBody = $('#iso-body');
-  let CURRENT_ISO_ID = null;
-  let CURRENT_ISO_DATA = null;
-  function closeIso() { isoModal.classList.remove('open'); isoModal.setAttribute('aria-hidden', 'true'); }
-  $('#iso-close').addEventListener('click', closeIso);
-  document.addEventListener('keydown', e => { if (e.key === 'Escape' && isoModal.classList.contains('open')) closeIso(); });
-  // Isolate-modal-only shortcuts: W=wallpaper, P=paint, C=copy primary hex, ?=help
-  // Scoped to when modal is open; doesn't conflict with global K/R/P card handler
-  // because that one returns early when any modal is open (line ~1714).
-  document.addEventListener('keydown', e => {
-    if (!isoModal.classList.contains('open')) return;
-    if (e.target.matches('input, textarea, select')) return;
-    const key = e.key.toLowerCase();
-    if (key === 'w' || key === 'p') {
-      const toggle = $('#iso-mode-toggle');
-      if (!toggle.classList.contains('visible')) {
-        showToast('Click "Isolate this design" first');
-        return;
-      }
-      e.preventDefault();
-      const mode = key === 'w' ? 'wallpaper' : 'paint';
-      const btn = toggle.querySelector('button[data-mode="' + mode + '"]');
-      if (btn) { btn.click(); showToast(mode === 'wallpaper' ? 'Wallpaper mode' : 'Paint mode'); }
-    } else if (key === 'c') {
-      e.preventDefault();
-      const hex = CURRENT_ISO_DATA && CURRENT_ISO_DATA.palette && CURRENT_ISO_DATA.palette[0] && CURRENT_ISO_DATA.palette[0].hex;
-      if (!hex) { showToast('No palette yet'); return; }
-      navigator.clipboard.writeText(hex).then(() => showToast('Copied ' + hex)).catch(() => showToast('Copy failed'));
-    } else if (key === '?' || (e.shiftKey && key === '/')) {
-      e.preventDefault();
-      showToast('W=wallpaper · P=paint · C=copy primary hex · Esc=close', 4000);
-    }
-  });
-
-  async function openIsolate(designId, title, imageUrl) {
-    CURRENT_ISO_ID = designId;
-    CURRENT_ISO_DATA = null;
-    $('#iso-design-title').textContent = 'Color isolate';
-    $('#iso-design-sub').textContent = '#' + designId + ' · ' + (title || '').slice(0, 80);
-    const hero = $('#iso-hero');
-    hero.style.backgroundImage = imageUrl ? "url('" + imageUrl + "')" : 'none';
-    $('#iso-palette').innerHTML = '<div class="iso-loading">extracting palette…</div>';
-    $('#iso-dominant').style.display = 'none';
-    $('#iso-dominant-card').innerHTML = '';
-    $('#iso-grid-wallpaper').innerHTML = '<div class="iso-loading">loading…</div>';
-    $('#iso-grid-paint').innerHTML = '';
-    $('#iso-mode-toggle').classList.remove('visible');
-    $('#iso-section-wallpaper').classList.add('visible');
-    $('#iso-section-paint').classList.remove('visible');
-    $('#iso-btn-isolate').disabled = false;
-    $('#iso-btn-isolate').textContent = 'Isolate this design';
-    isoModal.classList.add('open');
-    isoModal.setAttribute('aria-hidden', 'false');
-    try {
-      const r = await fetch('/api/isolate/' + designId);
-      if (!r.ok) {
-        $('#iso-palette').innerHTML = '<div class="iso-loading">failed (status ' + r.status + ')</div>';
-        return;
-      }
-      const data = await r.json();
-      CURRENT_ISO_DATA = data;
-      renderIsoDominant(data.design, data.dominant_sw);
-      renderIsoPalette(data.palette || []);
-    } catch (e) {
-      $('#iso-palette').innerHTML = '<div class="iso-loading">error: ' + escapeHTML(e.message) + '</div>';
-    }
-  }
-
-  // R5 — render the design's STORED dominant hex (canonical, persisted in DB)
-  // alongside its nearest SW. Sits ABOVE the extracted-palette block so the user
-  // sees the source-of-truth color and how it compares to what extractPalette found.
-  function renderIsoDominant(design, dominantSW) {
-    const card = $('#iso-dominant-card');
-    if (!design || !design.dominant_hex) {
-      $('#iso-dominant').style.display = 'none';
-      return;
-    }
-    const hex = design.dominant_hex;
-    const closenessChip = (dominantSW && dominantSW.closeness)
-      ? ' <span class="sw-closeness sw-close-' + dominantSW.closeness.band + '" title="' + escapeAttr(dominantSW.closeness.note) + '">' + dominantSW.closeness.band + '</span>'
-      : '';
-    const swInfo = dominantSW
-      ? '<div class="sw"><strong>' + escapeHTML(dominantSW.code) + '</strong> <span class="sw-name">' + escapeHTML(dominantSW.name) + '</span> · ΔE ' + dominantSW.deltaE + closenessChip + '</div>'
-      : '<div class="nosw">no Sherwin-Williams match available</div>';
-    card.innerHTML =
-      '<div class="dot" style="background:' + escapeAttr(hex) + '"></div>' +
-      '<div class="info">' +
-        '<div class="hex">' + escapeHTML(hex) + '</div>' +
-        swInfo +
-      '</div>';
-    $('#iso-dominant').style.display = 'block';
-  }
-
-  function renderIsoPalette(palette) {
-    if (!palette || !palette.length) {
-      $('#iso-palette').innerHTML = '<div class="iso-loading">no palette extracted</div>';
-      return;
-    }
-    $('#iso-palette').innerHTML = palette.map(p => {
-      const hex = p.hex || '#888';
-      const sw = p.sw;
-      const closenessChip = (sw && sw.closeness)
-        ? '<div class="sw-closeness sw-close-' + sw.closeness.band + '" title="ΔE ' + sw.deltaE + ' — ' + escapeAttr(sw.closeness.note) + '">' + escapeHTML(sw.closeness.band) + '</div>'
-        : '';
-      const swBlock = sw
-        ? '<div class="sw-code">' + escapeHTML(sw.code) + '</div>' +
-          '<div class="sw-name">' + escapeHTML(sw.name) + '</div>' +
-          closenessChip
-        : '<div class="sw-empty">—</div>';
-      return '<div class="iso-sw" title="' + escapeAttr(hex) + (sw ? ' · ' + escapeAttr(sw.code + ' ' + sw.name + ' · ΔE ' + sw.deltaE) : '') + '">' +
-             '<div class="dot" style="background:' + escapeAttr(hex) + '"></div>' +
-             '<div class="hex">' + escapeHTML(hex) + '</div>' +
-             swBlock +
-             '</div>';
-    }).join('');
-  }
-
-  function renderIsoWallpaper(designs) {
-    const grid = $('#iso-grid-wallpaper');
-    if (!designs || !designs.length) {
-      grid.innerHTML = '<div class="iso-loading">no coordinating wallpapers yet</div>';
-      return;
-    }
-    grid.innerHTML = designs.map(d => {
-      const img = (d.image_url || '').replace(/'/g, '');
-      const closenessTxt = d.closeness ? ' (' + d.closeness.band + ')' : '';
-      const t = escapeAttr((d.title || '#' + d.id) + ' · ΔE ' + (d.deltaE != null ? d.deltaE : '?') + closenessTxt);
-      const bandClass = d.closeness ? ' iso-de-' + d.closeness.band : '';
-      return '<a href="/design/' + d.id + '" target="_blank" title="' + t + '" style="background-image:url(' + img + ')" rel="noopener noreferrer">' +
-             '<span class="iso-de' + bandClass + '">ΔE ' + (d.deltaE != null ? d.deltaE.toFixed(1) : '?') + '</span>' +
-             '</a>';
-    }).join('');
-  }
-
-  function renderIsoPaint(coord) {
-    const grid = $('#iso-grid-paint');
-    if (!coord || !Object.keys(coord).length) {
-      grid.innerHTML = '<div class="iso-loading">no coordinating colors</div>';
-      return;
-    }
-    const labelMap = {
-      complement: 'Complement',
-      analogousLeft: 'Analogous −30°',
-      analogousRight: 'Analogous +30°',
-      triadicA: 'Triadic +120°',
-      triadicB: 'Triadic +240°',
-      splitComplementA: 'Split-comp +150°',
-      splitComplementB: 'Split-comp +210°',
-    };
-    grid.innerHTML = Object.entries(coord).map(([role, val]) => {
-      const hex = val.hex || '#888';
-      const sw = val.sw;
-      return '<div class="iso-paint-card">' +
-             '<div class="swatch" style="background:' + escapeAttr(hex) + '"></div>' +
-             '<div class="role">' + escapeHTML(labelMap[role] || role) + '</div>' +
-             '<div class="hex">' + escapeHTML(hex) + '</div>' +
-             (sw
-               ? '<div class="sw-code">' + escapeHTML(sw.code) + '</div>' +
-                 '<div class="sw-name">' + escapeHTML(sw.name) + '</div>'
-               : '<div class="sw-name" style="color:#555;font-style:italic">SW DB not loaded</div>') +
-             '<button type="button" data-copy="' + escapeAttr(hex) + '">copy hex</button>' +
-             '</div>';
-    }).join('');
-    grid.querySelectorAll('button[data-copy]').forEach(b => {
-      b.addEventListener('click', async () => {
-        try { await navigator.clipboard.writeText(b.dataset.copy); b.textContent = 'copied!'; setTimeout(() => b.textContent = 'copy hex', 1200); }
-        catch { b.textContent = 'copy failed'; }
-      });
-    });
-  }
-
-  // Isolate button → reveal mode toggle + render initial wallpaper view
-  $('#iso-btn-isolate').addEventListener('click', () => {
-    if (!CURRENT_ISO_DATA) return;
-    $('#iso-mode-toggle').classList.add('visible');
-    $('#iso-btn-isolate').disabled = true;
-    $('#iso-btn-isolate').textContent = 'Isolated · pick a mode';
-    renderIsoWallpaper(CURRENT_ISO_DATA.coordinating_designs || []);
-    renderIsoPaint(CURRENT_ISO_DATA.coordinating_hexes || {});
-  });
-
-  $('#iso-mode-toggle').addEventListener('click', e => {
-    const btn = e.target.closest('button[data-mode]');
-    if (!btn) return;
-    const mode = btn.dataset.mode;
-    $$('#iso-mode-toggle button').forEach(b => b.classList.toggle('active', b === btn));
-    $('#iso-section-wallpaper').classList.toggle('visible', mode === 'wallpaper');
-    $('#iso-section-paint').classList.toggle('visible', mode === 'paint');
-  });
-
-  // Click any review-card image to open isolate (admin-only, since the API is admin-gated;
-  // non-admin users won't have these cards rendered anyway per /designs ?review path).
-  document.addEventListener('click', e => {
-    const img = e.target.closest('.design-card.review-card .img-wrap, .design-card.review-card img');
-    if (!img) return;
-    const card = img.closest('.design-card.review-card');
-    if (!card) return;
-    // Don't hijack clicks on buttons / decision row / chips
-    if (e.target.closest('button, a, input, select, .chips')) return;
-    e.preventDefault();
-    const id = card.dataset.id;
-    const title = (card.querySelector('.card-title') || {}).textContent || ('#' + id);
-    const imgEl = card.querySelector('img');
-    const url = imgEl ? imgEl.src : '';
-    openIsolate(id, title, url);
-  });
-
-  // Keyboard: I = open isolate for focused card
-  document.addEventListener('keydown', e => {
-    if (e.target.matches('input, textarea, select')) return;
-    if (document.querySelector('#chat-modal.open, #pair-modal.open, #iso-modal.open')) return;
-    if (e.key.toLowerCase() === 'i' && FOCUSED_CARD) {
-      e.preventDefault();
-      const id = FOCUSED_CARD.dataset.id;
-      const title = (FOCUSED_CARD.querySelector('.card-title') || {}).textContent || ('#' + id);
-      const imgEl = FOCUSED_CARD.querySelector('img');
-      openIsolate(id, title, imgEl ? imgEl.src : '');
-    }
-  });
-
-  boot();
-})();
-</script>
-` : ''}
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── DESIGN DETAIL not-found page (shared between bad-id and missing-id branches)
-function renderDesignNotFound(req, idForCanonical) {
-  // Hydrate 4 designs for the recovery row. Match /designs filter exactly —
-  // it surfaces every design with an image_url, regardless of is_published.
-  // Earlier filter on is_published !== false zeroed out the row in prod because
-  // every loaded design currently ships with is_published: false.
-  let pickups = [];
-  try {
-    pickups = DESIGNS
-      .filter(d => d && d.image_url)
-      .slice(0, 200)
-      .sort(() => Math.random() - 0.5)
-      .slice(0, 4);
-  } catch {}
-  const pickupHtml = pickups.map(d => `
-    <a class="nf-pick" href="/design/${d.id}" aria-label="${d.title || 'Design ' + d.id}">
-      <div class="nf-pick-img" style="background-image:url('${d.image_url}')"></div>
-      <div class="nf-pick-meta">
-        <div class="nf-pick-title">${(d.title || 'Untitled').slice(0, 40)}</div>
-        <div class="nf-pick-cat">${d.category || ''}</div>
-      </div>
-    </a>`).join('');
-  const safePath = String(req.params.id || '').replace(/[<>"'&]/g, '').slice(0, 60);
-  // Use the first recovery thumb as the og:image so social shares of a 404 still
-  // render a real wallpaper preview (Canva-lens fix from the debate panel).
-  // Falls back to /og-default.png if no published designs are loaded yet.
-  const ogPick = (pickups[0] && pickups[0].image_url)
-    ? (pickups[0].image_url.startsWith('http') ? pickups[0].image_url : `https://wallco.ai${pickups[0].image_url}`)
-    : '';
-  return `${htmlHead({
-    title: 'Design not found — wallco.ai',
-    description: 'The requested design isn’t in our archive. Browse the live collection.',
-    canonical: `https://wallco.ai/design/${idForCanonical || safePath}`,
-    ogImage: ogPick
-  })}
-<style>
-  .nf {
-    min-height: calc(100vh - 120px);
-    padding: 140px 32px 80px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    background: var(--bg);
-  }
-  .nf-inner {
-    max-width: 720px;
-    text-align: center;
-    margin-bottom: 80px;
-  }
-  .nf-numeral {
-    font-family: var(--serif);
-    font-style: italic;
-    font-weight: 400;
-    font-size: clamp(140px, 22vw, 280px);
-    line-height: 0.85;
-    letter-spacing: -0.06em;
-    color: var(--gold);
-    opacity: 1;
-    margin-bottom: 24px;
-    user-select: none;
-  }
-  .nf-rule {
-    width: 48px;
-    height: 1px;
-    background: var(--ink);
-    margin: 12px auto 28px;
-    opacity: 0.5;
-  }
-  .nf-title {
-    font-family: var(--serif);
-    font-weight: 300;
-    font-size: clamp(28px, 4vw, 44px);
-    line-height: 1.15;
-    letter-spacing: -0.01em;
-    color: var(--ink);
-    margin: 0 0 18px;
-  }
-  .nf-meta {
-    font-family: var(--sans);
-    font-size: 13px;
-    letter-spacing: 0.06em;
-    text-transform: uppercase;
-    color: var(--ink-faint);
-    margin: 0 0 22px;
-  }
-  .nf-path {
-    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
-    text-transform: none;
-    letter-spacing: 0;
-    padding: 2px 0 3px;
-    border-bottom: 1px dotted var(--line);
-    color: var(--ink-soft);
-  }
-  .nf-body {
-    font-family: var(--serif);
-    font-weight: 400;
-    font-size: 18px;
-    line-height: 1.6;
-    color: var(--ink-soft);
-    max-width: 460px;
-    margin: 0 auto 36px;
-  }
-  .nf-cta {
-    display: inline-block;
-    font-family: var(--serif);
-    font-style: italic;
-    font-size: 18px;
-    color: var(--ink);
-    text-decoration: none;
-    padding-bottom: 4px;
-    border-bottom: 1px solid var(--gold);
-    transition: color 0.2s, border-color 0.2s;
-  }
-  .nf-cta:hover { color: var(--gold); border-bottom-color: var(--ink); }
-  .nf-pickup {
-    width: 100%;
-    max-width: 1200px;
-    border-top: 1px solid var(--line);
-    padding-top: 40px;
-  }
-  .nf-pickup-label {
-    font-family: var(--sans);
-    font-size: 12px;
-    letter-spacing: 0.22em;
-    text-transform: uppercase;
-    color: var(--ink-soft);
-    font-weight: 500;
-    text-align: center;
-    margin-bottom: 28px;
-  }
-  .nf-pickup-row {
-    display: grid;
-    grid-template-columns: repeat(4, 1fr);
-    gap: 24px;
-  }
-  .nf-pick {
-    text-decoration: none;
-    color: var(--ink);
-    display: block;
-  }
-  .nf-pick-img {
-    aspect-ratio: 1 / 1;
-    background-size: cover;
-    background-position: center;
-    background-color: var(--card-bg);
-    margin-bottom: 12px;
-    border: 1px solid var(--line);
-    transition: transform 0.4s ease;
-  }
-  .nf-pick:hover .nf-pick-img { transform: scale(1.02); }
-  .nf-pick-title {
-    font-family: var(--serif);
-    font-weight: 400;
-    font-size: 15px;
-    line-height: 1.3;
-    color: var(--ink);
-  }
-  .nf-pick-cat {
-    font-family: var(--sans);
-    font-size: 11px;
-    letter-spacing: 0.1em;
-    text-transform: uppercase;
-    color: var(--ink-faint);
-    margin-top: 4px;
-  }
-  @media (max-width: 720px) {
-    .nf { padding: 100px 20px 60px; }
-    .nf-pickup-row { grid-template-columns: repeat(2, 1fr); gap: 16px; }
-  }
-</style>
-<body>
-${htmlHeader('')}
-<main class="nf">
-  <div class="nf-inner">
-    <div class="nf-numeral">404</div>
-    <div class="nf-rule"></div>
-    <h1 class="nf-title">No design at this address</h1>
-    <p class="nf-meta"><span class="nf-path">/design/${safePath}</span></p>
-    <p class="nf-body">That ID isn’t in the wallco.ai archive—either retired during a recent migration or never minted. Browse the live collection below.</p>
-    <a class="nf-cta" href="/designs">Return to the collection →</a>
-  </div>
-  ${pickups.length ? `<div class="nf-pickup">
-    <div class="nf-pickup-label">Currently in rotation</div>
-    <div class="nf-pickup-row">${pickupHtml}</div>
-  </div>` : ''}
-</main>
-${FOOTER}
-${HAMBURGER_JS}
-${req.query.figcap === '1' ? '<script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>' : ''}
-</body>
-</html>`;
-}
-
-// ── DESIGN DETAIL
-// ── design_ratings: admin scoring + RLHF-lite for the generator
-// One row per (design, version) so we can see how the LLM improved over retries.
-function ensureDesignRatingsTable() {
-  try {
-    psqlQuery(`CREATE TABLE IF NOT EXISTS design_ratings (
-      id BIGSERIAL PRIMARY KEY,
-      design_id BIGINT NOT NULL,
-      color_score INT NOT NULL CHECK (color_score BETWEEN 1 AND 10),
-      style_score INT NOT NULL CHECK (style_score BETWEEN 1 AND 10),
-      composition_score INT NOT NULL CHECK (composition_score BETWEEN 1 AND 10),
-      spacing INT,                  -- 0..100 (50 = neutral)
-      invert BOOLEAN DEFAULT FALSE,
-      notes TEXT,
-      rated_by TEXT DEFAULT 'admin',
-      retry_count INT DEFAULT 0,    -- bumped when this rating triggers a retry
-      child_design_id BIGINT,       -- set when retry produces a new design
-      created_at TIMESTAMP DEFAULT NOW()
-    );`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS design_ratings_design_idx ON design_ratings(design_id, created_at DESC);`);
-  } catch (e) { console.error('[ratings] table init failed:', e.message); }
-}
-ensureDesignRatingsTable();
-
-// ── studio_renders: per-session log of every /studio render (R0c)
-// Steve ask 2026-05-12: "always save every render but give option to delete".
-// One row per compose call, soft-delete via deleted_at (file kept 7 days for undo).
-function ensureStudioRendersTable() {
-  try {
-    psqlQuery(`CREATE TABLE IF NOT EXISTS studio_renders (
-      id BIGSERIAL PRIMARY KEY,
-      session_uuid TEXT NOT NULL,
-      design_id BIGINT,                   -- FK to spoon_all_designs.id
-      room_id BIGINT,                     -- FK to wallco_rooms.id (nullable until R0b lands)
-      prompt TEXT,
-      pattern_hash TEXT,                  -- sha256 of the pattern PNG (dedup key)
-      created_at TIMESTAMP DEFAULT NOW(),
-      deleted_at TIMESTAMP NULL           -- soft-delete; null = live
-    );`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS studio_renders_session_idx
-                 ON studio_renders(session_uuid, created_at DESC);`);
-    psqlQuery(`CREATE INDEX IF NOT EXISTS studio_renders_design_idx
-                 ON studio_renders(design_id);`);
-  } catch (e) { console.error('[studio_renders] init failed:', e.message); }
-}
-ensureStudioRendersTable();
-
-function adminRatingGate(req, res) {
-  const _isAdmin = isAdmin(req);
-  if (!_isAdmin) { res.status(403).json({ error: 'admin only' }); return false; }
-  return true;
-}
-
-// POST /api/design/:id/rate  { color, style, composition, spacing?, invert?, notes? }
-app.post('/api/design/:id/rate', express.json({ limit: '128kb' }), (req, res) => {
-  if (!adminRatingGate(req, res)) return;
-  const id = parseInt(req.params.id, 10);
-  const { color, style, composition, spacing, invert, notes } = req.body || {};
-  const c = Number(color), s = Number(style), m = Number(composition);
-  if (![c, s, m].every(n => Number.isFinite(n) && n >= 1 && n <= 10)) {
-    return res.status(400).json({ error: 'color/style/composition must each be 1..10' });
-  }
-  const sp = Number.isFinite(Number(spacing)) ? Math.max(0, Math.min(100, Number(spacing))) : 50;
-  try {
-    const out = psqlQuery(`INSERT INTO design_ratings
-      (design_id, color_score, style_score, composition_score, spacing, invert, notes)
-      VALUES (${id}, ${c}, ${s}, ${m}, ${sp}, ${invert ? 'TRUE' : 'FALSE'}, ${pgEsc(notes || null)})
-      RETURNING id;`);
-    res.json({ ok: true, rating_id: parseInt(out, 10) });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// GET /api/design/:id/ratings — full history (admin reads override stack)
-app.get('/api/design/:id/ratings', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC),'[]'::json) FROM (
-      SELECT id, color_score, style_score, composition_score, spacing, invert, notes,
-             rated_by, retry_count, child_design_id, created_at
-      FROM design_ratings WHERE design_id = ${id}) t;`);
-    res.json({ ratings: JSON.parse(raw || '[]') });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// Build prompt nudges from the rating — the LLM uses these on retry.
-function nudgesFromRating(r) {
-  const out = [];
-  if (r.color_score <= 4)      out.push('refined palette, tonal harmony, no clashing hues');
-  else if (r.color_score >= 8) out.push('preserve color palette tightly');
-  if (r.style_score <= 4)      out.push('cohesive single-era style statement, commit to one tradition');
-  else if (r.style_score >= 8) out.push('preserve stylistic direction');
-  if (r.composition_score <= 4) out.push('rebalance composition, clearer focal point, deliberate negative space');
-  else if (r.composition_score >= 8) out.push('preserve composition');
-  const sp = Number(r.spacing ?? 50);
-  if (sp <= 35)      out.push('tighter element packing, denser repeat');
-  else if (sp >= 65) out.push('more breathing room between motifs, generous spacing');
-  if (r.invert) out.push('invert dominant and recessive palette roles');
-  return out;
-}
-
-// POST /api/design/:id/retry — kicks a fresh generation using the latest
-// rating as RLHF-lite feedback. The new design lands as a normal row in
-// spoon_all_designs; we link parent→child via design_ratings.child_design_id.
-app.post('/api/design/:id/retry', express.json({ limit: '32kb' }), (req, res) => {
-  if (!adminRatingGate(req, res)) return;
-  const id = parseInt(req.params.id, 10);
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT id, color_score, style_score, composition_score, spacing, invert, notes
-      FROM design_ratings WHERE design_id = ${id} ORDER BY created_at DESC LIMIT 1) t;`);
-    if (!raw) return res.status(400).json({ error: 'rate the design first' });
-    const r = JSON.parse(raw);
-
-    // pull parent's category + (a representative) prompt seed
-    const parentRow = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT category, prompt FROM spoon_all_designs WHERE id = ${id}) t;`);
-    const parent = parentRow ? JSON.parse(parentRow) : {};
-    const baseCategory = parent.category || 'mixed';
-    const nudges = nudgesFromRating(r);
-    const basePrompt = parent.prompt || `${baseCategory} seamless wallpaper repeat`;
-    const finalPrompt = `${basePrompt}. Corrections: ${nudges.join('; ')}.`;
-
-    psqlQuery(`UPDATE design_ratings SET retry_count = retry_count + 1
-               WHERE id = ${r.id};`);
-
-    const { spawn } = require('child_process');
-    const p = spawn('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1',
-      '--kind', 'seamless_tile',
-      '--category', baseCategory,
-      '--prompts', finalPrompt,
-    ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
-         detached: true, stdio: 'ignore' });
-    p.unref();
-
-    res.json({
-      ok: true,
-      retry_started: true,
-      base_category: baseCategory,
-      nudges,
-      final_prompt: finalPrompt,
-      note: 'New design will land in /designs in 30-90s. Refresh after.',
-    });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// POST /api/design/:id/comment — admin saves a free-text review comment on a
-// design (writes to notes col). Used by the review-panel "Save Comment" path.
-// 2026-05-20 — admin review loop per Steve.
-app.post('/api/design/:id/comment', express.json({ limit: '8kb' }), (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const id = parseInt(req.params.id, 10);
-  const comment = String(req.body?.comment || '').trim();
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  if (!comment) return res.status(400).json({ error: 'comment required' });
-  try {
-    const safe = comment.replace(/'/g, "''").slice(0, 4000);
-    psqlExecLocal(`UPDATE spoon_all_designs SET notes = COALESCE(notes,'') || E'\\n[admin ${new Date().toISOString()}] ' || '${safe}' WHERE id=${id};`);
-    res.json({ ok: true, id, saved: true });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// POST /api/design/:id/regenerate-with-comment — take parent design's prompt,
-// append admin comment as additional guidance, spawn a new generation. The
-// new design gets parent_design_id=<this id> so we can trace the lineage.
-// 2026-05-20 — Steve's "comment to make better and run new versions" directive.
-app.post('/api/design/:id/regenerate-with-comment', express.json({ limit: '8kb' }), (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const id = parseInt(req.params.id, 10);
-  const comment = String(req.body?.comment || '').trim();
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  if (!comment) return res.status(400).json({ error: 'comment required' });
-  try {
-    const parentRow = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT category, prompt, width_in, height_in FROM spoon_all_designs WHERE id=${id}) t;`);
-    if (!parentRow) return res.status(404).json({ error: 'parent design not found' });
-    const parent = JSON.parse(parentRow);
-    const baseCategory = parent.category || 'mixed';
-    const w = parent.width_in || 36;
-    const h = parent.height_in || 36;
-    const basePrompt = parent.prompt || `${baseCategory} seamless wallpaper repeat`;
-    // Append the admin comment as ADDITIONAL CORRECTION guidance. Keep the
-    // 2-tone / no-bleed anchor language at the end so it's the last thing the
-    // model reads (later text gets stronger attention weight).
-    const finalPrompt =
-      basePrompt +
-      ` ADMIN CORRECTION (apply this): ${comment}. ` +
-      `Re-emphasize: EXACTLY TWO COLORS, single-layer screen-print only, NO bleed between layers, NO ghost backgrounds, flat stencil silhouettes.`;
-    // Save the admin comment + parent reference on a notes trail
-    const safeComment = comment.replace(/'/g, "''").slice(0, 4000);
-    psqlExecLocal(`UPDATE spoon_all_designs SET notes = COALESCE(notes,'') || E'\\n[admin ${new Date().toISOString()} -> regen] ' || '${safeComment}' WHERE id=${id};`);
-    // Spawn detached generator. parent_design_id is set by a post-insert update
-    // hook we'll fire once the new ID is known — easiest path: poll for the
-    // most recent design with this category created in the next 90s and stamp
-    // it. For now we let the generator land naturally and rely on the prompt
-    // text to encode that this is a regen.
-    const { spawn } = require('child_process');
-    const p = spawn('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1',
-      '--kind', 'seamless_tile',
-      '--width', String(w),
-      '--height', String(h),
-      '--category', baseCategory,
-      '--prompts', finalPrompt,
-    ], { cwd: __dirname, env: { ...process.env }, detached: true, stdio: 'ignore' });
-    p.unref();
-
-    res.json({
-      ok: true,
-      parent_id: id,
-      regen_started: true,
-      base_category: baseCategory,
-      comment,
-      final_prompt: finalPrompt.slice(0, 500) + '...',
-      note: 'New design will land in /designs in 30-90s. Refresh after.',
-    });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// POST /api/design/:id/add-next-panel — admin extends a mural panel set by 1.
-// Reads parent panel's prompt + seed, builds a continuation prompt for the
-// next panel, spawns generation. New panel's parent_design_id = parent id,
-// notes field carries the panel index (panel 2 of M, panel 3 of M, ...).
-// Steve directive 2026-05-20 — multi-panel Monterey murals up to 20 panels.
-app.post('/api/design/:id/add-next-panel', express.json({ limit: '4kb' }), (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  try {
-    // Find the rightmost panel in this set — walk parent_design_id chain
-    // forward by querying for any design where parent_design_id=this-or-ancestor.
-    // Simpler heuristic: count children of root, panel_index = N+1.
-    const rootRow = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT id, parent_design_id, category, prompt, width_in, height_in
-      FROM spoon_all_designs WHERE id=${id}) t;`);
-    if (!rootRow) return res.status(404).json({ error: 'design not found' });
-    const root = JSON.parse(rootRow);
-    // Find the "root" (first panel) — walk up parent_design_id chain
-    let rootId = root.parent_design_id || root.id;
-    let depth = 0;
-    while (depth < 25) {
-      const r = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, parent_design_id FROM spoon_all_designs WHERE id=${rootId}) t;`);
-      if (!r) break;
-      const rr = JSON.parse(r);
-      if (rr.parent_design_id) { rootId = rr.parent_design_id; depth++; } else break;
-    }
-    // Count existing panels in this set (root + all children)
-    const countStr = psqlQuery(`SELECT count(*) FROM spoon_all_designs WHERE id=${rootId} OR parent_design_id=${rootId};`);
-    const existingCount = parseInt(countStr || '1', 10);
-    if (existingCount >= 20) return res.status(400).json({ error: 'panel set already at 20-panel max' });
-    const nextIdx = existingCount + 1;
-
-    const basePrompt = root.prompt || `mural panel`;
-    const finalPrompt =
-      basePrompt +
-      ` PANEL CONTINUATION: This is panel ${nextIdx} of a multi-panel mural set. ` +
-      `Seamlessly continue the scene from panel ${nextIdx - 1} as if hung directly to the right. ` +
-      `The left edge of THIS panel must match the right edge of the previous panel — same horizon line, same ground tone, same figure ink, same scale. ` +
-      `Extend the landscape rightward — new motifs that grow naturally from where the prior panel ended. ` +
-      `Do NOT repeat the prior panel's central composition; show what's to the RIGHT of it in the same scene.`;
-
-    const { spawn } = require('child_process');
-    const p = spawn('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1',
-      '--kind', 'mural_panel',
-      '--width', String(root.width_in || 36),
-      '--height', String(root.height_in || 132),
-      '--category', root.category || 'monterey-mural',
-      '--prompts', finalPrompt,
-    ], { cwd: __dirname, env: { ...process.env }, detached: true, stdio: 'ignore' });
-    p.unref();
-
-    // Stamp a marker so the post-publish step can find the new panel and
-    // wire its parent_design_id. We do this by polling for the most recent
-    // design in this category — simpler than IPC. Caller can poll /api/design/<rootId>/panels
-    // to discover the new panel once it lands.
-    psqlExecLocal(`INSERT INTO design_panel_extensions (root_design_id, panel_index, requested_at, requested_by_ip) VALUES (${rootId}, ${nextIdx}, NOW(), '${req.ip || 'admin'}') ON CONFLICT DO NOTHING;`);
-
-    res.json({
-      ok: true,
-      root_design_id: rootId,
-      next_panel_index: nextIdx,
-      existing_panel_count: existingCount,
-      panels_remaining_quota: 20 - existingCount,
-      note: `New panel ${nextIdx}/20 spawning. Lands in /designs in 30-90s. Refresh after.`,
-    });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// POST /api/design/:id/settlement-violation — HARD delete (URL goes 404),
-// append the prompt+motifs+tags signature to the settlement do-not-want
-// registry, and queue a fresh regen with this violation excluded as a
-// negative prompt. Reserved for content we DO NOT WANT (banana leaves,
-// monstera, tropical-leaf bleed-through, etc.). NOT reversible.
-app.post('/api/design/:id/settlement-violation', express.json({ limit: '16kb' }), (req, res) => {
-  if (!adminRatingGate(req, res)) return;
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  const reason = String(req.body?.reason || '').slice(0, 300).trim();
-  if (!reason) return res.status(400).json({ error: 'reason required' });
-
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, prompt, category,
-      motifs, tags, generator, dominant_hex, local_path, parent_design_id
-      FROM spoon_all_designs WHERE id=${id}) t;`);
-    if (!raw) return res.status(404).json({ error: 'design not found' });
-    const d = JSON.parse(raw);
-
-    // 1) Backup file before delete (forensic only — never republish)
-    const backupDir = path.join(require('os').homedir(), '.wallco-deleted', new Date().toISOString().slice(0,10));
-    try { fs.mkdirSync(backupDir, { recursive: true }); } catch {}
-    let backedUp = null;
-    if (d.local_path && fs.existsSync(d.local_path)) {
-      const bn = `${id}_${path.basename(d.local_path)}`;
-      backedUp = path.join(backupDir, bn);
-      try { fs.copyFileSync(d.local_path, backedUp); } catch {}
-    }
-
-    // 2) Append do-not-want signature to settlement registry
-    const auditDir = path.join(__dirname, 'data', 'settlement-audit');
-    try { fs.mkdirSync(auditDir, { recursive: true }); } catch {}
-    const registryPath = path.join(auditDir, 'do-not-want.jsonl');
-    const violationEntry = {
-      ts: new Date().toISOString(),
-      id, reason,
-      prompt:   d.prompt   || null,
-      category: d.category || null,
-      motifs:   d.motifs   || [],
-      tags:     d.tags     || [],
-      generator: d.generator || null,
-      dominant_hex: d.dominant_hex || null,
-      parent_design_id: d.parent_design_id || null,
-      backup_file: backedUp,
-    };
-    try { fs.appendFileSync(registryPath, JSON.stringify(violationEntry) + '\n', 'utf8'); } catch {}
-
-    // 3) Hard delete: file from disk + DB row (URL becomes 404)
-    if (d.local_path && fs.existsSync(d.local_path)) {
-      try { fs.unlinkSync(d.local_path); } catch {}
-    }
-    psqlQuery(`DELETE FROM spoon_all_designs WHERE id=${id};`);
-    // Drop from in-memory DESIGNS cache so /design/:id 404s immediately
-    const idx = DESIGNS.findIndex(x => x.id === id);
-    if (idx >= 0) DESIGNS.splice(idx, 1);
-
-    // 4) Queue a fresh regen with this violation excluded as a negative prompt
-    let regenStarted = false;
-    let regenErr = null;
-    try {
-      const basePrompt = (d.prompt || `${d.category || 'mixed'} seamless wallpaper repeat`).trim();
-      // Strip any existing "Corrections:" tail so we don't compound
-      const cleanBase = basePrompt.replace(/\.\s*Corrections:[^.]*\.?\s*$/i, '').trim();
-      const negativeAddon =
-        `Corrections: ABSOLUTELY NO ${reason}. Settlement-blocked content for this design family: no banana leaves, no monstera leaves, no palm fronds, no palm leaves, no tropical foliage, no plantain leaves — any plant content must be redrawn as the user's specified motif only.`;
-      const finalPrompt = `${cleanBase}. ${negativeAddon}`;
-      const { spawn } = require('child_process');
-      const p = spawn('node', [
-        path.join(__dirname, 'scripts', 'generate_designs.js'),
-        '--n', '1',
-        '--kind', 'seamless_tile',
-        '--category', d.category || 'mixed',
-        '--prompts', finalPrompt,
-      ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
-           detached: true, stdio: 'ignore' });
-      p.unref();
-      regenStarted = true;
-    } catch (e) { regenErr = e.message; }
-
-    res.json({
-      ok: true,
-      deleted: true,
-      id,
-      url_now_404: true,
-      backed_up_to: backedUp,
-      do_not_want_entry: registryPath,
-      regen_started: regenStarted,
-      regen_error: regenErr,
-      note: 'Design hard-deleted. Future generations will be filtered through the do-not-want registry.',
-    });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-app.get('/design/:id', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) {
-    return res.status(404).type('html').send(renderDesignNotFound(req));
-  }
-  const _isAdmin = isAdmin(req);
-  let design = DESIGNS.find(d => d.id === id);
-  // Hot-path fallback: design was generated after server start (e.g. just baked
-  // from /design/:id/bake). Live-query PG and synthesize a snapshot-matching
-  // entry on the fly so we never have to wait for refresh_designs_snapshot.py.
-  if (!design) {
-    try {
-      const row = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, kind, category,
-        dominant_hex, prompt, seed, local_path, is_published, created_at, notes, room_mockups,
-        user_color_good, user_style_good, user_scale_good, user_stars, user_removed
-        FROM spoon_all_designs WHERE id=${id}) t;`);
-      if (row) {
-        const r = JSON.parse(row);
-        const filename = (r.local_path || '').split('/').pop();
-        design = {
-          id: r.id, kind: r.kind, category: r.category || 'mixed',
-          dominant_hex: r.dominant_hex || '#888888',
-          saturation: hexSaturation(r.dominant_hex || '#888888'),
-          title: titleFor(r.category, r.dominant_hex, r.id),
-          handle: `wallco-${String(r.id).padStart(4,'0')}`,
-          image_url: `/designs/img/${filename}`, filename,
-          generator: 'wallco.ai', seed: r.seed,
-          is_published: !!r.is_published, created_at: r.created_at,
-          notes: r.notes || null,
-          room_mockups: Object.keys(r.room_mockups || {}).sort(),
-          user_color_good: r.user_color_good, user_style_good: r.user_style_good,
-          user_scale_good: r.user_scale_good, user_stars: r.user_stars,
-          user_removed: !!r.user_removed,
-        };
-        DESIGNS.push(design);  // lazy-extend so next request is fast
-      }
-    } catch {}
-  }
-  if (!design) return res.status(404).type('html').send(renderDesignNotFound(req, id));
-
-  // Conditional GET on per-design created_at. Designs rarely mutate after
-  // creation — admin room-mockup generations bump CATALOG_LAST_MODIFIED but
-  // the per-design page itself reflects design.created_at for crawler caching.
-  if (design.created_at && maybe304(req, res, new Date(design.created_at))) return;
-
-  const relIdx = DESIGNS.findIndex(d => d.id === id);
-  const prev = relIdx > 0 ? DESIGNS[relIdx-1] : null;
-  const next = relIdx < DESIGNS.length-1 ? DESIGNS[relIdx+1] : null;
-
-  // Resolve room-mockup contexts (residential / commercial / hospitality).
-  // Each context maps to ONE canonical room from the design's available
-  // mockups so the toggle shows up to 3 pills, never duplicates.
-  const ROOM_CONTEXTS = [
-    { id: 'residential', label: 'Residential', candidates: ['living_room','bedroom','dining_room'] },
-    { id: 'commercial',  label: 'Commercial',  candidates: ['office','boardroom','retail'] },
-    { id: 'hospitality', label: 'Hospitality', candidates: ['hotel_lobby','restaurant','hotel_corridor'] }
-  ];
-  const haveRooms = new Set(design.room_mockups || []);
-  const roomCtx = ROOM_CONTEXTS
-    .map(c => { const room = c.candidates.find(r => haveRooms.has(r)); return room ? { id: c.id, label: c.label, room } : null; })
-    .filter(Boolean);
-
-  // JSON-LD Product — enriched 2026-05-12 with sku/productID/color/material +
-  // additionalProperty (motifs, room previews, seed). Description weaves the
-  // top motifs + dominant hue so Google's snippet has real signal beyond
-  // "AI-generated wallpaper". Reference: schema.org/Product + Google Rich Result spec.
-  const _motifList = (design.motifs || []).slice(0, 4).join(', ');
-  const _hueName = (() => {
-    const h = (design.dominant_hex || '').toLowerCase();
-    if (!/^#[0-9a-f]{6}$/.test(h)) return '';
-    const r=parseInt(h.slice(1,3),16), g=parseInt(h.slice(3,5),16), b=parseInt(h.slice(5,7),16);
-    const max=Math.max(r,g,b), min=Math.min(r,g,b), s=max?(max-min)/max:0;
-    if (s<0.12) return 'neutral';
-    const hh = ((max===r ? (g-b)/(max-min)+(g<b?6:0) : max===g ? (b-r)/(max-min)+2 : (r-g)/(max-min)+4) * 60 + 360) % 360;
-    if (hh<15||hh>=345) return 'rose'; if (hh<40) return 'amber'; if (hh<60) return 'honey';
-    if (hh<90) return 'olive';  if (hh<160) return 'sage';  if (hh<200) return 'marine';
-    if (hh<250) return 'sapphire'; if (hh<290) return 'mauve'; return 'plum';
-  })();
-  const _enrichedDesc =
-    `wallco.ai original ${design.category} wallpaper pattern in ${_hueName} (${design.dominant_hex})` +
-    (_motifList ? `. Motifs: ${_motifList}.` : '.') +
-    ` Pattern No.${design.id}, generated through our proprietary AI design system — every pattern unique, never repeated.` +
-    ((design.room_mockups || []).length > 0
-      ? ` Includes ${design.room_mockups.length} room visualizations (residential, commercial, hospitality).`
-      : '') +
-    ` Free sample available.`;
-  const _addlProps = [
-    { "@type": "PropertyValue", "name": "Category", "value": design.category },
-    _hueName && { "@type": "PropertyValue", "name": "Hue family", "value": _hueName },
-    design.dominant_hex && { "@type": "PropertyValue", "name": "Dominant color", "value": design.dominant_hex },
-    (design.motifs && design.motifs.length) && { "@type": "PropertyValue", "name": "Motifs", "value": design.motifs.join(', ') },
-    (design.room_mockups && design.room_mockups.length) && { "@type": "PropertyValue", "name": "Room previews available", "value": design.room_mockups.length },
-    design.seed && { "@type": "PropertyValue", "name": "Generation seed", "value": String(design.seed) }
-  ].filter(Boolean);
-
-  // BreadcrumbList schema — surfaces "Home › Designs › <category> › <title>"
-  // in Google search results. Pairs with the existing Product schema so the
-  // SERP shows both rich-result card AND breadcrumbs.
-  const breadcrumbSchema = JSON.stringify({
-    "@context": "https://schema.org",
-    "@type": "BreadcrumbList",
-    "itemListElement": [
-      { "@type": "ListItem", "position": 1, "name": "Home",    "item": "https://wallco.ai/" },
-      { "@type": "ListItem", "position": 2, "name": "Designs", "item": "https://wallco.ai/designs" },
-      { "@type": "ListItem", "position": 3, "name": String(design.category || '').replace(/^./, c => c.toUpperCase()), "item": `https://wallco.ai/designs?cat=${encodeURIComponent(design.category || '')}` },
-      { "@type": "ListItem", "position": 4, "name": design.title }
-    ]
-  });
-  const productSchema = JSON.stringify({
-    "@context": "https://schema.org",
-    "@type": "Product",
-    "@id": `https://wallco.ai/design/${design.id}`,
-    "name": design.title,
-    "description": _enrichedDesc,
-    "url": `https://wallco.ai/design/${design.id}`,
-    "image": `https://wallco.ai${design.image_url}`,
-    "sku": design.handle || `wallco-${String(design.id).padStart(4,'0')}`,
-    "productID": String(design.id),
-    "category": design.category,
-    "color": _hueName || undefined,
-    "material": "Wallpaper",
-    "brand": { "@type": "Brand", "name": "wallco.ai" },
-    "manufacturer": { "@type": "Organization", "name": "Designer Wallcoverings", "url": "https://designerwallcoverings.com" },
-    "additionalProperty": _addlProps,
-    "offers": {
-      "@type": "Offer",
-      "availability": "https://schema.org/InStock",
-      "url": `https://wallco.ai/samples?design=${design.id}`,
-      "priceCurrency": "USD",
-      "description": "Free sample available — full roll pricing on request"
-    }
-  });
-  const schema = `${productSchema}</script>\n<script type="application/ld+json">${breadcrumbSchema}`;
-
-  res.type('html').send(`${htmlHead({
-    title: `${design.title} — wallco.ai`,
-    description: `wallco.ai original ${design.category} wallpaper. Pattern No.${design.id} — hand-curated, unique, never repeated. Free sample available.`,
-    canonical: `https://wallco.ai/design/${design.id}`,
-    ogImage: `https://wallco.ai${design.image_url}`,
-    ogType: 'product',
-    ogImageAlt: `${design.title} — ${_hueName} ${design.category} wallpaper pattern${_motifList ? ' featuring ' + _motifList : ''}`,
-    productOG: {
-      availability: 'in stock',
-      retailer_item_id: design.handle || `wallco-${String(design.id).padStart(4,'0')}`,
-      brand: 'wallco.ai',
-      category: design.category
-    },
-    // Preload the detail image — it's THE LCP candidate on /design/:id.
-    // Pairs with fetchpriority=high on the <img> tag so the browser
-    // double-confirms this is the most important image to start fetching.
-    preloadImages: [design.image_url]
-  })}
-<body>
-${htmlHeader('/designs')}
-<main class="detail-main">
-  <div class="detail-layout">
-    <div class="detail-image-col" style="position:relative">
-      <!-- HAMBURGER ICON (top-right of image) opens the INFO + EDIT modal ─── -->
-      <button id="edit-hamburger" class="edit-hamburger" aria-label="Open design info, color &amp; style"
-              aria-haspopup="dialog" aria-expanded="false">
-        <span></span><span></span><span></span>
-      </button>
-
-      <!-- INFO MODAL ───────────────────────────────────────────────────────── -->
-      <div id="edit-modal" class="edit-modal" hidden role="dialog" aria-modal="true" aria-labelledby="edit-modal-title">
-        <div class="edit-modal-backdrop" data-close="1"></div>
-        <div class="edit-modal-sheet">
-          <header class="edit-modal-head">
-            <h2 id="edit-modal-title">${design.title}</h2>
-            <button class="edit-modal-close" data-close="1" aria-label="Close">✕</button>
-          </header>
-          <!-- ── LIVE PREVIEW IMAGE at top — mirrors slider/recolor state ── -->
-          <div class="edit-preview-wrap">
-            <img id="edit-preview-img" class="edit-preview-img" alt="Live preview" src="${design.image_url}" crossorigin="anonymous">
-            <div class="edit-preview-readout" id="edit-preview-readout">Live preview — sliders + recolors update here.</div>
-          </div>
-          <nav class="edit-tabs">
-            <button class="edit-tab active" data-tab="adjust">Adjust</button>
-            <button class="edit-tab"        data-tab="color">Color</button>
-            <button class="edit-tab"        data-tab="info">Info</button>
-            <button class="edit-tab"        data-tab="style">Style</button>
-            <button class="edit-tab"        data-tab="save">Save</button>
-          </nav>
-          <div class="edit-modal-body">
-
-            <!-- INFO ─────────────────────────────────────────────────────── -->
-            <section class="edit-tabpane" data-pane="info">
-              <dl class="info-grid">
-                <dt>Category</dt><dd>${design.category}</dd>
-                <dt>Kind</dt><dd>${design.kind === 'seamless_tile' ? 'Seamless tile (24×24")' : design.kind}</dd>
-                <dt>Pattern No.</dt><dd>${String(design.id).padStart(4,'0')}</dd>
-                <dt>Generator</dt><dd>${design.generator}</dd>
-                <dt>Handle</dt><dd>${design.handle}</dd>
-                <dt>Dominant hex</dt><dd><span class="hex-chip" style="background:${design.dominant_hex}"></span>${design.dominant_hex}</dd>
-              </dl>
-
-              <!-- AI Interior Designer block — admin-only (front-end hides for
-                   non-admins; endpoint is requireAdmin so non-admins 404
-                   anyway). Generates title / pattern / color name / DIG# /
-                   tags via Gemini Vision per /interior-designer skill. -->
-              <div id="ai-designer-block" style="display:none;margin-top:24px;padding:16px;border:1px solid #d4cfc1;border-radius:8px;background:#faf7f1">
-                <div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
-                  <h3 class="pane-h" style="margin:0">AI Interior Designer</h3>
-                  <button id="ai-designer-run" type="button"
-                    style="padding:8px 14px;border:0;border-radius:4px;background:#1f1808;color:#f4ece0;font:600 11px var(--sans,system-ui);letter-spacing:.08em;text-transform:uppercase;cursor:pointer">
-                    Run AI Designer
-                  </button>
-                </div>
-                <p class="pane-readout" style="margin:6px 0 12px">
-                  Reads the image, suggests a designer title, canonical pattern name, named color, DIG number (if unassigned), and tag set. Review → Apply.
-                </p>
-                <div id="ai-designer-result" style="display:none">
-                  <div id="ai-designer-seam-warn" style="display:none;margin-bottom:10px;padding:8px 12px;border-radius:4px;background:#b00020;color:#fff;font:600 12px var(--sans);letter-spacing:.04em">
-                    ⚠ Visible seam detected — image may not tile cleanly. Consider regenerating.
-                  </div>
-                  <dl class="info-grid">
-                    <dt>Title</dt><dd><input id="ai-d-title"    type="text" style="width:100%;padding:6px 8px;border:1px solid #d4cfc1;border-radius:3px;font:13px var(--sans)"></dd>
-                    <dt>Pattern</dt><dd><input id="ai-d-pattern" type="text" style="width:100%;padding:6px 8px;border:1px solid #d4cfc1;border-radius:3px;font:13px var(--sans)"></dd>
-                    <dt>Color</dt><dd><input id="ai-d-color"     type="text" style="width:100%;padding:6px 8px;border:1px solid #d4cfc1;border-radius:3px;font:13px var(--sans)"></dd>
-                    <dt>DIG #</dt><dd><input id="ai-d-dig"       type="text" style="width:100%;padding:6px 8px;border:1px solid #d4cfc1;border-radius:3px;font:13px var(--sans);font-family:'SF Mono',ui-monospace,monospace"></dd>
-                    <dt>Tags</dt><dd><input id="ai-d-tags"       type="text" style="width:100%;padding:6px 8px;border:1px solid #d4cfc1;border-radius:3px;font:13px var(--sans)" placeholder="comma-separated"></dd>
-                  </dl>
-                  <p id="ai-d-notes" class="pane-readout" style="margin:10px 0 12px;font-style:italic"></p>
-                  <button id="ai-designer-apply" type="button"
-                    style="padding:9px 18px;border:0;border-radius:4px;background:#1f1808;color:#f4ece0;font:600 11px var(--sans);letter-spacing:.08em;text-transform:uppercase;cursor:pointer">
-                    Apply &amp; Save
-                  </button>
-                  <span id="ai-designer-status" style="margin-left:12px;font:12px var(--sans);color:#1a3"></span>
-                </div>
-              </div>
-            </section>
-
-            <!-- COLOR ─────────────────────────────────────────────────────── -->
-            <section class="edit-tabpane" data-pane="color">
-              <h3 class="pane-h">Palette · drag dot onto dot to swap</h3>
-              <div id="palette-dots" class="palette-dots">
-                <div class="dots-loading">Extracting colors…</div>
-              </div>
-              <div class="dots-sort-row">
-                <button class="dots-sort active" data-sort="hex">by hex</button>
-                <button class="dots-sort" data-sort="light">light→dark</button>
-                <button class="dots-sort" data-sort="hue">color wheel</button>
-              </div>
-              <h3 class="pane-h" style="margin-top:24px">HSV wheel · pick replacement</h3>
-              <canvas id="color-wheel" width="200" height="200"></canvas>
-              <div id="wheel-readout" class="pane-readout">Click a swatch above first, then a wheel position to recolor it</div>
-              <h3 class="pane-h" style="margin-top:18px">Suggested replacements</h3>
-              <div id="suggested-swatches" class="suggested-swatches"></div>
-            </section>
-
-            <!-- STYLE ─────────────────────────────────────────────────────── -->
-            <section class="edit-tabpane" data-pane="style">
-              <h3 class="pane-h">Style attribution</h3>
-              <p class="pane-text">${String(design.title || 'Untitled').replace(/[<>]/g,'')} reads as a <strong>${design.category || 'mixed'}</strong> design with a dominant ${design.dominant_hex || 'neutral'} tone.</p>
-              <h3 class="pane-h">Where it fits</h3>
-              <ul class="pane-list">
-                <li>Room: ${design.category === 'floral' ? 'bedroom · powder · breakfast nook' : design.category === 'damask' ? 'library · dining · formal' : design.category === 'geometric' ? 'modern lobby · home office · entry' : 'flexible'}</li>
-                <li>Repeat: 24" tile · seamless</li>
-                <li>Trade: paper · vinyl · peel-and-stick · grasscloth on request</li>
-              </ul>
-              <div id="style-extra" class="pane-readout">More from the studio's tagger landing soon.</div>
-            </section>
-
-            <!-- ADJUST ─────────────────────────────────────────────────────── -->
-            <section class="edit-tabpane active" data-pane="adjust">
-              <h3 class="pane-h">Live image adjustments</h3>
-              <label class="adj"><span>Hue</span>       <input type="range" id="adj-hue"  min="-180" max="180" step="1" value="0"><em id="adj-hue-v">0°</em></label>
-              <label class="adj"><span>Saturation</span><input type="range" id="adj-sat"  min="0"   max="200" step="1" value="100"><em id="adj-sat-v">100%</em></label>
-              <label class="adj"><span>Vibrance</span>  <input type="range" id="adj-vib"  min="0"   max="200" step="1" value="100"><em id="adj-vib-v">100%</em></label>
-              <label class="adj"><span>Brightness</span><input type="range" id="adj-bri"  min="0"   max="200" step="1" value="100"><em id="adj-bri-v">100%</em></label>
-              <label class="adj"><span>Contrast</span>  <input type="range" id="adj-con"  min="0"   max="200" step="1" value="100"><em id="adj-con-v">100%</em></label>
-              <label class="adj"><span>Opacity</span>   <input type="range" id="adj-opa"  min="20"  max="100" step="1" value="100"><em id="adj-opa-v">100%</em></label>
-              <label class="adj"><span>Sepia</span>     <input type="range" id="adj-sep"  min="0"   max="100" step="1" value="0"><em id="adj-sep-v">0%</em></label>
-              <label class="adj"><span>Blur</span>      <input type="range" id="adj-blur" min="0"   max="20"  step="1" value="0"><em id="adj-blur-v">0px</em></label>
-              <button id="adj-reset" class="adj-reset">Reset all</button>
-            </section>
-
-            <!-- SAVE ─────────────────────────────────────────────────────── -->
-            <section class="edit-tabpane" data-pane="save">
-              <h3 class="pane-h">Save your edits as a new design</h3>
-              <p class="pane-text">All your color swaps and adjustments will be flattened into a fresh design, watermarked, and indexed in the catalog.</p>
-              <button id="adj-bake" class="adj-bake">Bake edits → save as new design</button>
-            </section>
-
-          </div>
-        </div>
-      </div>
-
-      <style>
-        /* Hamburger button — small, top-right of the design image */
-        .edit-hamburger { position:absolute; top:14px; right:14px; width:42px; height:42px; background:rgba(20,18,15,.85); backdrop-filter:blur(8px); color:#f0eadc; border:1px solid rgba(255,255,255,.1); border-radius:8px; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; padding:0; z-index:18; transition:background .15s; }
-        .edit-hamburger:hover { background:rgba(20,18,15,.95); border-color:#d2b15c; }
-        .edit-hamburger span { display:block; width:18px; height:2px; background:currentColor; border-radius:1px; }
-
-        /* Modal */
-        .edit-modal { position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center; }
-        .edit-modal[hidden] { display:none; }
-        .edit-modal-backdrop { position:absolute; inset:0; background:rgba(10,9,8,.7); backdrop-filter:blur(6px); }
-        .edit-modal-sheet { position:relative; width:min(1100px, 96vw); max-height:94vh; background:#1a1816; color:#e8e2d6; border-radius:12px; border:1px solid #2a2a2a; box-shadow:0 24px 80px rgba(0,0,0,.6); display:flex; flex-direction:column; overflow:hidden; }
-        .edit-modal-head { display:flex; justify-content:space-between; align-items:center; padding:14px 22px; border-bottom:1px solid #2a2a2a; flex-shrink:0; }
-        /* Big live preview image at the top */
-        .edit-preview-wrap { position:relative; padding:14px 22px; background:#0f0e0c; border-bottom:1px solid #2a2a2a; flex-shrink:0; }
-        .edit-preview-img { display:block; width:100%; max-height:48vh; object-fit:contain; border-radius:6px; transition:filter .12s ease, opacity .12s ease; background:#000; }
-        .edit-preview-readout { font:11px var(--mono,monospace); color:#888; margin-top:8px; text-align:center; }
-        .edit-modal-head h2 { margin:0; font-family:'Cormorant Garamond',serif; font-weight:300; font-size:22px; color:#f0eadc; }
-        .edit-modal-close { background:transparent; border:0; color:#888; font-size:22px; cursor:pointer; padding:0 6px; line-height:1; }
-        .edit-modal-close:hover { color:#fff; }
-        .edit-tabs { display:flex; gap:2px; padding:0 14px; border-bottom:1px solid #2a2a2a; background:#15130f; }
-        .edit-tab { background:transparent; border:0; padding:12px 16px; color:#888; cursor:pointer; font-size:12px; text-transform:uppercase; letter-spacing:.08em; border-bottom:2px solid transparent; }
-        .edit-tab:hover { color:#e8e2d6; }
-        .edit-tab.active { color:#d2b15c; border-bottom-color:#d2b15c; }
-        .edit-modal-body { padding:18px 22px 22px; overflow-y:auto; flex:1; min-height:280px; }
-        .edit-tabpane { display:none; }
-        .edit-tabpane.active { display:block; }
-        .pane-h { font-size:11px; text-transform:uppercase; letter-spacing:.1em; color:#888; margin:0 0 10px; font-weight:500; }
-        .pane-text { font-size:13px; color:#bba; line-height:1.55; margin:0 0 14px; }
-        .pane-list { margin:0; padding-left:18px; font-size:13px; color:#bba; line-height:1.7; }
-        .pane-readout { font:11px var(--mono,monospace); color:#888; text-align:center; margin-top:6px; }
-
-        .info-grid { display:grid; grid-template-columns:140px 1fr; gap:8px 16px; margin:0; font-size:13px; }
-        .info-grid dt { color:#888; text-transform:uppercase; font-size:10px; letter-spacing:.08em; align-self:center; }
-        .info-grid dd { margin:0; color:#e8e2d6; }
-        .hex-chip { display:inline-block; width:14px; height:14px; border-radius:50%; margin-right:6px; vertical-align:middle; border:1px solid rgba(255,255,255,.2); }
-
-        .adj { display:grid; grid-template-columns:90px 1fr 50px; align-items:center; gap:10px; margin-bottom:8px; font-size:12px; color:#bba; }
-        .adj input[type=range] { width:100%; }
-        .adj em { font-style:normal; font-size:11px; color:#888; text-align:right; }
-        .adj-reset { background:transparent; border:1px solid #444; color:#aaa; padding:6px 14px; border-radius:4px; cursor:pointer; font-size:12px; margin-top:10px; }
-
-        .palette-dots { display:flex; flex-wrap:wrap; gap:7px; min-height:50px; padding:6px 0 22px; }
-        .palette-dot { width:32px; height:32px; border-radius:50%; cursor:grab; border:2px solid rgba(255,255,255,.15); position:relative; transition:transform .15s; }
-        .palette-dot:hover { transform:scale(1.18); border-color:#d2b15c; }
-        .palette-dot.selected { box-shadow:0 0 0 2px #d2b15c, 0 0 12px rgba(210,177,92,.6); transform:scale(1.12); }
-        .palette-dot.drag-over { box-shadow:0 0 0 2px #fff; }
-        .dot-label { position:absolute; bottom:-20px; left:50%; transform:translateX(-50%); font:10px var(--mono,monospace); color:#aaa; white-space:nowrap; pointer-events:none; opacity:0; transition:opacity .15s; }
-        .palette-dot:hover .dot-label { opacity:1; }
-        .dots-sort-row { display:flex; gap:6px; margin-top:14px; }
-        .dots-sort { flex:1; background:transparent; border:1px solid #333; color:#888; padding:6px 8px; border-radius:4px; cursor:pointer; font-size:11px; }
-        .dots-sort.active { background:#d2b15c; color:#1a1a1a; border-color:#d2b15c; }
-
-        #color-wheel { display:block; margin:8px auto 0; cursor:crosshair; border-radius:50%; }
-        .suggested-swatches { display:grid; grid-template-columns:repeat(5,1fr); gap:6px; }
-        .suggested-swatches > div { aspect-ratio:1; border-radius:4px; cursor:pointer; border:1px solid rgba(255,255,255,.1); }
-        .suggested-swatches > div:hover { border-color:#d2b15c; }
-        .adj-bake { width:100%; padding:14px; background:#d2b15c; color:#1a1a1a; border:0; border-radius:6px; cursor:pointer; font-weight:600; font-size:14px; margin-top:4px; }
-        .adj-bake:hover { background:#e0bd64; }
-        .adj-bake:disabled { opacity:.5; cursor:wait; }
-
-        @media (max-width: 640px) {
-          .edit-modal-sheet { width:96vw; max-height:94vh; }
-          .edit-tab { padding:10px 11px; font-size:11px; }
-        }
-      </style>
-
-      <div class="detail-img-wrap">
-        <img src="${design.image_url}" alt="${design.title}" class="detail-img" id="detail-img" loading="eager" fetchpriority="high" decoding="async" crossorigin="anonymous">
-        <canvas id="detail-canvas" style="display:none"></canvas>
-
-        <!-- ── COLOR STORY — palette dots + Sherwin-Williams + Dunn-Edwards rows
-             Drag any dot onto a pattern editor color slot to recolor it. -->
-        <section id="color-story" data-design-id="${design.id}" data-palette="${(design.dominant_hex || '').replace(/"/g,'&quot;')}"
-                 style="margin:18px 0 6px;padding:14px 16px;background:var(--card-bg,#f4f0e8);border:1px solid var(--line,#d8d0c0);border-radius:8px;">
-          <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
-            <strong style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint,#7a6e5a)">Color story</strong>
-            <span style="font-size:10.5px;color:var(--ink-faint,#7a6e5a);letter-spacing:.04em;">drag any dot onto a pattern color slot to recolor it</span>
-          </div>
-          <div id="cs-dots" class="cs-row" data-vendor="raw"
-               style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;min-height:28px;"></div>
-          <div id="cs-sw" class="cs-row" data-vendor="sw"
-               style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:8px;min-height:24px;"></div>
-          <div id="cs-de" class="cs-row" data-vendor="de"
-               style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:6px;min-height:24px;"></div>
-        </section>
-        <style>
-          .cs-dot { display:inline-flex; align-items:center; gap:6px; cursor:grab; user-select:none; }
-          .cs-dot[draggable=true]:active { cursor:grabbing; }
-          .cs-swatch { width:28px; height:28px; border-radius:50%; border:1px solid rgba(0,0,0,.18); box-shadow: inset 0 0 0 1px rgba(255,255,255,.18); }
-          .cs-row[data-vendor=sw] .cs-swatch, .cs-row[data-vendor=de] .cs-swatch { width:18px; height:18px; border-radius:4px; }
-          .cs-hex { font-family: ui-monospace, Menlo, monospace; font-size:10.5px; color:#5a4628; letter-spacing:.02em; }
-          .cs-name { font-size:11.5px; color:#3a2f1c; }
-          .cs-code { font-family: ui-monospace, Menlo, monospace; font-size:10.5px; color:#a08b6a; margin-left:4px; }
-          .cs-label { font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-faint,#7a6e5a); margin-right:4px; min-width:90px; display:inline-block; }
-        </style>
-        <script>
-        (function(){
-          var sec = document.getElementById('color-story');
-          if (!sec) return;
-          var palette = (sec.getAttribute('data-palette') || '').split(',').map(function(s){return s.trim();}).filter(Boolean);
-          if (!palette.length) { sec.hidden = true; return; }
-
-          function dotHtml(hex, sub) {
-            var swatch = sub && sub.swatch_hex ? sub.swatch_hex : hex;
-            return '<span class="cs-dot" draggable="true" data-hex="' + hex + '" data-paint="' + (sub ? (sub.name + ' ' + sub.code) : '') + '">' +
-                     '<span class="cs-swatch" style="background:' + swatch + '"></span>' +
-                     (sub
-                       ? '<span class="cs-name">' + sub.name + ' <span class="cs-code">' + sub.code + '</span></span>'
-                       : '<span class="cs-hex">' + hex.toUpperCase() + '</span>') +
-                   '</span>';
-          }
-
-          document.getElementById('cs-dots').innerHTML = palette.map(function(h){ return dotHtml(h); }).join('');
-
-          var ep = 'https://pairs.designerwallcoverings.com/api/pairs?palette=' +
-                   encodeURIComponent(palette.join(',')) + '&limit=1';
-          fetch(ep, { credentials: 'omit' })
-            .then(function(r){ return r.ok ? r.json() : Promise.reject(r.status); })
-            .then(function(d){
-              var pal = d && d.source && d.source.palette;
-              if (!pal || !pal.length) return;
-              var swRow = ['<span class="cs-label">Sherwin-Williams</span>'];
-              var deRow = ['<span class="cs-label">Dunn-Edwards</span>'];
-              pal.forEach(function(c){
-                var sw = c && c.paints && c.paints.sw;
-                var de = c && c.paints && c.paints.de;
-                if (sw) swRow.push(dotHtml(c.hex, { name: sw.name, code: sw.code, swatch_hex: sw.hex }));
-                if (de) deRow.push(dotHtml(c.hex, { name: de.name, code: de.code, swatch_hex: de.hex }));
-              });
-              if (swRow.length > 1) document.getElementById('cs-sw').innerHTML = swRow.join('');
-              else document.getElementById('cs-sw').remove();
-              if (deRow.length > 1) document.getElementById('cs-de').innerHTML = deRow.join('');
-              else document.getElementById('cs-de').remove();
-            })
-            .catch(function(){});
-
-          sec.addEventListener('dragstart', function(e){
-            var d = e.target.closest('.cs-dot');
-            if (!d) return;
-            e.dataTransfer.effectAllowed = 'copy';
-            var swatchEl = d.querySelector('.cs-swatch');
-            var swatchBg = swatchEl ? swatchEl.style.background : d.getAttribute('data-hex');
-            function rgbToHex(s){
-              var m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(s||'');
-              if (!m) return s;
-              return '#' + [+m[1],+m[2],+m[3]].map(function(n){return n.toString(16).padStart(2,'0');}).join('');
-            }
-            var hex = (swatchBg && swatchBg.charAt(0) === '#') ? swatchBg : rgbToHex(swatchBg);
-            if (!hex || hex.charAt(0) !== '#') hex = d.getAttribute('data-hex');
-            e.dataTransfer.setData('text/plain', hex);
-            e.dataTransfer.setData('application/x-wallco-color', hex);
-          });
-        })();
-        </script>
-
-        <!-- ── Inspired-by-archival badge (public; NO vendor name reveal) ───
-             Hits /api/design/:id/inspired-by-badge → { inspired: bool }. The
-             admin-only inspired_by_vendor column NEVER appears here — that
-             metadata lives behind /admin/inspirations. -->
-        <div id="inspired-by-badge" data-design-id="${design.id}"
-             style="display:none;margin:14px 0 4px;padding:10px 14px;background:#f7f3eb;border:1px solid #e0d4b8;border-radius:6px;font:12px var(--sans,system-ui);color:#5a4628;letter-spacing:.03em;">
-          <span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#c9a14b;margin-right:8px;vertical-align:middle;"></span>
-          <strong style="font:600 10.5px var(--sans,system-ui);letter-spacing:.18em;text-transform:uppercase;color:#8a6f44;">Inspired by an archival reference</strong>
-          <span style="display:block;margin-top:3px;color:#7a6e5a;font-size:11.5px;">Anchored to authentic designer-wallpaper DNA, reinterpreted as an original wallco composition.</span>
-        </div>
-        <script>
-        (function(){
-          var el = document.getElementById('inspired-by-badge');
-          if (!el) return;
-          fetch('/api/design/' + el.getAttribute('data-design-id') + '/inspired-by-badge', { credentials:'omit' })
-            .then(function(r){ return r.ok ? r.json() : null; })
-            .then(function(d){ if (d && d.inspired === true) el.style.display = 'block'; })
-            .catch(function(){});
-        })();
-        </script>
-
-        ${design.category === 'mural-scenic' ? `
-        <!-- ── LICENSE CTA — $950 infinite license + 150dpi download ─── -->
-        <div id="license-panel" data-design-id="${design.id}"
-             style="margin:20px 0;padding:20px 22px;background:linear-gradient(180deg,#fbf7ef,#f0e7d3);border:1px solid #c8b89a;border-radius:8px;color:#2a1f10">
-          <div style="display:flex;align-items:baseline;justify-content:space-between;gap:14px;flex-wrap:wrap">
-            <div>
-              <p style="font-size:10px;letter-spacing:.28em;text-transform:uppercase;color:#8a6f44;margin:0 0 4px;font-weight:600">License this scenic mural</p>
-              <h3 style="font-family:'Playfair Display',serif;font-weight:400;font-style:italic;margin:0;font-size:22px;color:#1f1808">Infinite-use license · 150 dpi · 20 ft × 11 ft</h3>
-            </div>
-            <div style="font-family:'Playfair Display',serif;font-size:34px;color:#1f1808;line-height:1">$950</div>
-          </div>
-          <p style="margin:10px 0 14px;font-size:13px;line-height:1.55;color:#4a3520">Perpetual unlimited-use license for the full 11 ft × 20 ft panoramic mural at 150 dpi. One-time payment, no royalties. Download the print-resolution file immediately after checkout (Real-ESRGAN-upscaled from the master).</p>
-          <form id="license-form" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
-            <input type="email" id="license-email" placeholder="your@email.com" required autocomplete="email"
-                   style="flex:1;min-width:240px;padding:10px 12px;border:1px solid #c8b89a;border-radius:4px;font:14px var(--sans,system-ui);background:#fff">
-            <button type="submit" id="license-buy"
-                    style="padding:10px 22px;border:0;border-radius:4px;background:#1f1808;color:#f4ece0;font:13px var(--sans,system-ui);letter-spacing:.06em;text-transform:uppercase;cursor:pointer">License &amp; Download</button>
-          </form>
-          <p id="license-msg" style="margin:10px 0 0;font-size:11px;color:#7a5e34;min-height:14px"></p>
-        </div>
-        <script>
-        (function(){
-          var panel = document.getElementById('license-panel');
-          if (!panel) return;
-          var did = panel.dataset.designId;
-          var form = document.getElementById('license-form');
-          var emailInput = document.getElementById('license-email');
-          var msg = document.getElementById('license-msg');
-          var btn = document.getElementById('license-buy');
-
-          // If we just returned from Stripe with ?licensed=1, surface a download CTA
-          var params = new URLSearchParams(location.search);
-          if (params.get('licensed') === '1') {
-            var email = decodeURIComponent(params.get('email')||'');
-            emailInput.value = email;
-            msg.innerHTML = '✓ License granted. <a href="/api/design/'+did+'/download?email='+encodeURIComponent(email)+'" style="color:#1f1808;font-weight:600">Download 150 dpi →</a> <span style="opacity:.6">(first download starts an upscale, ~1 min)</span>';
-            btn.textContent = 'Re-download';
-          } else if (params.get('canceled') === '1') {
-            msg.textContent = 'Checkout canceled. No charge.';
-          } else if (params.get('error')) {
-            msg.textContent = 'Checkout error: ' + params.get('error');
-          }
-
-          form.addEventListener('submit', function(e){
-            e.preventDefault();
-            var email = (emailInput.value||'').trim();
-            if (!email) return;
-            // If they already have a license, skip Stripe — straight to download
-            fetch('/api/design/'+did+'/license/status?email='+encodeURIComponent(email))
-              .then(function(r){return r.json();})
-              .then(function(j){
-                if (j.has_license) {
-                  msg.innerHTML = '✓ You already own this. <a href="/api/design/'+did+'/download?email='+encodeURIComponent(email)+'" style="color:#1f1808;font-weight:600">Download 150 dpi →</a>';
-                  return;
-                }
-                btn.disabled = true; btn.textContent = 'Opening checkout…';
-                fetch('/api/design/'+did+'/license/checkout', {
-                  method:'POST', headers:{'Content-Type':'application/json'},
-                  body: JSON.stringify({ email: email })
-                }).then(function(r){return r.json();})
-                  .then(function(j){
-                    if (j.checkout_url) { location.href = j.checkout_url; }
-                    else { msg.textContent = 'Error: ' + (j.error||'unknown'); btn.disabled=false; btn.textContent='License & Download'; }
-                  }).catch(function(e){
-                    msg.textContent = 'Error: ' + e.message;
-                    btn.disabled = false; btn.textContent='License & Download';
-                  });
-              });
-          });
-        })();
-        </script>
-        ` : ''}
-
-        ${_isAdmin ? `
-        <!-- ── USER RATING PANEL ─────────────────────────────────────────
-             Feeds spoon_all_designs.user_color_good / style_good / scale_good /
-             stars / removed. Admin-only — POST handler is admin-gated, so
-             rendering for non-admins guarantees "save error" on every click. -->
-        <div id="rating-panel" data-design-id="${design.id}"
-             style="margin:18px 0;padding:14px 16px;background:var(--card-bg,#f4f0e8);border:1px solid var(--line,#d8d0c0);border-radius:8px;font:13px var(--sans,system-ui)">
-          <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:center;justify-content:space-between">
-            <strong style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint,#7a6e5a)">Rate this design</strong>
-            <div style="display:flex;gap:8px;flex-wrap:wrap">
-              <button id="rating-remove" type="button"
-                style="background:none;border:1px solid #c84a3a;color:#c84a3a;padding:4px 10px;border-radius:14px;cursor:pointer;font-size:11px;letter-spacing:.06em"
-                title="Soft-remove from public listings — hides everywhere but row stays in DB">Remove design</button>
-              <button id="rating-settlement" type="button"
-                style="background:#b00020;border:1px solid #b00020;color:#fff;padding:4px 10px;border-radius:14px;cursor:pointer;font-size:11px;letter-spacing:.06em;font-weight:600"
-                title="HARD delete: removes from URL (404), logs motifs/prompt to settlement do-not-want registry, kicks a fresh regen with this violation excluded">Settlement Violation</button>
-            </div>
-          </div>
-
-          <!-- ADMIN REVIEW LOOP (2026-05-20) — comment + regenerate-w-comment + delete -->
-          <div id="admin-review-panel" style="margin-top:16px;padding:14px 16px;background:#faf6ec;border:1px solid #e2d7b8;border-radius:8px">
-            <div style="font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:#8a7c66;margin-bottom:8px;font-weight:600">Admin Review · Comment → Regenerate / Save / Delete</div>
-            <textarea id="ar-comment" rows="3"
-              placeholder="What's wrong / what to fix? e.g. 'too much bleed in the background, make the silhouettes harder-edged' or 'make the cacti smaller and add more breathing room'"
-              style="width:100%;box-sizing:border-box;padding:9px 11px;border:1px solid #d8cdb0;border-radius:5px;font:13px/1.4 ui-sans-serif,system-ui;background:#fff;resize:vertical;min-height:64px"></textarea>
-            <div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;align-items:center">
-              <button id="ar-regen" type="button"
-                style="background:#3a8a5a;border:1px solid #2f7048;color:#fff;padding:7px 16px;border-radius:6px;cursor:pointer;font-size:12px;letter-spacing:.06em;font-weight:600"
-                title="Take this design's prompt, append your comment as additional guidance, generate a new design (parent_design_id set to this one)">Regenerate with comment →</button>
-              <button id="ar-save" type="button"
-                style="background:none;border:1px solid #c9a14b;color:#9a7a36;padding:7px 14px;border-radius:6px;cursor:pointer;font-size:12px;letter-spacing:.06em"
-                title="Save your comment to this design's notes for later reference, no regen">Save comment</button>
-              <button id="ar-delete" type="button"
-                style="background:none;border:1px solid #c84a3a;color:#c84a3a;padding:7px 14px;border-radius:6px;cursor:pointer;font-size:12px;letter-spacing:.06em;margin-left:auto"
-                title="Soft-delete this SKU (user_removed=TRUE)">Delete SKU</button>
-            </div>
-            <div id="ar-status" style="font-size:11px;color:#8a7c66;margin-top:8px;min-height:14px"></div>
-          </div>
-          <script>
-          (function(){
-            var did = ${design.id};
-            var commentEl = document.getElementById('ar-comment');
-            var statusEl = document.getElementById('ar-status');
-            function setStatus(msg, isErr) {
-              statusEl.textContent = msg;
-              statusEl.style.color = isErr ? '#c84a3a' : '#3a8a5a';
-            }
-            document.getElementById('ar-regen').addEventListener('click', function(){
-              var c = (commentEl.value || '').trim();
-              if (!c) { setStatus('Add a comment first — what should be different?', true); return; }
-              setStatus('Spawning regen — new design lands in ~30-90s. Refresh /designs after.', false);
-              fetch('/api/design/' + did + '/regenerate-with-comment', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({ comment: c })
-              }).then(function(r){ return r.json(); }).then(function(j){
-                if (j.ok) { setStatus('OK — regen spawned. New design in ~60s.', false); }
-                else { setStatus('Error: ' + (j.error || 'unknown'), true); }
-              }).catch(function(e){ setStatus('Network error: ' + e.message, true); });
-            });
-            document.getElementById('ar-save').addEventListener('click', function(){
-              var c = (commentEl.value || '').trim();
-              if (!c) { setStatus('Empty comment — nothing to save.', true); return; }
-              fetch('/api/design/' + did + '/comment', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({ comment: c })
-              }).then(function(r){ return r.json(); }).then(function(j){
-                if (j.ok) { setStatus('Comment saved to notes.', false); }
-                else { setStatus('Error: ' + (j.error || 'unknown'), true); }
-              }).catch(function(e){ setStatus('Network error: ' + e.message, true); });
-            });
-            document.getElementById('ar-delete').addEventListener('click', function(){
-              if (!confirm('Delete SKU ' + did + '? It will disappear from /designs.')) return;
-              fetch('/api/design/' + did + '/remove', { method: 'POST' })
-                .then(function(r){ return r.json(); }).then(function(j){
-                  if (j.ok) { setStatus('Deleted. Redirecting to /designs...', false); setTimeout(function(){ location.href = '/designs'; }, 800); }
-                  else { setStatus('Error: ' + (j.error || 'unknown'), true); }
-                }).catch(function(e){ setStatus('Network error: ' + e.message, true); });
-            });
-          })();
-          </script>
-          <div style="display:flex;gap:18px;flex-wrap:wrap;margin-top:12px;align-items:center">
-            <div class="rate-axis" data-axis="color_good">
-              <span class="rate-label">Color combo</span>
-              <button class="rate-btn rate-yes" data-val="true">Good</button>
-              <button class="rate-btn rate-no"  data-val="false">No</button>
-            </div>
-            <div class="rate-axis" data-axis="style_good">
-              <span class="rate-label">Style</span>
-              <button class="rate-btn rate-yes" data-val="true">Good</button>
-              <button class="rate-btn rate-no"  data-val="false">No</button>
-            </div>
-            <div class="rate-axis" data-axis="scale_good">
-              <span class="rate-label">Scale</span>
-              <button class="rate-btn rate-yes" data-val="true">Good</button>
-              <button class="rate-btn rate-no"  data-val="false">No</button>
-            </div>
-            <div class="rate-axis" data-axis="stars" style="display:flex;flex-direction:column;gap:4px">
-              <span class="rate-label">Stars</span>
-              <span class="rate-stars" id="rate-stars">
-                ${[1,2,3,4,5].map(n => `<button class="rate-star" data-val="${n}" type="button" title="${n} star${n>1?'s':''}">★</button>`).join('')}
-              </span>
-            </div>
-            <span id="rating-status" style="font-size:11px;color:var(--ink-faint,#888);margin-left:auto"></span>
-          </div>
-        </div>
-        <style>
-          #rating-panel .rate-axis { display:inline-flex; flex-direction:column; gap:5px; }
-          #rating-panel .rate-label { font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-faint,#8a7c66); }
-          #rating-panel .rate-btn { background:transparent; border:1px solid var(--line,#c8bfac); color:var(--ink,#3a2818); padding:3px 10px; border-radius:14px; cursor:pointer; font-size:11px; letter-spacing:.04em; margin-right:4px; }
-          #rating-panel .rate-btn:hover { border-color:var(--ink,#3a2818); }
-          #rating-panel .rate-btn.is-on.rate-yes { background:#3a8a5a; border-color:#3a8a5a; color:#fff; }
-          #rating-panel .rate-btn.is-on.rate-no  { background:#c84a3a; border-color:#c84a3a; color:#fff; }
-          #rating-panel .rate-stars { display:inline-flex; gap:2px; }
-          #rating-panel .rate-star { background:transparent; border:0; color:#c8bfac; font-size:18px; padding:0 2px; cursor:pointer; line-height:1; }
-          #rating-panel .rate-star.is-on { color:#c9a14b; }
-          #rating-panel .rate-star:hover, #rating-panel .rate-star:hover ~ .rate-star { color:#c8bfac; }
-          #rating-panel .rate-star:hover { color:#c9a14b; }
-        </style>
-        <script>
-        (function(){
-          var panel = document.getElementById('rating-panel');
-          if (!panel) return;
-          var did = panel.dataset.designId;
-          var statusEl = document.getElementById('rating-status');
-          // Seed current state from injected design data (rendered server-side)
-          var state = {
-            color_good: ${design.user_color_good === true || design.user_color_good === false ? design.user_color_good : 'null'},
-            style_good: ${design.user_style_good === true || design.user_style_good === false ? design.user_style_good : 'null'},
-            scale_good: ${design.user_scale_good === true || design.user_scale_good === false ? design.user_scale_good : 'null'},
-            stars:      ${Number.isFinite(design.user_stars) ? design.user_stars : 'null'}
-          };
-          function paint() {
-            ['color_good','style_good','scale_good'].forEach(function(axis){
-              panel.querySelectorAll('[data-axis="'+axis+'"] .rate-btn').forEach(function(b){
-                b.classList.toggle('is-on', String(state[axis]) === b.dataset.val);
-              });
-            });
-            panel.querySelectorAll('.rate-star').forEach(function(s){
-              s.classList.toggle('is-on', state.stars != null && parseInt(s.dataset.val,10) <= state.stars);
-            });
-          }
-          paint();
-          function save(patch, doneText) {
-            statusEl.textContent = 'saving…';
-            fetch('/api/design/' + did + '/rate', {
-              method: 'POST', headers: {'Content-Type':'application/json'}, credentials:'same-origin',
-              body: JSON.stringify(patch)
-            }).then(function(r){
-              if (!r.ok) throw new Error('save failed');
-              return r.json();
-            }).then(function(){
-              Object.assign(state, patch);
-              paint();
-              statusEl.textContent = doneText || 'saved';
-              setTimeout(function(){ statusEl.textContent=''; }, 1500);
-            }).catch(function(e){ statusEl.textContent = 'save error'; });
-          }
-          // Yes/No toggle clicks
-          panel.querySelectorAll('.rate-axis[data-axis$="_good"] .rate-btn').forEach(function(btn){
-            btn.addEventListener('click', function(){
-              var axis = btn.closest('.rate-axis').dataset.axis;
-              var clicked = btn.dataset.val === 'true';
-              var current = state[axis];
-              // Toggle off if clicking the already-on choice; otherwise set.
-              var next = (current === clicked) ? null : clicked;
-              var patch = {}; patch[axis.replace('_good','_good')] = next;
-              save({[axis]: next});
-            });
-          });
-          // Stars
-          panel.querySelectorAll('.rate-star').forEach(function(s){
-            s.addEventListener('click', function(){
-              var v = parseInt(s.dataset.val,10);
-              var next = state.stars === v ? null : v;
-              save({ stars: next });
-            });
-          });
-          // Remove
-          document.getElementById('rating-remove').addEventListener('click', function(){
-            if (!confirm('Soft-remove design #'+did+'? It will be hidden from the grid but stays in DB (reversible).')) return;
-            statusEl.textContent = 'removing…';
-            fetch('/api/design/' + did + '/remove', { method: 'POST', credentials:'same-origin' })
-              .then(function(r){ return r.json(); })
-              .then(function(j){
-                statusEl.textContent = 'removed';
-                setTimeout(function(){ location.href = '/designs'; }, 800);
-              }).catch(function(){ statusEl.textContent = 'remove error'; });
-          });
-          // Settlement Violation — HARD delete + log to do-not-want + queue regen.
-          // Only to be used for content we DO NOT WANT (e.g., banana leaves).
-          document.getElementById('rating-settlement').addEventListener('click', function(){
-            var reason = prompt('Settlement Violation reason (e.g. "banana leaves visible", "monstera", "tropical foliage"):');
-            if (!reason || !reason.trim()) return;
-            if (!confirm('HARD delete design #'+did+'?\\nThis will:\\n  • remove the URL (404)\\n  • delete the file from disk\\n  • log the prompt/motifs/tags to the settlement do-not-want registry\\n  • queue a fresh regen excluding this violation\\nThis is NOT reversible.')) return;
-            statusEl.textContent = 'logging violation + regenerating…';
-            var params = new URLSearchParams(window.location.search);
-            var adminToken = params.get('admin') || localStorage.getItem('wallco_admin_token') || '';
-            fetch('/api/design/' + did + '/settlement-violation', {
-              method: 'POST',
-              headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken },
-              credentials:'same-origin',
-              body: JSON.stringify({ reason: reason.trim() })
-            })
-              .then(function(r){ return r.json().then(function(j){ return { ok:r.ok, j:j }; }); })
-              .then(function(res){
-                if (!res.ok) {
-                  statusEl.textContent = 'error: ' + (res.j.error || 'unknown');
-                  return;
-                }
-                statusEl.textContent = 'violation logged · regen queued';
-                setTimeout(function(){ location.href = '/designs'; }, 1200);
-              })
-              .catch(function(e){ statusEl.textContent = 'settlement-violation error: ' + e.message; });
-          });
-        })();
-        </script>
-        ` : ''}
-
-        <div class="scale-preview" style="margin-top:18px">
-          <div class="scale-controls" style="display:flex;align-items:center;gap:14px;margin-bottom:10px;flex-wrap:wrap">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:flex;align-items:center;gap:10px;flex:1;min-width:240px">
-              Pattern scale
-              <input type="range" id="scale-slider" min="4" max="24" step="2" value="24" style="flex:1;max-width:280px">
-            </label>
-            <span id="scale-readout" style="font-family:var(--serif);font-size:18px;color:var(--ink);min-width:90px">24" repeat</span>
-          </div>
-          <!-- Mural-size repeat stepper. Click Next → to walk through 1× (full
-               tile · MURAL MODE with pan + zoom), 2×, 4×, 8× (room-scale),
-               16× (mural-scale) repeat counts.
-               At level 0 (1×1) the viewport switches to MURAL MODE: drag to
-               pan the image, scroll to zoom, double-click to reset.
-               Patterned after grahambrown.com and rebelwalls.com mural tools. -->
-          <div class="mural-stepper" style="display:flex;align-items:center;gap:12px;margin-bottom:10px;flex-wrap:wrap">
-            <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint)">Mural repeat</span>
-            <div id="mural-dots" style="display:flex;gap:6px">
-              <span class="mural-dot active" data-level="0" title="Mural — full tile · 1×1 · drag to position"></span>
-              <span class="mural-dot" data-level="1" title="2×2"></span>
-              <span class="mural-dot" data-level="2" title="4×4"></span>
-              <span class="mural-dot" data-level="3" title="8×8 — room"></span>
-              <span class="mural-dot" data-level="4" title="16×16 — mural"></span>
-            </div>
-            <span id="mural-readout" style="font-family:var(--serif);font-size:14px;color:var(--ink-soft,#555);min-width:200px">Mural · 1×1 — drag to position</span>
-            <button type="button" id="mural-reset" style="padding:6px 10px;border:1px solid var(--line);background:transparent;color:var(--ink-soft);font:10.5px var(--sans);letter-spacing:.06em;text-transform:uppercase;border-radius:4px;cursor:pointer;display:none">Reset</button>
-            <button type="button" id="mural-next" style="padding:6px 12px;border:1px solid var(--line);background:transparent;color:var(--ink);font:11px var(--sans);font-weight:600;letter-spacing:.06em;text-transform:uppercase;border-radius:4px;cursor:pointer;margin-left:auto">Next →</button>
-          </div>
-          <style>
-            .mural-dot { width:8px; height:8px; border-radius:50%; background:#d8d2c4; border:1px solid #b9b3a3; transition: background .15s, transform .15s; cursor:pointer; }
-            .mural-dot.active { background: var(--gold,#c9a14b); border-color: var(--gold,#c9a14b); transform: scale(1.25); }
-            #scale-viewport.mural-mode { cursor: grab; }
-            #scale-viewport.mural-mode.dragging { cursor: grabbing; }
-            #scale-viewport .mural-hint { position:absolute; bottom:8px; left:10px; right:10px; display:flex; justify-content:space-between; align-items:center; font:10px ui-monospace,Menlo,monospace; color:#fff; text-shadow:0 1px 2px rgba(0,0,0,.7); letter-spacing:.04em; pointer-events:none; opacity:.85; }
-            #scale-viewport .mural-hint .zoom-pill { padding:2px 7px; background:rgba(0,0,0,.42); border-radius:999px; pointer-events:auto; }
-          </style>
-          <div id="scale-viewport" style="width:100%;aspect-ratio:3/2;border:1px solid var(--line);border-radius:8px;overflow:hidden;background-image:url('${design.image_url}');background-repeat:repeat;background-size:160px 160px;position:relative;user-select:none;-webkit-user-select:none">
-            <div class="mural-hint" id="mural-hint" style="display:none">
-              <span>Drag to position · scroll to zoom · double-click to reset</span>
-              <span class="zoom-pill" id="mural-zoom-pill">100%</span>
-            </div>
-          </div>
-          <script>
-          (function(){
-            // Mural-size stepper. Cycles 1×→2×→4×→8×→16× repeat counts on the
-            // existing #scale-viewport. Labels: "Mural · 1×1 — drag to position",
-            // "2×2", "4×4", "8×8 · room scale", "16×16 · mural". Persists last
-            // level in localStorage under "wallco.detail.mural-level".
-            //
-            // At LEVEL 0 the viewport enters MURAL MODE — the design fills the
-            // whole wall as ONE panel and the user can drag/zoom to pick the
-            // exact crop. This matches the UX on grahambrown.com murals and
-            // rebelwalls.com — the user, not the algorithm, decides what part
-            // of the image lands on their wall.
-            const LEVELS = [
-              { count: 1,  label: 'Mural · 1×1 — drag to position' },
-              { count: 2,  label: '2×2' },
-              { count: 4,  label: '4×4' },
-              { count: 8,  label: '8×8 · room scale' },
-              { count: 16, label: '16×16 · mural' },
-            ];
-            const vp     = document.getElementById('scale-viewport');
-            const dots   = document.querySelectorAll('#mural-dots .mural-dot');
-            const read   = document.getElementById('mural-readout');
-            const btn    = document.getElementById('mural-next');
-            const resetBtn = document.getElementById('mural-reset');
-            const hint   = document.getElementById('mural-hint');
-            const zoomPill = document.getElementById('mural-zoom-pill');
-            const SLIDER = document.getElementById('scale-slider');
-            const DESIGN_ID = ${JSON.stringify(design.id || '')};
-            const LS_LEVEL = 'wallco.detail.mural-level';
-            const LS_CROP  = 'wallco.detail.mural-crop.' + DESIGN_ID;
-            let level = 0;
-            // mural crop state — per-design
-            let crop = { zoom: 1, px: 50, py: 50 }; // zoom 1 = "cover the viewport once"; px/py % position
-            try { const stored = localStorage.getItem(LS_LEVEL); if (stored!=null) level = Math.min(Math.max(parseInt(stored,10)||0, 0), LEVELS.length-1); } catch {}
-            try { const saved = JSON.parse(localStorage.getItem(LS_CROP) || 'null'); if (saved && typeof saved.zoom === 'number') crop = Object.assign(crop, saved); } catch {}
-            function persistCrop() { try { localStorage.setItem(LS_CROP, JSON.stringify(crop)); } catch {} }
-            function applyCrop() {
-              const sizePct = (100 * crop.zoom).toFixed(1) + '%';
-              vp.style.backgroundSize = sizePct + ' ' + sizePct;
-              vp.style.backgroundRepeat = 'no-repeat';
-              vp.style.backgroundPosition = crop.px.toFixed(2) + '% ' + crop.py.toFixed(2) + '%';
-              if (zoomPill) zoomPill.textContent = Math.round(crop.zoom * 100) + '%';
-            }
-            function apply() {
-              const { count, label } = LEVELS[level];
-              dots.forEach((d,i) => d.classList.toggle('active', i===level));
-              if (read) read.textContent = label;
-              try { localStorage.setItem(LS_LEVEL, String(level)); } catch {}
-              if (level === 0) {
-                vp.classList.add('mural-mode');
-                if (hint) hint.style.display = 'flex';
-                if (resetBtn) resetBtn.style.display = '';
-                applyCrop();
-              } else {
-                vp.classList.remove('mural-mode');
-                if (hint) hint.style.display = 'none';
-                if (resetBtn) resetBtn.style.display = 'none';
-                vp.style.backgroundRepeat = 'repeat';
-                vp.style.backgroundSize = (100/count).toFixed(2) + '% ' + (100/count).toFixed(2) + '%';
-                vp.style.backgroundPosition = '0% 0%';
-              }
-            }
-            if (btn) btn.addEventListener('click', () => { level = (level + 1) % LEVELS.length; apply(); });
-            dots.forEach(d => d.addEventListener('click', (e) => { level = parseInt(e.currentTarget.dataset.level,10) || 0; apply(); }));
-            // The legacy "Pattern scale" slider keeps working at levels 1-4.
-            // At level 0 (mural mode) the slider hands off to drag/zoom.
-            if (SLIDER) SLIDER.addEventListener('input', () => {
-              if (level === 0) return; // mural mode owns the viewport
-              level = 0;
-              dots.forEach(d => d.classList.remove('active'));
-              dots[0] && dots[0].classList.add('active');
-              if (read) read.textContent = LEVELS[0].label;
-              apply();
-            });
-
-            // ── Mural mode interactions: drag-to-pan, wheel-to-zoom, dblclick reset.
-            let drag = null;
-            vp.addEventListener('mousedown', (e) => {
-              if (level !== 0) return;
-              drag = { x: e.clientX, y: e.clientY, px0: crop.px, py0: crop.py };
-              vp.classList.add('dragging');
-              e.preventDefault();
-            });
-            window.addEventListener('mousemove', (e) => {
-              if (!drag || level !== 0) return;
-              const rect = vp.getBoundingClientRect();
-              const dx = (e.clientX - drag.x) / rect.width * 100;
-              const dy = (e.clientY - drag.y) / rect.height * 100;
-              // Drag right → reveal left side of image (background-position % goes down)
-              crop.px = Math.max(0, Math.min(100, drag.px0 - dx));
-              crop.py = Math.max(0, Math.min(100, drag.py0 - dy));
-              applyCrop();
-            });
-            window.addEventListener('mouseup', () => {
-              if (drag) { drag = null; vp.classList.remove('dragging'); persistCrop(); }
-            });
-            // touch support
-            vp.addEventListener('touchstart', (e) => {
-              if (level !== 0 || e.touches.length !== 1) return;
-              drag = { x: e.touches[0].clientX, y: e.touches[0].clientY, px0: crop.px, py0: crop.py };
-              vp.classList.add('dragging');
-            }, { passive: true });
-            vp.addEventListener('touchmove', (e) => {
-              if (!drag || level !== 0 || e.touches.length !== 1) return;
-              const rect = vp.getBoundingClientRect();
-              const dx = (e.touches[0].clientX - drag.x) / rect.width * 100;
-              const dy = (e.touches[0].clientY - drag.y) / rect.height * 100;
-              crop.px = Math.max(0, Math.min(100, drag.px0 - dx));
-              crop.py = Math.max(0, Math.min(100, drag.py0 - dy));
-              applyCrop();
-              e.preventDefault();
-            }, { passive: false });
-            vp.addEventListener('touchend', () => { if (drag) { drag = null; vp.classList.remove('dragging'); persistCrop(); } });
-            // wheel-to-zoom (only in mural mode, only when modifier or hover — avoid stealing page scroll)
-            vp.addEventListener('wheel', (e) => {
-              if (level !== 0) return;
-              e.preventDefault();
-              const dz = e.deltaY < 0 ? 1.08 : 1/1.08;
-              crop.zoom = Math.max(0.5, Math.min(6, crop.zoom * dz));
-              applyCrop();
-              persistCrop();
-            }, { passive: false });
-            // double-click → reset
-            vp.addEventListener('dblclick', (e) => {
-              if (level !== 0) return;
-              crop = { zoom: 1, px: 50, py: 50 };
-              applyCrop();
-              persistCrop();
-            });
-            if (resetBtn) resetBtn.addEventListener('click', () => {
-              crop = { zoom: 1, px: 50, py: 50 };
-              applyCrop();
-              persistCrop();
-            });
-            apply();
-          })();
-          </script>
-          <div style="display:flex;justify-content:space-between;font:10.5px var(--sans);color:var(--ink-faint);margin-top:6px;letter-spacing:.04em">
-            <span>← Smaller repeat (denser pattern)</span>
-            <span id="scale-width-hint">24" wide, sold per yard</span>
-            <span>Larger repeat →</span>
-          </div>
-
-          <!-- Roll-width selector (24 / 36 / 52) — mirrors the right-column tabs; both sync via localStorage. -->
-          <div class="width-slider-row" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Roll width</label>
-            <div class="width-tabs" id="width-tabs-below-scale" data-native-w="${design.width_in || ''}" style="display:flex;gap:8px">
-              <button type="button" class="w-tab" data-w="24" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">24" residential</button>
-              <button type="button" class="w-tab" data-w="36" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">36" wide-roll</button>
-              <button type="button" class="w-tab" data-w="52" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">52" commercial</button>
-            </div>
-            <div style="font:10.5px var(--sans);color:var(--ink-faint);margin-top:6px;letter-spacing:.04em">
-              Pick your roll width — all three are available; the request form below will lock in your choice.
-            </div>
-          </div>
-
-          ${_isAdmin ? `
-          <!-- Admin: tone-on-tone color-wheel recoloring. Per design click,
-               spins up a Gemini image-edit in the target hue. Steve's
-               2026-05-13 ask: "Add buttons to admin to create in color
-               wheel color. for tone on tone items." -->
-          <div class="recolor-wheel" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Admin · tone-on-tone color wheel</label>
-            <p style="font:11px var(--sans);color:var(--ink-faint);margin:0 0 10px">Click a hue to generate a tone-on-tone variant. Each takes ~5-15s &amp; costs ~$0.001 (gemini-image-edit).</p>
-            <div id="recolor-buttons" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">
-              <button data-hue="red"      style="padding:6px 14px;background:#dc2626;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Red</button>
-              <button data-hue="orange"   style="padding:6px 14px;background:#ea580c;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Orange</button>
-              <button data-hue="yellow"   style="padding:6px 14px;background:#ca8a04;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Yellow</button>
-              <button data-hue="lime"     style="padding:6px 14px;background:#65a30d;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Lime</button>
-              <button data-hue="green"    style="padding:6px 14px;background:#16a34a;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Green</button>
-              <button data-hue="teal"     style="padding:6px 14px;background:#0d9488;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Teal</button>
-              <button data-hue="cyan"     style="padding:6px 14px;background:#0891b2;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Cyan</button>
-              <button data-hue="blue"     style="padding:6px 14px;background:#2563eb;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Blue</button>
-              <button data-hue="indigo"   style="padding:6px 14px;background:#4338ca;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Indigo</button>
-              <button data-hue="violet"   style="padding:6px 14px;background:#7c3aed;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Violet</button>
-              <button data-hue="magenta"  style="padding:6px 14px;background:#c026d3;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Magenta</button>
-              <button data-hue="pink"     style="padding:6px 14px;background:#db2777;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Pink</button>
-              <button data-hue="neutral"  style="padding:6px 14px;background:#a16207;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Neutral</button>
-              <button data-hue="charcoal" style="padding:6px 14px;background:#262626;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Charcoal</button>
-              <button id="recolor-all" style="padding:6px 14px;background:linear-gradient(90deg,#dc2626,#ea580c,#ca8a04,#16a34a,#0891b2,#2563eb,#7c3aed,#db2777);color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em;flex-basis:100%;margin-top:4px">✦ Spin all 14 hues (parallel)</button>
-            </div>
-            <div id="recolor-status" style="font:12px var(--sans);color:var(--ink-soft,#555);min-height:18px"></div>
-            <div id="recolor-results" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-top:10px"></div>
-          </div>
-          <script>
-          (function(){
-            const stat = document.getElementById('recolor-status');
-            const results = document.getElementById('recolor-results');
-            const fireOne = async (hue, btn) => {
-              if (btn) { btn.disabled = true; btn.style.opacity = '0.5'; }
-              const startTxt = '→ ' + hue + '…';
-              stat.textContent = (stat.textContent ? stat.textContent + '  ' : '') + startTxt;
-              try {
-                const r = await fetch('/api/design/${design.id}/recolor', {
-                  method: 'POST', headers: { 'Content-Type': 'application/json' },
-                  body: JSON.stringify({ hue }),
-                });
-                const j = await r.json();
-                if (!j.ok) throw new Error(j.error || 'failed');
-                const card = document.createElement('div');
-                card.innerHTML = '<a href="/design/' + j.new_id + '" target="_blank" style="display:block;text-decoration:none;color:inherit">'
-                  + '<img src="' + j.new_image_url + '" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:6px;border:1px solid var(--line)">'
-                  + '<div style="font:10px var(--sans);text-align:center;margin-top:4px;color:var(--ink-soft,#555)">' + hue + ' · #' + j.new_id + '</div>'
-                  + '</a>';
-                results.appendChild(card);
-                stat.textContent = stat.textContent.replace(startTxt, '✓ ' + hue);
-              } catch (e) {
-                stat.textContent = stat.textContent.replace(startTxt, '✗ ' + hue + ':' + e.message);
-              } finally {
-                if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
-              }
-            };
-            document.querySelectorAll('#recolor-buttons button[data-hue]').forEach(b => {
-              b.addEventListener('click', () => fireOne(b.dataset.hue, b));
-            });
-            const allBtn = document.getElementById('recolor-all');
-            if (allBtn) allBtn.addEventListener('click', async () => {
-              allBtn.disabled = true; allBtn.style.opacity = '0.5';
-              const hues = Array.from(document.querySelectorAll('#recolor-buttons button[data-hue]')).map(b => b.dataset.hue);
-              stat.textContent = 'firing ' + hues.length + ' in parallel…';
-              await Promise.all(hues.map(h => fireOne(h, null)));
-              allBtn.disabled = false; allBtn.style.opacity = '1';
-            });
-          })();
-          </script>
-          ` : ''}
-
-          <!-- Share this design — Pinterest (one-click Pin) + Instagram
-               (download image + open IG). Per Steve's 2026-05-13 ask. -->
-          <div class="share-block" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Share this design</label>
-            <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
-              <button type="button" id="share-pinterest" style="padding:10px 16px;background:#e60023;color:#fff;border:0;border-radius:6px;font:600 12px var(--sans);letter-spacing:.06em;text-transform:uppercase;cursor:pointer;display:inline-flex;align-items:center;gap:6px">
-                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.4 0 0 5.4 0 12c0 5.1 3.1 9.4 7.6 11.2-.1-.9-.2-2.4 0-3.4.2-.9 1.4-5.8 1.4-5.8s-.4-.7-.4-1.8c0-1.7 1-2.9 2.2-2.9 1 0 1.5.8 1.5 1.7 0 1-.7 2.6-1 4-.3 1.2.6 2.1 1.7 2.1 2.1 0 3.7-2.2 3.7-5.4 0-2.8-2-4.8-4.9-4.8-3.3 0-5.3 2.5-5.3 5.1 0 1 .4 2.1.9 2.7.1.1.1.2.1.3l-.3 1.4c-.1.2-.2.3-.4.2-1.5-.7-2.4-2.9-2.4-4.6 0-3.7 2.7-7.1 7.8-7.1 4.1 0 7.3 2.9 7.3 6.8 0 4.1-2.6 7.4-6.2 7.4-1.2 0-2.3-.6-2.7-1.4l-.7 2.8c-.3 1-1 2.3-1.5 3.1 1.1.3 2.3.5 3.5.5 6.6 0 12-5.4 12-12C24 5.4 18.6 0 12 0z"/></svg>
-                Pin it
-              </button>
-              <button type="button" id="share-instagram" style="padding:10px 16px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888);color:#fff;border:0;border-radius:6px;font:600 12px var(--sans);letter-spacing:.06em;text-transform:uppercase;cursor:pointer;display:inline-flex;align-items:center;gap:6px">
-                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.16c3.2 0 3.58 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.64.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38a3.7 3.7 0 0 1-1.38.9c-.42.16-1.06.36-2.23.41-1.27.06-1.64.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.16 15.58 2.16 15.2 2.16 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.16 8.8 2.16 12 2.16M12 0C8.74 0 8.33 0 7.05.07 5.78.13 4.9.33 4.14.63a5.85 5.85 0 0 0-2.13 1.38A5.85 5.85 0 0 0 .63 4.14C.33 4.9.13 5.78.07 7.05.01 8.33 0 8.74 0 12c0 3.26.01 3.67.07 4.95.06 1.27.26 2.15.56 2.91.31.79.73 1.46 1.38 2.13a5.85 5.85 0 0 0 2.13 1.38c.76.3 1.64.5 2.91.56C8.33 23.99 8.74 24 12 24c3.26 0 3.67-.01 4.95-.07 1.27-.06 2.15-.26 2.91-.56a5.85 5.85 0 0 0 2.13-1.38 5.85 5.85 0 0 0 1.38-2.13c.3-.76.5-1.64.56-2.91.06-1.28.07-1.69.07-4.95s-.01-3.67-.07-4.95c-.06-1.27-.26-2.15-.56-2.91a5.85 5.85 0 0 0-1.38-2.13A5.85 5.85 0 0 0 19.86.63C19.1.33 18.22.13 16.95.07 15.67.01 15.26 0 12 0zm0 5.84A6.16 6.16 0 1 0 18.16 12 6.16 6.16 0 0 0 12 5.84M12 16a4 4 0 1 1 4-4 4 4 0 0 1-4 4m6.41-11.85a1.44 1.44 0 1 0 1.44 1.44 1.44 1.44 0 0 0-1.44-1.44"/></svg>
-                Share to Instagram
-              </button>
-              <button type="button" id="share-copy-link" style="padding:10px 14px;background:#fff;color:var(--ink);border:1px solid var(--line);border-radius:6px;font:600 12px var(--sans);letter-spacing:.06em;text-transform:uppercase;cursor:pointer">
-                🔗 Copy link
-              </button>
-              <span id="share-status" class="muted" style="font:11px var(--sans);color:var(--ink-faint);"></span>
-            </div>
-            <div style="font:10.5px var(--sans);color:var(--ink-faint);margin-top:6px;letter-spacing:.04em">
-              Pin opens Pinterest's pin-it window. Instagram downloads the design image — open IG, tap +, choose the saved image.
-            </div>
-          </div>
-          <script>
-          (function(){
-            const pageUrl = window.location.href;
-            const imgUrl = ${JSON.stringify('https://wallco.ai' + design.image_url)};
-            const title  = ${JSON.stringify((design.title || 'wallco.ai design') + ' — wallco.ai original wallpaper')};
-            const handle = ${JSON.stringify(design.handle || ('design-' + design.id))};
-            const stat   = document.getElementById('share-status');
-
-            // Pinterest — opens the pin-create window in a small popup.
-            // Pinterest fetches the image directly from imgUrl + back-links
-            // to pageUrl.
-            const pinBtn = document.getElementById('share-pinterest');
-            if (pinBtn) pinBtn.addEventListener('click', () => {
-              const u = 'https://pinterest.com/pin/create/button/?url='
-                + encodeURIComponent(pageUrl)
-                + '&media=' + encodeURIComponent(imgUrl)
-                + '&description=' + encodeURIComponent(title);
-              window.open(u, 'pinit', 'width=750,height=620,scrollbars=yes');
-            });
-
-            // Instagram — IG has no public share URL. Best UX on desktop:
-            // download the image, then prompt user to upload it in IG.
-            // On mobile, attempt the instagram://library deep link first;
-            // fall back to download.
-            const igBtn = document.getElementById('share-instagram');
-            if (igBtn) igBtn.addEventListener('click', async () => {
-              stat.textContent = 'preparing image…';
-              try {
-                const r = await fetch(imgUrl);
-                const blob = await r.blob();
-                const a = document.createElement('a');
-                a.href = URL.createObjectURL(blob);
-                a.download = handle + '.png';
-                document.body.appendChild(a); a.click(); a.remove();
-                setTimeout(() => URL.revokeObjectURL(a.href), 1000);
-                const isMobile = /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);
-                if (isMobile) {
-                  stat.innerHTML = '✓ Saved — <a href="instagram://library" style="color:var(--accent)">open Instagram</a>';
-                } else {
-                  stat.innerHTML = '✓ Saved <strong>' + handle + '.png</strong> — open Instagram on your phone, tap +, choose the image.';
-                }
-              } catch (e) {
-                stat.innerHTML = '<span style="color:#b91c1c">Download failed: ' + (e.message || String(e)) + '</span>';
-              }
-            });
-
-            // Copy link to clipboard
-            const copyBtn = document.getElementById('share-copy-link');
-            if (copyBtn) copyBtn.addEventListener('click', async () => {
-              try {
-                await navigator.clipboard.writeText(pageUrl);
-                const t = copyBtn.textContent;
-                copyBtn.textContent = '✓ Copied';
-                setTimeout(() => { copyBtn.textContent = t; }, 1500);
-              } catch { stat.textContent = 'copy failed'; }
-            });
-          })();
-          </script>
-
-          <!-- Furnish this design — coordinated furniture + lighting + decor
-               recommendations scoped to residential / commercial / hospitality /
-               healthcare. POSTs to /api/design/:id/furnish. -->
-          <div class="furnish-block" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Furnish this design</label>
-            <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px">
-              <select id="furnish-context" style="padding:9px 12px;border:1px solid var(--line);background:white;color:var(--ink);font:13px var(--sans);border-radius:6px;flex:1;min-width:180px">
-                <option value="residential">Residential — home</option>
-                <option value="commercial">Commercial — office / retail</option>
-                <option value="hospitality">Hospitality — hotel / restaurant</option>
-                <option value="healthcare">Healthcare — clinic / waiting room</option>
-              </select>
-              <button type="button" id="furnish-btn" style="padding:10px 18px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border:0;border-radius:6px;font:600 12px var(--sans);letter-spacing:.06em;text-transform:uppercase;cursor:pointer">✦ Generate furnishings</button>
-            </div>
-            <div id="furnish-output" style="display:none;background:#fafaf6;border:1px solid var(--line);border-radius:8px;padding:14px;font:13px var(--sans);color:var(--ink)"></div>
-            <div style="font:10.5px var(--sans);color:var(--ink-faint);margin-top:6px;letter-spacing:.04em">
-              Pulls coordinating furniture, lighting, art, and accessories from this pattern's palette and motif.
-            </div>
-          </div>
-          <script>
-          (function(){
-            const btn = document.getElementById('furnish-btn');
-            const out = document.getElementById('furnish-output');
-            const ctxSel = document.getElementById('furnish-context');
-            if (!btn || !out || !ctxSel) return;
-            btn.addEventListener('click', async () => {
-              out.style.display = 'block';
-              out.innerHTML = '<em style="color:var(--ink-faint)">Generating coordinated furnishings…</em>';
-              btn.disabled = true; btn.style.opacity = '0.6';
-              try {
-                const r = await fetch('/api/design/${design.id}/furnish', {
-                  method: 'POST',
-                  headers: { 'Content-Type': 'application/json' },
-                  body: JSON.stringify({ context: ctxSel.value }),
-                });
-                const j = await r.json();
-                if (!j.ok) throw new Error(j.error || 'gen failed');
-                let html = '';
-                for (const cat of (j.categories || [])) {
-                  html += '<div style="margin-bottom:14px"><div style="font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);margin-bottom:6px">' + cat.category + '</div><ul style="margin:0;padding-left:18px">';
-                  for (const item of (cat.items || [])) {
-                    html += '<li style="margin-bottom:4px"><strong>' + item.name + '</strong>' + (item.detail ? ' — <span style="color:var(--ink-soft,#555)">' + item.detail + '</span>' : '') + '</li>';
-                  }
-                  html += '</ul></div>';
-                }
-                if (j.note) html += '<div style="font:11px var(--sans);color:var(--ink-faint);font-style:italic;margin-top:8px">' + j.note + '</div>';
-                out.innerHTML = html || '<em>No suggestions returned.</em>';
-              } catch (e) {
-                out.innerHTML = '<span style="color:#b91c1c">Error: ' + (e.message || String(e)) + '</span>';
-              } finally {
-                btn.disabled = false; btn.style.opacity = '1';
-              }
-            });
-          })();
-          </script>
-
-          <!-- ──────────────────────────────────────────────────────────────
-               Wall-fit crop tool — user enters wall dimensions, sees the
-               pattern tiled to scale + computed roll count + can download the
-               composition as a PNG. Steve's standing rule (2026-05-13): every
-               wallco.ai design must let users crop a section based on their
-               measurements BEFORE sample/order so they preview the actual fit.
-               Pattern is already seamless (24" tile), so this just tiles it
-               across an aspect-correct canvas — no edge-matching math needed.
-          ── ─────────────────────────────────────────────────────────────── -->
-          <div class="wallfit-crop" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
-            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Crop to your wall</label>
-            <div style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end;margin-bottom:12px">
-              <label style="font:11px var(--sans);color:var(--ink-soft,#555);display:block">
-                Wall width
-                <div style="display:flex;align-items:center;gap:4px;margin-top:2px">
-                  <input id="wallfit-w" type="number" min="12" max="600" step="0.5" value="120" style="width:100%;padding:8px 10px;border:1px solid var(--line);border-radius:6px;font:14px var(--sans)">
-                  <select id="wallfit-w-unit" style="padding:7px 4px;border:1px solid var(--line);border-radius:6px;font:11px var(--sans);background:white">
-                    <option value="in">in</option><option value="ft">ft</option>
-                  </select>
-                </div>
-              </label>
-              <label style="font:11px var(--sans);color:var(--ink-soft,#555);display:block">
-                Wall height
-                <div style="display:flex;align-items:center;gap:4px;margin-top:2px">
-                  <input id="wallfit-h" type="number" min="12" max="240" step="0.5" value="96" style="width:100%;padding:8px 10px;border:1px solid var(--line);border-radius:6px;font:14px var(--sans)">
-                  <select id="wallfit-h-unit" style="padding:7px 4px;border:1px solid var(--line);border-radius:6px;font:11px var(--sans);background:white">
-                    <option value="in">in</option><option value="ft">ft</option>
-                  </select>
-                </div>
-              </label>
-              <button type="button" id="wallfit-download" style="padding:9px 14px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border:0;border-radius:6px;font:600 11px var(--sans);letter-spacing:.06em;text-transform:uppercase;cursor:pointer;white-space:nowrap">↓ PNG</button>
-            </div>
-            <div style="position:relative;background:#f4f1ea;border:1px solid var(--line);border-radius:8px;overflow:hidden">
-              <canvas id="wallfit-canvas" style="display:block;width:100%;height:auto"></canvas>
-              <!-- Scale ribbon: shows wall width in ft beneath the preview -->
-              <div id="wallfit-scale" style="position:absolute;bottom:6px;left:8px;right:8px;display:flex;justify-content:space-between;font:10px ui-monospace,Menlo,monospace;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6);letter-spacing:.04em;pointer-events:none"></div>
-            </div>
-            <div id="wallfit-readout" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;margin-top:10px;font:11px var(--sans);color:var(--ink-soft,#555)">
-              <!-- filled by JS: rolls needed, drops per roll, waste % -->
-            </div>
-          </div>
-        </div>
-
-        <script>
-        (function(){
-          // Wall-fit crop tool. Reads roll width from localStorage (synced
-          // with the .w-tab buttons above). Renders the seamless tile across
-          // a wall-shaped canvas at scale. Roll count = ceil(walls / roll).
-          // Yard length assumed 396" (11 yd standard double-roll) for waste
-          // math — adjust ROLL_LENGTH_IN if real roll length differs.
-          var imgUrl = ${JSON.stringify(design.image_url)};
-          var defaultRepeatIn = 24;  // sane default if Pattern scale slider hasn't been touched
-          var ROLL_LENGTH_IN = 396;  // 11 yards (American double-roll), conservative
-          var c = document.getElementById('wallfit-canvas');
-          if (!c) return;
-          var ctx = c.getContext('2d');
-          var wIn = document.getElementById('wallfit-w');
-          var hIn = document.getElementById('wallfit-h');
-          var wU  = document.getElementById('wallfit-w-unit');
-          var hU  = document.getElementById('wallfit-h-unit');
-          var readout = document.getElementById('wallfit-readout');
-          var scaleEl = document.getElementById('wallfit-scale');
-          var dl = document.getElementById('wallfit-download');
-
-          // Restore previous wall dims
-          try {
-            var prev = JSON.parse(localStorage.getItem('wallco.wallfit') || '{}');
-            if (prev.w) { wIn.value = prev.w; if (prev.wu) wU.value = prev.wu; }
-            if (prev.h) { hIn.value = prev.h; if (prev.hu) hU.value = prev.hu; }
-          } catch {}
-
-          var img = new Image();
-          img.crossOrigin = 'anonymous';
-          var imgReady = false;
-          img.onload = function() { imgReady = true; paint(); };
-          img.onerror = function() { /* silently leave canvas blank */ };
-          img.src = imgUrl;
-
-          function toIn(val, unit) { return unit === 'ft' ? val * 12 : val; }
-          function getRollWidth() {
-            try {
-              var m = JSON.parse(localStorage.getItem('wallco.material') || 'null');
-              var w = m && parseInt(m.w, 10);
-              if ([24, 36, 52].indexOf(w) >= 0) return w;
-            } catch {}
-            return 24;
-          }
-          function getRepeatIn() {
-            var v = parseInt(localStorage.getItem('wallco.scale') || '', 10);
-            return (v && v >= 4) ? v : defaultRepeatIn;
-          }
-
-          function paint() {
-            var wallW = toIn(parseFloat(wIn.value) || 120, wU.value);
-            var wallH = toIn(parseFloat(hIn.value) || 96,  hU.value);
-            var rollW = getRollWidth();
-            var repeat = Math.min(getRepeatIn(), rollW);
-            // Persist
-            try { localStorage.setItem('wallco.wallfit', JSON.stringify({ w: wIn.value, wu: wU.value, h: hIn.value, hu: hU.value })); } catch {}
-            // Canvas size: keep px-per-inch constant so wide walls get wide
-            // canvases. Cap total px to ~1100 wide for layout sanity.
-            var pxPerIn = Math.min(8, Math.max(2, 1100 / wallW));
-            var W = Math.round(wallW * pxPerIn);
-            var H = Math.round(wallH * pxPerIn);
-            c.width = W; c.height = H;
-            // Background fill for unloaded / failed image
-            ctx.fillStyle = '#f4f1ea'; ctx.fillRect(0, 0, W, H);
-            if (imgReady) {
-              // Tile size in px = repeat inches × pxPerIn. Pattern image is
-              // already seamless so we can drop-repeat without offsetting.
-              var tilePx = Math.max(8, Math.round(repeat * pxPerIn));
-              for (var y = 0; y < H; y += tilePx) {
-                for (var x = 0; x < W; x += tilePx) {
-                  ctx.drawImage(img, x, y, tilePx, tilePx);
-                }
-              }
-              // Roll-seam guides — vertical lines at every roll-width
-              ctx.save();
-              ctx.strokeStyle = 'rgba(255,255,255,.35)';
-              ctx.lineWidth = 1;
-              for (var rx = rollW * pxPerIn; rx < W; rx += rollW * pxPerIn) {
-                ctx.beginPath(); ctx.moveTo(rx, 0); ctx.lineTo(rx, H); ctx.stroke();
-              }
-              ctx.restore();
-            }
-            // Scale ribbon
-            scaleEl.innerHTML = '<span>0 ft</span><span>' + (wallW / 12).toFixed(1) + ' ft wide × ' + (wallH / 12).toFixed(1) + ' ft tall</span><span>' + (wallW / 12).toFixed(1) + ' ft</span>';
-            // Read-out: rolls + drops + waste
-            var drops = Math.ceil(wallW / rollW);
-            var rollsNeeded = Math.ceil((drops * wallH) / ROLL_LENGTH_IN);
-            var coverage = drops * rollW * wallH;
-            var purchased = rollsNeeded * rollW * ROLL_LENGTH_IN;
-            var waste = purchased ? Math.max(0, ((purchased - coverage) / purchased) * 100) : 0;
-            readout.innerHTML =
-              '<div><b style="color:var(--ink)">' + rollsNeeded + '</b> roll' + (rollsNeeded === 1 ? '' : 's') + ' needed</div>' +
-              '<div><b style="color:var(--ink)">' + drops + '</b> vertical drop' + (drops === 1 ? '' : 's') + '</div>' +
-              '<div><b style="color:var(--ink)">' + rollW + '" </b> roll width</div>' +
-              '<div><b style="color:var(--ink)">' + repeat + '" </b> pattern repeat</div>' +
-              '<div><b style="color:var(--ink)">~' + waste.toFixed(0) + '%</b> waste</div>';
-          }
-
-          [wIn, hIn, wU, hU].forEach(function(el){ el.addEventListener('input', paint); el.addEventListener('change', paint); });
-          window.addEventListener('storage', function(e){ if (e.key === 'wallco.material' || e.key === 'wallco.scale') paint(); });
-          // Re-paint when any .w-tab on the page changes (same-tab; storage event only fires cross-tab)
-          document.querySelectorAll('.w-tab').forEach(function(t){ t.addEventListener('click', function(){ setTimeout(paint, 0); }); });
-          var scaleSl = document.getElementById('scale-slider');
-          if (scaleSl) scaleSl.addEventListener('input', function(){ setTimeout(paint, 0); });
-
-          dl.addEventListener('click', function(){
-            if (!imgReady) return;
-            var url = c.toDataURL('image/png');
-            var a = document.createElement('a');
-            var did = ${JSON.stringify(String(design.id).padStart(4,'0'))};
-            a.href = url;
-            a.download = 'wallco-' + did + '-wall-crop.png';
-            document.body.appendChild(a); a.click(); a.remove();
-          });
-
-          paint();
-        })();
-        </script>
-
-        <script>
-        (function(){
-          // The viewport visually represents one roll-width × ~2/3 roll-width (1 yard tall when roll=36).
-          // Hard rule (Steve): the repeat can never exceed the selected roll width.
-          // Max repeat clamps to roll width; selecting a smaller roll auto-clamps the current value.
-          var slider = document.getElementById('scale-slider');
-          var read   = document.getElementById('scale-readout');
-          var port   = document.getElementById('scale-viewport');
-          var hint   = document.getElementById('scale-width-hint');
-
-          function currentRollWidth() {
-            try {
-              var m = JSON.parse(localStorage.getItem('wallco.material') || 'null');
-              var w = m && parseInt(m.w, 10);
-              if ([24, 36, 52].indexOf(w) >= 0) return w;
-            } catch {}
-            return 24;
-          }
-
-          function applyRollWidth(rollW) {
-            // Slider max can never exceed roll width.
-            slider.max = String(rollW);
-            // Clamp current value down if it exceeds the new max.
-            if (parseInt(slider.value, 10) > rollW) slider.value = String(rollW);
-            if (hint) hint.textContent = rollW + '" wide, sold per yard';
-            paint();
-          }
-
-          // Persist (bounded by current roll width)
-          var saved = parseInt(localStorage.getItem('wallco.scale') || '', 10);
-          if (saved && saved >= 4) slider.value = String(saved);
-
-          function paint(){
-            var rollW = parseInt(slider.max, 10) || 24;
-            var inches = Math.min(parseInt(slider.value, 10), rollW);
-            slider.value = String(inches);
-            read.textContent = inches + '" repeat';
-            // % of viewport that one repeat occupies = inches / roll-width.
-            var pct = (inches / rollW) * 100;
-            port.style.backgroundSize = pct + '% auto';
-            localStorage.setItem('wallco.scale', String(inches));
-          }
-          slider.addEventListener('input', paint);
-
-          // Initial: align with whichever roll width is currently selected.
-          applyRollWidth(currentRollWidth());
-
-          // Listen to every roll-width tab on the page; re-apply on click.
-          document.querySelectorAll('.w-tab').forEach(function(t){
-            t.addEventListener('click', function(){
-              // The existing width-tab handler updates localStorage.wallco.material asynchronously;
-              // run on the next tick so we read the freshly-persisted value.
-              setTimeout(function(){ applyRollWidth(currentRollWidth()); }, 0);
-            });
-          });
-        })();
-        </script>
-      </div>
-    </div>
-    <div class="detail-info-col">
-      <p class="detail-breadcrumb"><a href="/designs" id="back-to-grid">Designs</a> &rsaquo; ${design.category}</p>
-      <script>
-      // Preserve the filtered grid URL the user came from so clicking Designs
-      // returns them to /designs?hue=rose&motif=floral, not bare /designs.
-      // Only honors same-origin /designs referrers; everything else stays default.
-      (function(){
-        var a = document.getElementById('back-to-grid'); if (!a) return;
-        try {
-          var ref = document.referrer || '';
-          if (!ref) return;
-          var u = new URL(ref);
-          if (u.origin !== location.origin) return;
-          if (!u.pathname.startsWith('/designs')) return;
-          if (u.search) a.href = u.pathname + u.search;
-        } catch(_) {}
-      })();
-      </script>
-      <h1 class="detail-title">${design.title}</h1>
-
-      <div class="provenance-badge">
-        ${_isAdmin ? `<span class="ai-badge">AI-Original</span>` : ''}
-        Pattern No.${String(design.id).padStart(4,'0')} &middot; ${(design.category || 'mixed').charAt(0).toUpperCase()+(design.category || 'mixed').slice(1)} &middot; ${design.kind === 'seamless_tile' ? 'Seamless Tile' : (design.kind || 'pattern')}
-      </div>
-
-      <!-- USER VOTE — 5-star widget, anonymous session-cookie tracking -->
-      <div id="user-vote-card" style="margin:14px 0;padding:12px 16px;background:#faf6ec;border:1px solid #e7dcc3;border-radius:8px;font-size:13px">
-        <div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap">
-          <div>
-            <strong style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:15px;color:#1a1816">Your rating</strong>
-            <span id="vote-avg" style="margin-left:8px;color:#666;font-size:12px"></span>
-          </div>
-          <div id="star-row" data-id="${design.id}" style="font-size:24px;letter-spacing:4px;cursor:pointer;color:#bba">
-            <span data-s="1">☆</span><span data-s="2">☆</span><span data-s="3">☆</span><span data-s="4">☆</span><span data-s="5">☆</span>
-          </div>
-        </div>
-        <div style="margin-top:6px;font-size:11px;color:#888">Each vote teaches the system. We use your taste signals to bias what we generate next.</div>
-      </div>
-      <script>
-      (async function(){
-        var row = document.getElementById('star-row');
-        var avgEl = document.getElementById('vote-avg');
-        var id = row.getAttribute('data-id');
-        function paint(s){
-          Array.from(row.children).forEach(function(el,i){ el.textContent = (i < s) ? '★' : '☆'; });
-          row.style.color = s ? '#d2b15c' : '#bba';
-        }
-        async function refresh(){
-          var v = await (await fetch('/api/design/' + id + '/votes')).json();
-          if (v.my_score) paint(v.my_score);
-          if (v.count) avgEl.textContent = '· average ★' + (v.avg || '?') + ' from ' + v.count + ' vote' + (v.count===1?'':'s');
-        }
-        Array.from(row.children).forEach(function(el){
-          el.addEventListener('mouseenter', function(){ paint(parseInt(el.getAttribute('data-s'),10)); });
-          el.addEventListener('click', async function(){
-            var s = parseInt(el.getAttribute('data-s'),10);
-            paint(s);
-            var r = await fetch('/api/design/' + id + '/user-vote', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({score: s})});
-            var j = await r.json();
-            if (j.user_score_avg) avgEl.textContent = '· average ★' + j.user_score_avg + ' from ' + j.user_vote_count + ' vote' + (j.user_vote_count===1?'':'s');
-          });
-        });
-        row.addEventListener('mouseleave', refresh);
-        refresh();
-      })();
-      </script>
-
-      <!-- AI Suggested Rating (graphic designer + interior designer) -->
-      <div id="ai-rating-card" style="display:none;margin:14px 0;padding:14px 16px;background:#1a1816;color:#e8e2d6;border-radius:8px;border:1px solid #2a2a2a;font-size:13px">
-        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
-          <strong style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:16px;color:#d2b15c">AI Suggested Rating</strong>
-          <span id="rating-combined" style="font-size:18px;color:#d2b15c"></span>
-        </div>
-        <div id="rating-summary" style="color:#bba;font-size:12px;line-height:1.55"></div>
-        <div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:10px;font-size:11px">
-          <div><div style="text-transform:uppercase;letter-spacing:.08em;color:#888">Graphic designer</div><div id="rating-gd-score" style="font-size:14px;color:#fff"></div><div id="rating-gd-critique" style="color:#aaa;margin-top:4px"></div></div>
-          <div><div style="text-transform:uppercase;letter-spacing:.08em;color:#888">Interior designer</div><div id="rating-id-score" style="font-size:14px;color:#fff"></div><div id="rating-id-critique" style="color:#aaa;margin-top:4px"></div></div>
-        </div>
-      </div>
-      <script>
-      (async function(){
-        try {
-          var r = await fetch('/api/design/${design.id}/rating');
-          if (!r.ok) return;
-          var j = await r.json();
-          if (!j.combined_score) return;
-          document.getElementById('ai-rating-card').style.display = 'block';
-          document.getElementById('rating-combined').textContent = '★ ' + j.combined_score + ' / 5';
-          document.getElementById('rating-summary').textContent = j.rating_summary || '';
-          document.getElementById('rating-gd-score').textContent = (j.gd_score || '?') + ' / 5';
-          document.getElementById('rating-gd-critique').textContent = j.gd_critique || '';
-          document.getElementById('rating-id-score').textContent = (j.id_score || '?') + ' / 5';
-          document.getElementById('rating-id-critique').textContent = j.id_critique || '';
-        } catch(e) {}
-      })();
-      </script>
-
-      <div class="detail-palette">
-        <span class="swatch-main" style="background:${design.dominant_hex}" title="Dominant ${design.dominant_hex}"></span>
-        <span class="hex-label">${design.dominant_hex}</span>
-      </div>
-
-      <!-- Full palette — every extracted color as a paint chip + a "paint" stripe -->
-      ${(() => {
-        const pal = Array.isArray(design.palette) ? design.palette.filter(p => p && p.hex) : [];
-        if (pal.length === 0) return '';
-        const chips = pal.map(p => `
-          <div class="paint-chip" data-hex="${p.hex}" title="${p.pct != null ? p.pct + '% of the design' : p.hex} — click to copy">
-            <div class="paint-chip-color" style="background:${p.hex}"></div>
-            <div class="paint-chip-label">
-              <span class="paint-chip-hex">${p.hex.toUpperCase()}</span>
-              <span class="paint-chip-pct">${p.pct != null ? p.pct + '%' : ''}</span>
-            </div>
-          </div>`).join('');
-        // Paint stripe — proportional widths matching each color's pct in the design.
-        const totalPct = pal.reduce((s, p) => s + (p.pct || 0), 0) || pal.length;
-        const stripe = pal.map(p => {
-          const w = ((p.pct || (100 / pal.length)) / totalPct * 100).toFixed(2);
-          return `<div class="paint-stripe-cell" style="background:${p.hex};flex:0 0 ${w}%" title="${p.hex} · ${p.pct || ''}%"></div>`;
-        }).join('');
-        return `
-      <div class="full-palette">
-        <div class="palette-header">
-          <span class="palette-label">Palette · ${pal.length} colors · click a chip to copy hex</span>
-        </div>
-        <div class="paint-chips">${chips}</div>
-        <div class="paint-stripe" title="Paint stripe — colors at their actual coverage in the design">${stripe}</div>
-        <div class="paint-stripe-caption">Paint stripe — each color at its actual coverage in the design</div>
-      </div>
-      <style>
-        .full-palette { margin:14px 0 4px; }
-        .palette-header { margin-bottom:8px; }
-        .palette-label { font:11px var(--sans, system-ui); text-transform:uppercase; letter-spacing:.08em; color:#888; }
-        .paint-chips { display:grid; grid-template-columns:repeat(auto-fit, minmax(120px, 1fr)); gap:10px; }
-        .paint-chip { border:1px solid #e0d8c4; border-radius:6px; overflow:hidden; background:#fff; cursor:pointer; transition:transform .12s, border-color .12s; }
-        .paint-chip:hover { transform:translateY(-1px); border-color:#c9a14b; }
-        .paint-chip-color { aspect-ratio:1.6/1; border-bottom:1px solid rgba(0,0,0,.08); }
-        .paint-chip-label { padding:7px 10px; display:flex; justify-content:space-between; align-items:center; font:11px ui-monospace, monospace; }
-        .paint-chip-hex { color:#1a1816; font-weight:600; letter-spacing:.04em; }
-        .paint-chip-pct { color:#888; font-size:10.5px; }
-        .paint-stripe { display:flex; height:64px; margin-top:14px; border:1px solid #e0d8c4; border-radius:6px; overflow:hidden; }
-        .paint-stripe-cell { transition:flex-grow .15s; }
-        .paint-stripe-cell:hover { flex-grow:1.6; }
-        .paint-stripe-caption { text-align:center; font:11px var(--sans, system-ui); color:#888; margin-top:6px; }
-      </style>
-      <script>
-        document.querySelectorAll('.paint-chip').forEach(el => {
-          el.addEventListener('click', async () => {
-            const hex = el.dataset.hex;
-            try { await navigator.clipboard.writeText(hex); } catch(_) {}
-            const lab = el.querySelector('.paint-chip-hex');
-            if (!lab) return;
-            const orig = lab.textContent;
-            lab.textContent = '✓ copied';
-            setTimeout(() => { lab.textContent = orig; }, 1200);
-          });
-        });
-      </script>`;
-      })()}
-
-      <!-- Width + material selector -->
-      <div class="material-picker" style="margin:18px 0 20px;padding:16px 18px;border:1px solid var(--line);border-radius:8px;background:var(--card-bg)">
-        <div style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:10px">Width</div>
-        <div class="width-tabs" id="width-tabs" data-native-w="${design.width_in || ''}" style="display:flex;gap:8px;margin-bottom:14px">
-          <button type="button" class="w-tab" data-w="24" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">24" residential</button>
-          <button type="button" class="w-tab" data-w="36" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">36" wide-roll</button>
-          <button type="button" class="w-tab" data-w="52" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">52" commercial</button>
-        </div>
-        <div style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:10px">Material</div>
-        <div class="material-tabs" id="material-tabs" style="display:flex;flex-wrap:wrap;gap:6px"></div>
-        <div id="material-readout" style="margin-top:10px;font:12px var(--sans);color:var(--ink-soft)"></div>
-      </div>
-
-      <div class="detail-spec-panel">
-        <div class="spec-row"><span class="spec-key">Category</span><span class="spec-val">${design.category}</span></div>
-        <div class="spec-row"><span class="spec-key">Width</span><span class="spec-val" id="spec-width">24"</span></div>
-        <div class="spec-row"><span class="spec-key">Material</span><span class="spec-val" id="spec-material">Paper</span></div>
-        <div class="spec-row"><span class="spec-key">Sold by</span><span class="spec-val">Per yard</span></div>
-        <div class="spec-row"><span class="spec-key">Repeat</span><span class="spec-val" id="spec-repeat">24" (adjust on left)</span></div>
-        <div class="spec-row"><span class="spec-key">Kind</span><span class="spec-val">${design.kind === 'seamless_tile' ? 'Seamless tile' : design.kind}</span></div>
-        <div class="spec-row"><span class="spec-key">Designed by</span><span class="spec-val">${design.generator}</span></div>
-        <div class="spec-row"><span class="spec-key">Handle</span><span class="spec-val">${design.handle}</span></div>
-      </div>
-
-      <script>
-      (function(){
-        var WIDTH_MATERIALS = {
-          '24': [
-            {key:'paper',      label:'Paper',         note:'Traditional non-woven, easy install'},
-            {key:'vinyl',      label:'Vinyl',         note:'Scrubbable, kid-proof, kitchens & baths'},
-            {key:'peelstick',  label:'Peel & Stick',  note:'Rental-friendly, removable, no paste'},
-            {key:'grasscloth', label:'Grasscloth',    note:'Natural fiber, textural, handcrafted'}
-          ],
-          '36': [
-            {key:'paper',      label:'Paper',         note:'Wide-roll non-woven, fewer seams across walls'},
-            {key:'vinyl',      label:'Vinyl',         note:'Scrubbable wide-format, large-pattern friendly'},
-            {key:'grasscloth', label:'Grasscloth',    note:'Wide-bolt natural fiber, premium upcharge'}
-          ],
-          '52': [
-            {key:'vinyl',      label:'Type II Vinyl', note:'Commercial scrubbable, Class A fire-rated'},
-            {key:'paper',      label:'Contract Paper',note:'Heavy-duty backing, hospitality / corridor use'}
-          ]
-        };
-        var widthTabs = document.querySelectorAll('.w-tab');
-        var widthHost = document.getElementById('width-tabs');
-        var matWrap   = document.getElementById('material-tabs');
-        var readout   = document.getElementById('material-readout');
-        var specW     = document.getElementById('spec-width');
-        var specM     = document.getElementById('spec-material');
-
-        // Default the slider to the design's native width when available (24/36/52); else 24".
-        var nativeW = (widthHost && widthHost.dataset.nativeW) ? String(Math.round(parseFloat(widthHost.dataset.nativeW))) : '';
-        if (!['24','36','52'].includes(nativeW)) nativeW = '24';
-        var saved = JSON.parse(localStorage.getItem('wallco.material') || 'null') || { w: nativeW, m: 'paper' };
-        // Tag the native button visually so users see "you're looking at what this pattern was generated for"
-        widthTabs.forEach(function(t){
-          if (t.dataset.w === nativeW) {
-            t.title = 'Generated at this width';
-            t.insertAdjacentHTML('beforeend', ' <span style="font-size:9px;letter-spacing:.16em;opacity:.7;margin-left:4px">·NATIVE</span>');
-          }
-        });
-
-        function paintMaterials(w) {
-          var opts = WIDTH_MATERIALS[w];
-          matWrap.innerHTML = opts.map(function(o){
-            var on = (o.key === saved.m);
-            return '<button type="button" class="m-tab' + (on ? ' is-on' : '') + '" data-m="' + o.key + '" data-note="' + o.note + '" style="padding:8px 14px;border:1px solid ' + (on ? 'var(--accent)' : 'var(--line)') + ';background:' + (on ? 'var(--accent)' : 'transparent') + ';color:' + (on ? 'var(--bg)' : 'var(--ink)') + ';font:12px var(--sans);font-weight:500;border-radius:999px;cursor:pointer">' + o.label + '</button>';
-          }).join('');
-          // If saved.m isn't in this width's options, fall back to first
-          if (!opts.some(function(o){return o.key===saved.m;})) saved.m = opts[0].key;
-          // Wire material buttons
-          matWrap.querySelectorAll('.m-tab').forEach(function(btn){
-            btn.addEventListener('click', function(){
-              saved.m = btn.dataset.m;
-              paintMaterials(w);
-              persist();
-            });
-          });
-          var cur = opts.find(function(o){return o.key===saved.m;}) || opts[0];
-          readout.textContent = cur.note;
-          specW.textContent = w + '"';
-          specM.textContent = cur.label;
-        }
-        function persist(){ localStorage.setItem('wallco.material', JSON.stringify(saved)); }
-
-        widthTabs.forEach(function(t){
-          t.addEventListener('click', function(){
-            saved.w = t.dataset.w;
-            widthTabs.forEach(function(x){
-              var on = x===t;
-              x.classList.toggle('is-on', on);
-              x.style.background = on ? 'var(--accent)' : 'transparent';
-              x.style.color      = on ? 'var(--bg)' : 'var(--ink)';
-              x.style.borderColor= on ? 'var(--accent)' : 'var(--line)';
-            });
-            paintMaterials(saved.w);
-            persist();
-          });
-        });
-        // Restore on load
-        widthTabs.forEach(function(t){
-          if (t.dataset.w === saved.w) t.click();
-        });
-        // If neither matched (shouldn't happen), default to 24"
-        if (!document.querySelector('.w-tab.is-on')) widthTabs[0].click();
-      })();
-      </script>
-
-      <!-- Wall-size calculator: enter W×H, get panel count + total yardage + estimated cost -->
-      <details class="wall-calc" style="margin:14px 0 0;padding:0;border:1px solid var(--line,#ddd);border-radius:6px;background:var(--card-bg,#faf8f3)">
-        <summary style="cursor:pointer;padding:10px 14px;font:12px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft,#555);list-style:none">
-          <span aria-hidden="true">📐</span> Wall-size calculator <span style="color:var(--ink-faint,#999);font-size:11px;text-transform:none;letter-spacing:0">— estimate panels, yardage, cost</span>
-          <span id="wc-summary-chip" style="display:none;margin-left:8px;padding:2px 8px;background:#d2b15c33;border:1px solid #d2b15c;border-radius:10px;color:#5a4520;font-size:11px;text-transform:none;letter-spacing:0;font-weight:600" aria-hidden="true"></span>
-        </summary>
-        <div style="padding:6px 14px 14px;display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end">
-          <label style="font:11px var(--sans);color:var(--ink-soft);text-transform:uppercase;letter-spacing:.06em">Wall width (ft)<br><input id="wc-w" type="number" min="1" max="200" step="0.5" value="10" style="margin-top:4px;width:100%;padding:6px 8px;font:14px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <label style="font:11px var(--sans);color:var(--ink-soft);text-transform:uppercase;letter-spacing:.06em">Wall height (ft)<br><input id="wc-h" type="number" min="1" max="40" step="0.5" value="9" style="margin-top:4px;width:100%;padding:6px 8px;font:14px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <button type="button" id="wc-calc" style="padding:8px 16px;background:var(--ink,#111);color:var(--bg,#fff);border:0;border-radius:4px;cursor:pointer;font:13px var(--sans);height:fit-content">Calculate</button>
-        </div>
-        <div id="wc-result" style="padding:0 14px 14px;font:13px/1.5 var(--sans);color:var(--ink,#111);display:none"></div>
-        <div id="wc-pinbar" style="padding:0 14px 12px;display:flex;align-items:center;gap:10px;font:11px var(--sans);color:var(--ink-faint,#999)">
-          <a id="wc-pin" href="javascript:void(0)" style="color:var(--ink-soft,#555);text-decoration:underline;text-underline-offset:2px">Save defaults</a>
-          <span id="wc-pin-status" style="display:none;align-items:center;gap:6px;color:#2d6a4f;font-weight:600"><span aria-hidden="true">✓</span><span id="wc-pin-label"></span><a id="wc-pin-clear" href="javascript:void(0)" style="margin-left:6px;color:var(--ink-faint,#999);font-weight:400;text-decoration:underline">clear</a></span>
-        </div>
-      </details>
-      <script>
-      (function(){
-        var btn = document.getElementById('wc-calc');
-        if (!btn) return;
-        var wEl = document.getElementById('wc-w');
-        var hEl = document.getElementById('wc-h');
-        var wallDetails = btn.closest('details');
-        var pinLink = document.getElementById('wc-pin');
-        var pinStatus = document.getElementById('wc-pin-status');
-        var pinLabel = document.getElementById('wc-pin-label');
-        var pinClear = document.getElementById('wc-pin-clear');
-        // Hydrate priority: pinned defaults beat the most-recent working values.
-        // Guards: w/h must be positive finite numbers; pin expires after 90 days
-        // so a stale 2024 pin doesn't haunt the 2026 catalog. Unit-tagged so a
-        // future inches/cm variant can drop mismatched pins on load.
-        var pinned = null, recent = null;
-        try { pinned = JSON.parse(localStorage.getItem('wallco-wall-calc-pin') || 'null'); } catch(e){}
-        try { recent = JSON.parse(localStorage.getItem('wallco-wall-calc') || 'null'); } catch(e){}
-        var PIN_TTL_MS = 90*86400*1000;
-        function pinValid(p){
-          return !!(p && isFinite(p.w) && isFinite(p.h) && p.w > 0 && p.h > 0
-              && (!p.unit || p.unit === 'ft')
-              && (!p.t || (Date.now() - p.t) < PIN_TTL_MS));
-        }
-        if (pinned && !pinValid(pinned)) {
-          try { localStorage.removeItem('wallco-wall-calc-pin'); } catch(e){}
-          pinned = null;
-        }
-        var prev = pinValid(pinned) ? pinned : recent;
-        if (prev && isFinite(prev.w) && prev.w > 0) wEl.value = prev.w;
-        if (prev && isFinite(prev.h) && prev.h > 0) hEl.value = prev.h;
-        if (prev && wallDetails && !wallDetails.open) wallDetails.open = true;
-        function renderPinUI(){
-          var chip = document.getElementById('wc-summary-chip');
-          if (pinValid(pinned)) {
-            pinLink.style.display = 'none';
-            pinStatus.style.display = 'inline-flex';
-            pinLabel.textContent = 'Pinned ' + pinned.w + '×' + pinned.h + ' ft';
-            if (chip) { chip.textContent = '📌 ' + pinned.w + '×' + pinned.h + ' ft'; chip.style.display = 'inline-block'; }
-          } else {
-            pinLink.style.display = 'inline';
-            pinStatus.style.display = 'none';
-            if (chip) { chip.style.display = 'none'; chip.textContent = ''; }
-          }
-        }
-        renderPinUI();
-        function persist(){
-          try { localStorage.setItem('wallco-wall-calc', JSON.stringify({ w: parseFloat(wEl.value)||0, h: parseFloat(hEl.value)||0, t: Date.now() })); } catch(e){}
-        }
-        pinLink.addEventListener('click', function(){
-          var w = parseFloat(wEl.value), h = parseFloat(hEl.value);
-          if (!isFinite(w) || !isFinite(h) || w<=0 || h<=0) return;
-          pinned = { w: w, h: h, unit: 'ft', pinned: true, t: Date.now() };
-          try { localStorage.setItem('wallco-wall-calc-pin', JSON.stringify(pinned)); } catch(e){}
-          renderPinUI();
-        });
-        pinClear.addEventListener('click', function(){
-          pinned = null;
-          try { localStorage.removeItem('wallco-wall-calc-pin'); } catch(e){}
-          renderPinUI();
-        });
-        function compute() {
-          var w = parseFloat(wEl.value);
-          var h = parseFloat(hEl.value);
-          var box = document.getElementById('wc-result');
-          if (!isFinite(w) || !isFinite(h) || w<=0 || h<=0) { box.style.display='block'; box.innerHTML = '<span style="color:#b03a2e">Enter both width and height in feet.</span>'; return; }
-          persist();
-          // Read user's chosen panel width (24/27/36) from the existing material picker if present.
-          var savedWidth = 24;
-          try { var s = JSON.parse(localStorage.getItem('wallco.material') || 'null'); if (s && parseInt(s.w,10)) savedWidth = parseInt(s.w,10); } catch(e){}
-          var panelWidthInches = savedWidth; // panel width in inches
-          var panelLengthFt = 27; // standard roll length in ft (a typical 27 sq-ft single roll)
-          var wallSqFt = w * h;
-          var panelCoverageFt2 = (panelWidthInches/12) * panelLengthFt; // each panel covers this many sq-ft
-          // Match-waste factor: AI-original tiles are 24×24 in seamless so most patterns repeat cleanly. Use a 15% waste buffer.
-          var withWaste = wallSqFt * 1.15;
-          var panels = Math.ceil(withWaste / panelCoverageFt2);
-          var totalYards = +((panels * panelLengthFt) / 3).toFixed(1);
-          // Rough pricing tier: $7.50/sq-ft installed material list (typical mid-range custom wall, AI-original)
-          var lowEnd = Math.round(panels * panelCoverageFt2 * 6.5);
-          var highEnd = Math.round(panels * panelCoverageFt2 * 11.5);
-          box.style.display='block';
-          box.innerHTML =
-            '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:6px">' +
-              '<div><strong style="display:block;font:22px var(--serif,Georgia,serif);color:var(--ink,#111)">' + panels + '</strong><span style="font:11px var(--sans);color:var(--ink-faint,#999);text-transform:uppercase;letter-spacing:.06em">panels (' + panelWidthInches + '" wide)</span></div>' +
-              '<div><strong style="display:block;font:22px var(--serif,Georgia,serif);color:var(--ink,#111)">' + totalYards + ' yd</strong><span style="font:11px var(--sans);color:var(--ink-faint,#999);text-transform:uppercase;letter-spacing:.06em">total length</span></div>' +
-              '<div><strong style="display:block;font:22px var(--serif,Georgia,serif);color:var(--ink,#111)">' + wallSqFt.toFixed(0) + ' ft²</strong><span style="font:11px var(--sans);color:var(--ink-faint,#999);text-transform:uppercase;letter-spacing:.06em">wall area</span></div>' +
-              '<div><strong style="display:block;font:22px var(--serif,Georgia,serif);color:var(--ink,#111)">~$' + lowEnd.toLocaleString() + '–$' + highEnd.toLocaleString() + '</strong><span style="font:11px var(--sans);color:var(--ink-faint,#999);text-transform:uppercase;letter-spacing:.06em">material estimate</span></div>' +
-            '</div>' +
-            '<p style="margin:10px 0 0;font:11px/1.4 var(--sans);color:var(--ink-faint,#999)">Estimate uses ' + panelWidthInches + '" panel width, 27 ft panel length, 15% match-waste buffer. Final quote depends on substrate + installation. Sample first.</p>';
-        }
-        btn.addEventListener('click', compute);
-        wEl.addEventListener('keyup', function(e){ if (e.key==='Enter') compute(); });
-        hEl.addEventListener('keyup', function(e){ if (e.key==='Enter') compute(); });
-        wEl.addEventListener('change', persist);
-        hEl.addEventListener('change', persist);
-        // If we restored values, auto-compute on PDP load so the result is already visible.
-        if (prev && isFinite(prev.w) && isFinite(prev.h) && prev.w > 0 && prev.h > 0) {
-          compute();
-        }
-      })();
-      </script>
-
-      <!-- Sample CTA (DW standing rule — non-negotiable) -->
-      <div class="cta-group">
-        <a id="cta-sample-link" href="/samples?design=${design.id}" class="btn-primary cta-sample">Request Free Sample</a>
-        <a href="/designs" class="btn-outline">Back to Designs</a>
-        <button id="cta-share" type="button" class="btn-outline cta-share"
-                data-share-title="${design.title} — wallco.ai"
-                data-share-text="Found this in the wallco.ai catalog — ${design.title}"
-                data-share-url="https://wallco.ai/design/${design.id}"
-                aria-label="Share this design">
-          <span class="cta-share-icon" aria-hidden="true">↗</span>
-          <span class="cta-share-label">Share</span>
-        </button>
-      </div>
-
-      <!-- Trade / professional functions row -->
-      <div class="pro-tools" role="group" aria-label="Trade tools" style="display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 4px;padding-top:14px;border-top:1px dashed var(--line,#ddd)">
-        <a href="/design/${design.id}/tearsheet" target="_blank" rel="noopener noreferrer" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;text-decoration:none;display:inline-flex;align-items:center;gap:6px" title="Open print-ready tear sheet (1-page PDF)">
-          <span aria-hidden="true">⎙</span> Tear Sheet (PDF)
-        </a>
-        <a id="cta-email-client" href="mailto:?subject=${encodeURIComponent('wallco.ai design — ' + (design.title || 'Design #' + design.id))}&body=${encodeURIComponent('Take a look at this wallpaper pattern I found:\n\n' + (design.title || 'Design #' + design.id) + '\nhttps://wallco.ai/design/' + design.id + '\n\nTear sheet (print/PDF):\nhttps://wallco.ai/design/' + design.id + '/tearsheet\n\nFree trade samples available from Designer Wallcoverings — info@designerwallcoverings.com\n')}" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;text-decoration:none;display:inline-flex;align-items:center;gap:6px" title="Email this design to a client (opens your mail app)">
-          <span aria-hidden="true">✉</span> Email to Client
-        </a>
-        <button id="cta-add-compare" type="button" data-design-id="${design.id}" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Add to side-by-side compare (up to 4)">
-          <span aria-hidden="true">+</span> <span class="cta-add-compare-label">Compare</span>
-        </button>
-        <a id="cta-view-compare" href="/compare" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;text-decoration:none;display:none;align-items:center;gap:6px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border-color:var(--gold,#c9a14b)" title="View your compare list">
-          View Compare (<span id="cta-compare-count">0</span>)
-        </a>
-        <button id="cta-trade-save" type="button" data-design-id="${design.id}" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Save to your trade library (sign-in required)">
-          <span class="cta-trade-save-icon" aria-hidden="true">♡</span> <span class="cta-trade-save-label">Save</span>
-        </button>
-        <a id="cta-trade-view-saved" href="/me/saved" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;text-decoration:none;display:none;align-items:center;gap:6px" title="View your saved designs">
-          View saved (<span id="cta-trade-saved-count">0</span>)
-        </a>
-        <button id="cta-add-basket" type="button" data-design-id="${design.id}" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Add to your sample basket (up to 12)">
-          <span aria-hidden="true">+</span> <span class="cta-add-basket-label">Sample</span>
-        </button>
-        <a id="cta-view-basket" href="/basket" class="btn-outline" style="font:12px var(--sans);padding:7px 14px;text-decoration:none;display:none;align-items:center;gap:6px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border-color:var(--gold,#c9a14b)" title="View your sample basket">
-          View Basket (<span id="cta-basket-count">0</span>)
-        </a>
-      </div>
-      <script>
-      (function(){
-        var addBtn = document.getElementById('cta-add-compare');
-        var viewBtn = document.getElementById('cta-view-compare');
-        var countEl = document.getElementById('cta-compare-count');
-        if (!addBtn || !viewBtn) return;
-        var thisId = parseInt(addBtn.dataset.designId, 10);
-        function getList(){ try { return JSON.parse(localStorage.getItem('wallco-compare') || '[]').filter(function(x){ return typeof x === 'number' && x>0; }); } catch(e){ return []; } }
-        function setList(arr){ try { localStorage.setItem('wallco-compare', JSON.stringify(arr.slice(0,4))); } catch(e){} }
-        function refresh(){
-          var list = getList();
-          if (countEl) countEl.textContent = list.length;
-          if (list.length > 0) {
-            viewBtn.style.display = 'inline-flex';
-            viewBtn.setAttribute('href', '/compare?ids=' + list.join(','));
-          } else {
-            viewBtn.style.display = 'none';
-          }
-          var lbl = addBtn.querySelector('.cta-add-compare-label');
-          if (list.indexOf(thisId) >= 0) {
-            if (lbl) lbl.textContent = 'In compare';
-            addBtn.dataset.in = '1';
-            addBtn.style.background = 'var(--card-bg, #faf8f3)';
-          } else {
-            if (lbl) lbl.textContent = 'Compare';
-            addBtn.dataset.in = '0';
-            addBtn.style.background = '';
-          }
-        }
-        addBtn.addEventListener('click', function(){
-          var list = getList();
-          var idx = list.indexOf(thisId);
-          if (idx >= 0) { list.splice(idx, 1); }
-          else if (list.length < 4) { list.push(thisId); }
-          else { alert('Compare holds 4 designs max. Remove one to add another.'); return; }
-          setList(list);
-          refresh();
-        });
-        refresh();
-      })();
-      </script>
-      <script>
-      // Basket toggle — mirrors the compare toggle pattern exactly (different key, cap=12)
-      (function(){
-        var addBtn = document.getElementById('cta-add-basket');
-        var viewBtn = document.getElementById('cta-view-basket');
-        var countEl = document.getElementById('cta-basket-count');
-        if (!addBtn || !viewBtn) return;
-        var thisId = parseInt(addBtn.dataset.designId, 10);
-        function getList(){ try { return JSON.parse(localStorage.getItem('wallco-basket') || '[]').filter(function(x){ return typeof x === 'number' && x>0; }); } catch(e){ return []; } }
-        function setList(arr){ try { localStorage.setItem('wallco-basket', JSON.stringify(arr.slice(0,12))); } catch(e){} }
-        function refresh(){
-          var list = getList();
-          if (countEl) countEl.textContent = list.length;
-          if (list.length > 0) {
-            viewBtn.style.display = 'inline-flex';
-            viewBtn.setAttribute('href', '/basket');
-          } else {
-            viewBtn.style.display = 'none';
-          }
-          var lbl = addBtn.querySelector('.cta-add-basket-label');
-          if (list.indexOf(thisId) >= 0) {
-            if (lbl) lbl.textContent = 'In basket';
-            addBtn.dataset.in = '1';
-            addBtn.style.background = 'var(--card-bg, #faf8f3)';
-          } else {
-            if (lbl) lbl.textContent = 'Sample';
-            addBtn.dataset.in = '0';
-            addBtn.style.background = '';
-          }
-        }
-        addBtn.addEventListener('click', function(){
-          var list = getList();
-          var idx = list.indexOf(thisId);
-          if (idx >= 0) { list.splice(idx, 1); }
-          else if (list.length < 12) { list.push(thisId); }
-          else { alert('Sample basket holds 12 designs max. Remove one to add another.'); return; }
-          setList(list);
-          refresh();
-          // Broadcast — nav chip + any other listeners stay in sync.
-          try { window.dispatchEvent(new CustomEvent('wallco-basket-changed', { detail: { count: list.length, design_id: thisId, in_basket: list.indexOf(thisId) >= 0 } })); } catch(e){}
-        });
-        refresh();
-      })();
-      </script>
-      <script>
-      // Trade save toggle — server-side (POST /api/saved). Falls back to /trade/login
-      // when not authenticated. Hydrates initial state from /api/trade/me payload.
-      (function(){
-        var saveBtn = document.getElementById('cta-trade-save');
-        var viewBtn = document.getElementById('cta-trade-view-saved');
-        var countEl = document.getElementById('cta-trade-saved-count');
-        if (!saveBtn) return;
-        var thisId = parseInt(saveBtn.dataset.designId, 10);
-        var iconEl  = saveBtn.querySelector('.cta-trade-save-icon');
-        var labelEl = saveBtn.querySelector('.cta-trade-save-label');
-
-        function setSavedState(saved) {
-          if (saved) {
-            if (labelEl) labelEl.textContent = 'Saved';
-            if (iconEl)  iconEl.textContent = '♥';
-            saveBtn.style.background = 'var(--gold, #c9a14b)';
-            saveBtn.style.color = 'var(--accent, #0d0d0d)';
-            saveBtn.style.borderColor = 'var(--gold, #c9a14b)';
-          } else {
-            if (labelEl) labelEl.textContent = 'Save';
-            if (iconEl)  iconEl.textContent = '♡';
-            saveBtn.style.background = '';
-            saveBtn.style.color = '';
-            saveBtn.style.borderColor = '';
-          }
-        }
-        function updateCount(n) {
-          if (countEl) countEl.textContent = (n == null ? 0 : n);
-          if (viewBtn) viewBtn.style.display = (n && n > 0) ? 'inline-flex' : 'none';
-        }
-        function hydrate(me) {
-          if (!me || !me.authenticated) { updateCount(0); return; }
-          updateCount(me.saved_count || 0);
-          // Probe save state for this design — cheap, single design_id-only payload.
-          // (We piggyback on the toggle endpoint with HEAD-like idempotent GET later
-          // if needed; for now refresh on click.)
-          fetch('/api/saved/state?design_id=' + thisId, { credentials:'same-origin' })
-            .then(function(r){ return r.ok ? r.json() : { saved:false }; })
-            .then(function(j){ setSavedState(!!j.saved); })
-            .catch(function(){});
-        }
-        // If header has already loaded /api/trade/me, use cached payload; else listen.
-        if (window.__tradeMe) hydrate(window.__tradeMe);
-        else window.addEventListener('trade-me-loaded', function(ev){ hydrate(ev.detail); });
-
-        saveBtn.addEventListener('click', function(){
-          var me = window.__tradeMe;
-          if (!me || !me.authenticated) {
-            var ret = encodeURIComponent('/design/' + thisId);
-            if (confirm('Sign in to save designs?')) location.href = '/trade/login?return=' + ret;
-            return;
-          }
-          saveBtn.disabled = true;
-          fetch('/api/saved', {
-            method:'POST', headers:{'Content-Type':'application/json'},
-            credentials:'same-origin',
-            body: JSON.stringify({ design_id: thisId })
-          })
-            .then(function(r){ return r.json(); })
-            .then(function(j){
-              if (j.ok) {
-                setSavedState(j.saved);
-                updateCount(j.saved_count);
-                if (window.__tradeMe) window.__tradeMe.saved_count = j.saved_count;
-                // Broadcast — nav listener (and any other surfaces) sync without re-fetching.
-                try { window.dispatchEvent(new CustomEvent('wallco-saved-changed', { detail: { saved_count: j.saved_count, design_id: thisId, saved: j.saved } })); } catch(e){}
-              } else {
-                alert(j.error || 'Save failed.');
-              }
-            })
-            .catch(function(err){ alert('Network error: ' + err.message); })
-            .finally(function(){ saveBtn.disabled = false; });
-        });
-      })();
-      </script>
-      <script>
-      (function(){
-        var btn = document.getElementById('cta-share');
-        if (!btn) return;
-        var labelEl = btn.querySelector('.cta-share-label');
-        var origLabel = labelEl.textContent;
-        var resetTimer = null;
-        function flash(msg){
-          if (resetTimer) clearTimeout(resetTimer);
-          labelEl.textContent = msg;
-          resetTimer = setTimeout(function(){ labelEl.textContent = origLabel; }, 1800);
-        }
-        btn.addEventListener('click', async function(){
-          var url = btn.dataset.shareUrl;
-          var title = btn.dataset.shareTitle;
-          var text = btn.dataset.shareText;
-          // Prefer native Web Share API when available (mobile + supporting desktop browsers).
-          if (navigator.share) {
-            try { await navigator.share({ title: title, text: text, url: url }); return; }
-            catch (e) { /* user cancelled or unsupported — fall through to clipboard */ }
-          }
-          // Clipboard fallback.
-          try {
-            await navigator.clipboard.writeText(url);
-            flash('Link copied');
-          } catch (e) {
-            // Last resort: prompt() so the user can copy manually.
-            try { window.prompt('Copy this link:', url); } catch (_) {}
-          }
-        });
-      })();
-      </script>
-      <script>
-      (function(){
-        function syncCtaWidth(){
-          var saved = JSON.parse(localStorage.getItem('wallco.material') || 'null');
-          if (!saved || !saved.w) return;
-          var link = document.getElementById('cta-sample-link');
-          if (!link) return;
-          var base = link.getAttribute('href').split('?')[0];
-          var q = new URLSearchParams(link.getAttribute('href').split('?')[1] || '');
-          q.set('width', saved.w);
-          link.setAttribute('href', base + '?' + q.toString());
-        }
-        syncCtaWidth();
-        document.querySelectorAll('.w-tab').forEach(function(t){
-          t.addEventListener('click', function(){ setTimeout(syncCtaWidth, 0); });
-        });
-      })();
-      </script>
-
-      <!-- Pair-with sibling palette neighbors -->
-      <div class="pair-with-block" style="margin-top:32px">
-        <h3 style="font-family:var(--serif);font-weight:300;font-size:20px;margin:0 0 4px">Pair this with</h3>
-        <p style="font-size:12px;color:var(--ink-faint);margin:0 0 14px;letter-spacing:.04em">Sibling designs by palette · nearest neighbors in CIE Lab space</p>
-        <div id="pair-with-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;min-height:120px">
-          <div style="grid-column:1/-1;color:var(--ink-faint);font-size:12px">Loading pairings…</div>
-        </div>
-      </div>
-      <script>
-      (function(){
-        var id = ${design.id};
-        fetch('/api/design/' + id + '/pairings').then(r=>r.json()).then(function(d){
-          var g = document.getElementById('pair-with-grid');
-          if (!d.designs || !d.designs.length) { g.innerHTML = '<div style="grid-column:1/-1;color:var(--ink-faint);font-size:12px">No close pairings yet — catalog is still seeding.</div>'; return; }
-          g.innerHTML = d.designs.map(function(p){
-            return '<a href="/design/' + p.id + '" style="text-decoration:none;color:inherit;display:block">'
-              + '<div style="aspect-ratio:1;background:#0a0907 no-repeat center/cover;background-image:url(\'' + p.image_url + '\');border-radius:6px;border:1px solid var(--line);transition:transform .15s;"></div>'
-              + '<div style="margin-top:6px;display:flex;align-items:center;gap:6px"><span style="width:9px;height:9px;border-radius:50%;background:' + p.dominant_hex + ';border:1px solid rgba(0,0,0,.12)"></span><span style="font-size:11px;color:var(--ink-soft)">Lab Δ ' + p.distance.toFixed(1) + '</span></div>'
-              + '</a>';
-          }).join('');
-        }).catch(function(e){
-          document.getElementById('pair-with-grid').innerHTML = '<div style="grid-column:1/-1;color:var(--ink-faint);font-size:12px">Pairings unavailable.</div>';
-        });
-      })();
-      </script>
-
-      ${_isAdmin ? `
-      <!-- Admin: isolate colors (palette + SW + coordinating) -->
-      <div id="iso-block" style="margin-top:28px;padding:18px;border:1px solid var(--line);border-radius:10px;background:rgba(0,0,0,.02)">
-        <div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;margin-bottom:10px;flex-wrap:wrap">
-          <h3 style="font-family:var(--serif);font-weight:300;font-size:18px;margin:0">Color isolate</h3>
-          <span style="font-size:11px;color:var(--ink-faint);letter-spacing:.04em">Admin-only · palette + Sherwin-Williams + coordinating</span>
-        </div>
-        <div id="iso-palette-detail" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px"><span style="color:var(--ink-faint);font-size:12px">loading palette…</span></div>
-        <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
-          <button id="iso-isolate-btn" type="button" style="padding:9px 16px;background:#1a1816;color:#d2b15c;border:1px solid #d2b15c;border-radius:3px;cursor:pointer;font:13px/1 var(--sans);font-weight:600;letter-spacing:.06em;text-transform:uppercase">Isolate this design</button>
-          <div id="iso-mode-detail" style="display:none;gap:0;border:1px solid var(--line);border-radius:3px;overflow:hidden">
-            <button type="button" data-mode="wallpaper" style="padding:8px 14px;background:#1a1816;color:#fff;border:none;cursor:pointer;font:11px/1 var(--sans);letter-spacing:.06em;text-transform:uppercase">Wallpaper</button>
-            <button type="button" data-mode="paint" style="padding:8px 14px;background:transparent;color:var(--ink-soft);border:none;cursor:pointer;font:11px/1 var(--sans);letter-spacing:.06em;text-transform:uppercase">Paint</button>
-          </div>
-        </div>
-        <div id="iso-section-wp-detail" style="display:none;margin-top:14px">
-          <div style="font-size:11px;color:var(--ink-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px">Coordinating wallpapers</div>
-          <div id="iso-grid-wp-detail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px"></div>
-        </div>
-        <div id="iso-section-pt-detail" style="display:none;margin-top:14px">
-          <div style="font-size:11px;color:var(--ink-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px">Coordinating paint colors</div>
-          <div id="iso-grid-pt-detail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px"></div>
-        </div>
-      </div>
-      <script>
-      (function(){
-        var ID = ${design.id};
-        var DATA = null;
-        function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g, function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
-        var palEl = document.getElementById('iso-palette-detail');
-        var btn   = document.getElementById('iso-isolate-btn');
-        var mode  = document.getElementById('iso-mode-detail');
-        var wpSec = document.getElementById('iso-section-wp-detail');
-        var ptSec = document.getElementById('iso-section-pt-detail');
-        var wpG   = document.getElementById('iso-grid-wp-detail');
-        var ptG   = document.getElementById('iso-grid-pt-detail');
-        function renderPal(palette){
-          if (!palette || !palette.length) { palEl.innerHTML = '<span style="color:var(--ink-faint);font-size:12px">no palette</span>'; return; }
-          palEl.innerHTML = palette.map(function(p){
-            var sw = p.sw;
-            return '<div style="display:flex;flex-direction:column;align-items:center;gap:4px;min-width:80px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:4px">' +
-                   '<div style="width:48px;height:48px;border-radius:50%;background:'+esc(p.hex)+';border:1px solid rgba(0,0,0,.08)"></div>' +
-                   '<div style="font-family:ui-monospace,monospace;font-size:10px;color:var(--ink)">'+esc(p.hex)+'</div>' +
-                   (sw ? '<div style="font-size:10px;color:#a67b1f">'+esc(sw.code)+'</div>'+
-                         '<div style="font-size:10px;color:var(--ink-faint);text-align:center;line-height:1.2;max-width:90px">'+esc(sw.name)+'</div>'
-                       : '<div style="font-size:10px;color:var(--ink-faint);font-style:italic">—</div>') +
-                   '</div>';
-          }).join('');
-        }
-        function renderWP(designs){
-          if (!designs || !designs.length) { wpG.innerHTML = '<div style="grid-column:1/-1;color:var(--ink-faint);font-size:12px">no coordinating wallpapers yet</div>'; return; }
-          wpG.innerHTML = designs.map(function(d){
-            var url = (d.image_url||'').replace(/'/g,'');
-            return '<a href="/design/'+d.id+'" style="display:block;aspect-ratio:1/1;background:#0d0c0a center/cover url('+url+');border-radius:3px;border:1px solid var(--line);position:relative">' +
-                   '<span style="position:absolute;bottom:2px;right:3px;font:9px/1 ui-monospace,monospace;color:#fff;background:rgba(0,0,0,.6);padding:1px 4px;border-radius:2px">ΔE '+(d.deltaE!=null?d.deltaE.toFixed(1):'?')+'</span></a>';
-          }).join('');
-        }
-        function renderPT(coord){
-          if (!coord || !Object.keys(coord).length) { ptG.innerHTML = '<div style="grid-column:1/-1;color:var(--ink-faint);font-size:12px">no coordinating colors</div>'; return; }
-          var labelMap = {complement:'Complement',analogousLeft:'Analogous −30°',analogousRight:'Analogous +30°',triadicA:'Triadic +120°',triadicB:'Triadic +240°',splitComplementA:'Split-comp +150°',splitComplementB:'Split-comp +210°'};
-          ptG.innerHTML = Object.keys(coord).map(function(k){
-            var v = coord[k], sw = v.sw;
-            return '<div style="display:flex;flex-direction:column;gap:5px;padding:8px;background:#fff;border:1px solid var(--line);border-radius:4px">' +
-                   '<div style="width:100%;aspect-ratio:1/1;background:'+esc(v.hex)+';border-radius:3px;border:1px solid rgba(0,0,0,.08)"></div>' +
-                   '<div style="font-size:9px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">'+esc(labelMap[k]||k)+'</div>' +
-                   '<div style="font-family:ui-monospace,monospace;font-size:11px;color:var(--ink)">'+esc(v.hex)+'</div>' +
-                   (sw ? '<div style="font-size:10px;color:#a67b1f">'+esc(sw.code)+'</div>'+
-                         '<div style="font-size:10px;color:var(--ink-soft);line-height:1.2">'+esc(sw.name)+'</div>'
-                       : '<div style="font-size:10px;color:var(--ink-faint);font-style:italic">SW DB not loaded</div>') +
-                   '<button type="button" data-copy="'+esc(v.hex)+'" style="margin-top:4px;padding:4px 6px;background:#fff;border:1px solid var(--line);color:var(--ink-soft);font:10px/1 var(--sans);cursor:pointer;border-radius:2px">copy hex</button>' +
-                   '</div>';
-          }).join('');
-          ptG.querySelectorAll('button[data-copy]').forEach(function(b){
-            b.addEventListener('click', function(){ navigator.clipboard.writeText(b.dataset.copy).then(function(){ b.textContent='copied!'; setTimeout(function(){b.textContent='copy hex';},1200); }).catch(function(){ b.textContent='copy failed';}); });
-          });
-        }
-        function load(){
-          fetch('/api/isolate/' + ID).then(function(r){ return r.ok ? r.json() : Promise.reject(r.status); }).then(function(d){
-            DATA = d;
-            renderPal(d.palette || []);
-          }).catch(function(err){
-            palEl.innerHTML = '<span style="color:var(--ink-faint);font-size:12px">failed: '+esc(err)+'</span>';
-          });
-        }
-        btn.addEventListener('click', function(){
-          if (!DATA) return;
-          mode.style.display = 'flex';
-          wpSec.style.display = 'block';
-          ptSec.style.display = 'none';
-          btn.disabled = true; btn.style.opacity = '.55'; btn.style.cursor = 'default';
-          btn.textContent = 'Isolated · pick a mode';
-          renderWP(DATA.coordinating_designs || []);
-          renderPT(DATA.coordinating_hexes || {});
-        });
-        mode.addEventListener('click', function(e){
-          var b = e.target.closest('button[data-mode]'); if (!b) return;
-          var m = b.dataset.mode;
-          Array.from(mode.querySelectorAll('button')).forEach(function(x){
-            var on = x === b;
-            x.style.background = on ? '#1a1816' : 'transparent';
-            x.style.color = on ? '#fff' : 'var(--ink-soft)';
-          });
-          wpSec.style.display = m === 'wallpaper' ? 'block' : 'none';
-          ptSec.style.display = m === 'paint' ? 'block' : 'none';
-        });
-        load();
-      })();
-      </script>
-
-      <!-- Admin: rate this design (RLHF-lite for the generator) -->
-      <div id="rate-block" style="margin-top:28px;padding:18px;border:1px solid var(--line);border-radius:10px;background:rgba(0,0,0,.02)">
-        <h3 style="font-family:var(--serif);font-weight:300;font-size:18px;margin:0 0 4px">Rate this design</h3>
-        <p style="font-size:11px;color:var(--ink-faint);margin:0 0 14px;letter-spacing:.04em">Admin-only · color · style · composition · 1-10 · feeds the next retry</p>
-        <div class="rate-rows" style="display:grid;grid-template-columns:120px 1fr 36px;gap:10px;align-items:center;font-size:13px">
-          <label for="r-color">Color</label>
-          <input id="r-color" type="range" min="1" max="10" step="1" value="7">
-          <span id="r-color-v">7</span>
-          <label for="r-style">Style</label>
-          <input id="r-style" type="range" min="1" max="10" step="1" value="7">
-          <span id="r-style-v">7</span>
-          <label for="r-comp">Composition</label>
-          <input id="r-comp" type="range" min="1" max="10" step="1" value="7">
-          <span id="r-comp-v">7</span>
-          <label for="r-space">Spacing<br><small style="color:var(--ink-faint)">tighter ⟷ wider</small></label>
-          <input id="r-space" type="range" min="0" max="100" step="5" value="50">
-          <span id="r-space-v">50</span>
-          <label for="r-invert">Invert palette</label>
-          <label style="display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ink-soft)"><input id="r-invert" type="checkbox"> swap dominant/recessive</label>
-          <span></span>
-        </div>
-        <textarea id="r-notes" placeholder="Notes for the model (optional)" style="width:100%;margin-top:10px;padding:8px;border:1px solid var(--line);border-radius:6px;font-family:inherit;font-size:13px;min-height:44px"></textarea>
-        <div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">
-          <button type="button" class="btn-outline" id="btn-rate-save">Save rating</button>
-          <button type="button" class="btn-outline" id="btn-rate-retry">Save + try again</button>
-          <span id="rate-status" style="font-size:12px;color:var(--ink-faint);align-self:center"></span>
-        </div>
-        <details id="rate-history" style="margin-top:14px">
-          <summary style="cursor:pointer;font-size:12px;color:var(--ink-soft)">Rating history (overrides on top)</summary>
-          <div id="rate-history-list" style="margin-top:8px;font-size:12px;color:var(--ink-soft)">Loading…</div>
-        </details>
-      </div>
-      <script>
-      (function(){
-        var id = ${design.id};
-        function vof(x){ return document.getElementById(x).value; }
-        function bind(slider, out){ var s=document.getElementById(slider), o=document.getElementById(out); s.addEventListener('input',function(){o.textContent=s.value;}); }
-        bind('r-color','r-color-v'); bind('r-style','r-style-v'); bind('r-comp','r-comp-v'); bind('r-space','r-space-v');
-        function payload(){return{color:+vof('r-color'),style:+vof('r-style'),composition:+vof('r-comp'),spacing:+vof('r-space'),invert:document.getElementById('r-invert').checked,notes:vof('r-notes')||null};}
-        function setStatus(t,isErr){var s=document.getElementById('rate-status');s.textContent=t;s.style.color=isErr?'#b00020':'var(--ink-faint)';}
-        async function post(url, body){var r=await fetch(url+'?review=1',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});return r.json();}
-        document.getElementById('btn-rate-save').addEventListener('click', async function(){
-          setStatus('Saving…');
-          var j = await post('/api/design/'+id+'/rate', payload());
-          setStatus(j.ok ? 'Saved rating #' + j.rating_id : ('Error: ' + (j.error||'?')), !j.ok);
-          if (j.ok) loadHistory();
-        });
-        document.getElementById('btn-rate-retry').addEventListener('click', async function(){
-          setStatus('Saving + queueing retry…');
-          var j = await post('/api/design/'+id+'/rate', payload());
-          if (!j.ok) { setStatus('Error: '+(j.error||'?'), true); return; }
-          var k = await post('/api/design/'+id+'/retry', {});
-          setStatus(k.ok ? ('Retry queued — nudges: ' + (k.nudges||[]).join(' · ')) : ('Retry error: '+(k.error||'?')), !k.ok);
-          loadHistory();
-        });
-        async function loadHistory(){
-          try {
-            var r = await fetch('/api/design/'+id+'/ratings?review=1').then(r=>r.json());
-            var list = document.getElementById('rate-history-list');
-            if (!r.ratings || !r.ratings.length) { list.textContent = 'No ratings yet.'; return; }
-            list.innerHTML = r.ratings.map(function(x){
-              var when = (x.created_at || '').replace('T',' ').slice(0,16);
-              return '<div style="padding:6px 0;border-top:1px dashed var(--line)">'+
-                when + ' — C:' + x.color_score + ' S:' + x.style_score + ' Co:' + x.composition_score +
-                ' space:' + x.spacing + (x.invert ? ' invert' : '') +
-                (x.retry_count > 0 ? ' · ' + x.retry_count + ' retr' + (x.retry_count===1?'y':'ies') : '') +
-                (x.notes ? '<div style="color:var(--ink-faint)">'+ (x.notes||'').replace(/</g,'&lt;') +'</div>' : '') +
-                '</div>';
-            }).join('');
-          } catch(e) {}
-        }
-        loadHistory();
-      })();
-      </script>
-      ` : ''}
-
-      ${roomCtx.length ? `
-      <!-- Room mockup with context toggle (residential / commercial / hospitality) -->
-      <div class="room-toggle" style="margin:22px 0 4px">
-        <div class="room-pills" role="tablist" style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">
-          ${roomCtx.map((c, i) => `
-            <button type="button" role="tab"
-                    class="room-pill${i===0?' active':''}"
-                    data-room="${c.room}"
-                    aria-selected="${i===0?'true':'false'}"
-                    style="padding:7px 14px;border-radius:18px;border:1px solid rgba(0,0,0,.15);background:${i===0?'#1a1816':'transparent'};color:${i===0?'#fff':'inherit'};font:inherit;font-size:12.5px;letter-spacing:.3px;cursor:pointer;transition:all .18s">${c.label}</button>
-          `).join('')}
-        </div>
-        <figure style="margin:0;border-radius:10px;overflow:hidden;background:rgba(0,0,0,.04)">
-          <img id="room-toggle-img"
-               src="/designs/room/design_${design.id}_${roomCtx[0].room}.png"
-               alt="${design.title} — ${roomCtx[0].label.toLowerCase()}"
-               loading="lazy"
-               style="display:block;width:100%;height:auto;border-radius:10px 10px 0 0">
-          <figcaption id="room-toggle-cap" style="font-size:12px;color:var(--ink-faint);padding:8px 12px">${roomCtx[0].label} · ${roomCtx[0].room.replace(/_/g,' ')} — AI rendering</figcaption>
-        </figure>
-        <script>
-        (function(){
-          var pills = document.querySelectorAll('.room-pill');
-          var img   = document.getElementById('room-toggle-img');
-          var cap   = document.getElementById('room-toggle-cap');
-          var did   = ${design.id};
-          var labels = ${JSON.stringify(Object.fromEntries(roomCtx.map(c => [c.room, c.label])))};
-          pills.forEach(function(p){
-            p.addEventListener('click', function(){
-              pills.forEach(function(x){
-                x.classList.remove('active');
-                x.setAttribute('aria-selected','false');
-                x.style.background = 'transparent';
-                x.style.color = 'inherit';
-              });
-              p.classList.add('active');
-              p.setAttribute('aria-selected','true');
-              p.style.background = '#1a1816';
-              p.style.color = '#fff';
-              var room = p.dataset.room;
-              img.src = '/designs/room/design_' + did + '_' + room + '.png';
-              img.alt = ${JSON.stringify(design.title)} + ' — ' + (labels[room] || '').toLowerCase();
-              cap.textContent = (labels[room]||'') + ' · ' + room.replace(/_/g,' ') + ' — AI rendering';
-            });
-          });
-        })();
-        </script>
-      </div>
-      ` : ''}
-
-      <!-- Tools (admin / preview) -->
-      <div class="tool-group" style="margin-top:18px;display:flex;flex-wrap:wrap;gap:10px">
-        <button type="button" class="btn-outline" id="btn-room"        data-id="${design.id}">See in a Room</button>
-        <button type="button" class="btn-outline" id="btn-spoon-dry"   data-id="${design.id}">Spoonflower (dry-run)</button>
-        <button type="button" class="btn-outline" id="btn-spoon-live"  data-id="${design.id}">Spoonflower — Upload as New</button>
-        <button type="button" class="btn-outline" id="btn-shopify"     data-id="${design.id}">Upload to Shopify</button>
-        <button type="button" class="btn-outline" id="btn-narrate"     data-id="${design.id}">Narrate (ElevenLabs)</button>
-      </div>
-      <div style="margin-top:8px;font:11px/1.4 var(--sans);color:#666">
-        DW Original Number:
-        <code style="font:12px ui-monospace,Menlo,monospace;background:rgba(0,0,0,.05);padding:2px 6px;border-radius:3px">${design.dig_number || 'DIG_AI_' + String(design.id).padStart(6,'0')}</code>
-      </div>
-      <div id="tool-result" style="margin-top:16px;display:none;padding:14px;border-radius:6px;background:rgba(0,0,0,.05);font-size:13px;line-height:1.5"></div>
-      <div id="room-result" style="margin-top:14px;display:none">
-        <img id="room-img" alt="Room preview" style="max-width:100%;border-radius:8px;border:1px solid rgba(0,0,0,.08)">
-      </div>
-      <audio id="narrate-audio" controls style="margin-top:10px;display:none;width:100%"></audio>
-
-      <script>
-      (function(){
-        var R = document.getElementById('tool-result');
-        function show(msg, isErr) {
-          R.style.display='block';
-          R.style.background = isErr ? '#f8d7da' : '#d4edda';
-          R.style.color = isErr ? '#721c24' : '#155724';
-          R.innerHTML = msg;
-        }
-        async function post(url, body) {
-          var r = await fetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
-          return r.json();
-        }
-
-        document.getElementById('btn-room').addEventListener('click', async function(){
-          show('Generating room preview… (10–30s)', false);
-          this.disabled = true;
-          var j = await post('/api/room', { design_id: this.dataset.id, roomType:'bedroom', angle:'straight_on', cameraDistance:5 });
-          this.disabled = false;
-          if (j.ok) {
-            show('Room preview ready.', false);
-            var img = document.getElementById('room-img');
-            img.src = j.image_url + '?t=' + Date.now();
-            document.getElementById('room-result').style.display = 'block';
-          } else {
-            show('Room generation failed: ' + (j.error||'unknown'), true);
-          }
-        });
-
-        document.getElementById('btn-spoon-dry').addEventListener('click', async function(){
-          show('Spoonflower dry-run: logging in + checking upload page…', false);
-          this.disabled = true;
-          var j = await post('/api/spoonflower/upload', { design_id: this.dataset.id, dry: true });
-          this.disabled = false;
-          if (j.ok) show('Dry-run OK. job=' + j.job_id + '. <a href="/api/spoonflower/status/' + j.job_id + '" target="_blank" rel="noopener noreferrer">view log</a>', false);
-          else show('Dry-run failed: ' + (j.error||'unknown'), true);
-        });
-
-        document.getElementById('btn-spoon-live').addEventListener('click', async function(){
-          if (!confirm('Upload this design to Spoonflower as a NEW product? This is a public publish.')) return;
-          var btn = this;
-          var designId = this.dataset.id;
-          show('Live upload running… (Spoonflower publish typically takes 60–180s)', false);
-          btn.disabled = true;
-          var j = await post('/api/spoonflower/upload', { design_id: designId, dry: false });
-          if (!j.ok) {
-            btn.disabled = false;
-            show('Upload failed: ' + (j.error||'unknown'), true);
-            return;
-          }
-          var jobId = j.job_id;
-          show('Upload kicked off (job ' + jobId + '). Polling for the new Spoonflower pattern number…', false);
-          // Poll /api/spoonflower/status/:job until the python script
-          // finishes. 5s cadence, hard cap at 6 minutes.
-          var started = Date.now();
-          var poll = setInterval(async function(){
-            try {
-              var r = await fetch('/api/spoonflower/status/' + jobId);
-              var s = await r.json();
-              var st = s && s.job && s.job.status;
-              if (st === 'done' || st === 'error') {
-                clearInterval(poll);
-                btn.disabled = false;
-                if (st === 'error') {
-                  show('Spoonflower upload errored. <a href="/api/spoonflower/status/' + jobId + '" target="_blank" rel="noopener noreferrer">view log</a>', true);
-                  return;
-                }
-                if (s.sf_design_id) {
-                  var link = s.sf_public_url || ('https://www.spoonflower.com/en/shop/wallpaper/' + s.sf_design_id);
-                  show(
-                    '<strong>Spoonflower pattern #' + s.sf_design_id + '</strong> created. ' +
-                    '<a href="' + link + '" target="_blank" rel="noopener noreferrer">View on Spoonflower ↗</a><br>' +
-                    '<span style="font-size:12px;color:#3a3631">Next: click <b>Upload to Shopify</b> below to publish to designer-laboratory-sandbox.</span>',
-                    false
-                  );
-                  var sb = document.getElementById('btn-shopify');
-                  if (sb) { sb.style.borderColor = '#c9a14b'; sb.style.background = '#fff8e6'; }
-                } else {
-                  show('Upload finished but Spoonflower pattern ID could not be parsed. <a href="/api/spoonflower/status/' + jobId + '" target="_blank" rel="noopener noreferrer">view log</a>', true);
-                }
-                return;
-              }
-              if (Date.now() - started > 6 * 60 * 1000) {
-                clearInterval(poll);
-                btn.disabled = false;
-                show('Upload still running after 6 min — check <a href="/api/spoonflower/status/' + jobId + '" target="_blank" rel="noopener noreferrer">job log</a> manually.', true);
-              }
-            } catch (e) { /* swallow transient poll errors */ }
-          }, 5000);
-        });
-
-        // Upload to Shopify — admin only (endpoint 404s for non-admins).
-        var shopifyBtn = document.getElementById('btn-shopify');
-        if (shopifyBtn) {
-          shopifyBtn.addEventListener('click', async function(){
-            if (!confirm('Create a new Shopify product on designer-laboratory-sandbox for this design? It will land as DRAFT.')) return;
-            var btn = this;
-            btn.disabled = true;
-            show('Creating Shopify product…', false);
-            try {
-              var r = await fetch('/api/design/' + btn.dataset.id + '/upload-shopify', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({})
-              });
-              var j = await r.json();
-              if (r.ok && j.ok) {
-                show(
-                  '<strong>Shopify product ' + j.shopify_product_id + '</strong> created ' +
-                  (j.already_uploaded ? '(already existed)' : '(DRAFT)') + '. ' +
-                  'SKU <code>' + (j.dig_number || '') + '</code>. ' +
-                  '<a href="' + j.shopify_product_url + '" target="_blank" rel="noopener noreferrer">View on store ↗</a>' +
-                  (j.shopify_admin_url ? ' · <a href="' + j.shopify_admin_url + '" target="_blank" rel="noopener noreferrer">admin ↗</a>' : ''),
-                  false
-                );
-              } else {
-                show('Shopify upload failed: ' + (j.error || (j.shopify_response && JSON.stringify(j.shopify_response).slice(0,300)) || 'unknown'), true);
-              }
-            } catch (e) { show('Shopify upload error: ' + e.message, true); }
-            btn.disabled = false;
-          });
-        }
-
-        document.getElementById('btn-narrate').addEventListener('click', async function(){
-          show('Generating narration…', false);
-          this.disabled = true;
-          try {
-            var r = await fetch('/api/voice/preview?design_id=' + this.dataset.id);
-            if (!r.ok) { var t = await r.text(); show('Narration failed: ' + t, true); this.disabled=false; return; }
-            var blob = await r.blob();
-            var a = document.getElementById('narrate-audio');
-            a.src = URL.createObjectURL(blob);
-            a.style.display = 'block';
-            a.play();
-            show('Narration ready.', false);
-          } catch(e) { show('Narration error: ' + e.message, true); }
-          this.disabled = false;
-        });
-      })();
-      </script>
-
-      <!-- ── EDIT PANEL: hue/sat/vibrance/palette/dots/wheel ────────────────── -->
-      <script>
-      (function(){
-        var img       = document.getElementById('detail-img');
-        var canvas    = document.getElementById('detail-canvas');
-        var modal     = document.getElementById('edit-modal');
-        var hamburger = document.getElementById('edit-hamburger');
-        if (!img || !modal || !hamburger) return;
-
-        function openModal(){
-          modal.hidden = false;
-          hamburger.setAttribute('aria-expanded','true');
-          document.body.style.overflow = 'hidden';
-        }
-        function closeModal(){
-          modal.hidden = true;
-          hamburger.setAttribute('aria-expanded','false');
-          document.body.style.overflow = '';
-        }
-        hamburger.addEventListener('click', openModal);
-        modal.addEventListener('click', function(e){
-          if (e.target.getAttribute('data-close') === '1') closeModal();
-        });
-        document.addEventListener('keydown', function(e){
-          if (e.key === 'Escape' && !modal.hidden) closeModal();
-        });
-
-        // Tab switching
-        document.querySelectorAll('.edit-tab').forEach(function(t){
-          t.addEventListener('click', function(){
-            var key = this.getAttribute('data-tab');
-            document.querySelectorAll('.edit-tab').forEach(function(x){ x.classList.toggle('active', x === t); });
-            document.querySelectorAll('.edit-tabpane').forEach(function(p){
-              p.classList.toggle('active', p.getAttribute('data-pane') === key);
-            });
-          });
-        });
-
-        // ── AI Interior Designer panel (admin-only) ────────────────────────
-        (function(){
-          var params = new URLSearchParams(window.location.search);
-          var adminToken = params.get('admin') || localStorage.getItem('wallco_admin_token') || '';
-          if (params.get('admin')) localStorage.setItem('wallco_admin_token', adminToken);
-          if (!adminToken) return;
-          var block = document.getElementById('ai-designer-block');
-          if (!block) return;
-          block.style.display = 'block';
-
-          var designId = parseInt(document.body.dataset.designId || '0', 10);
-          if (!designId) {
-            var sec = document.getElementById('rooms-gallery');
-            if (sec && sec.dataset.designId) designId = parseInt(sec.dataset.designId, 10);
-          }
-          if (!designId) {
-            // Last-ditch fallback — parse from URL
-            var m = window.location.pathname.match(/\/design\/(\d+)/);
-            if (m) designId = parseInt(m[1], 10);
-          }
-
-          var runBtn   = document.getElementById('ai-designer-run');
-          var applyBtn = document.getElementById('ai-designer-apply');
-          var status   = document.getElementById('ai-designer-status');
-          var result   = document.getElementById('ai-designer-result');
-          var seamWarn = document.getElementById('ai-designer-seam-warn');
-          var lastSuggestion = null;
-
-          async function runAI(apply){
-            status.textContent = apply ? 'Saving…' : 'Asking Gemini…';
-            status.style.color = '#555';
-            try {
-              var r = await fetch('/api/design/' + designId + '/ai-designer', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken },
-                body: JSON.stringify({ apply: !!apply })
-              });
-              var j = await r.json();
-              if (!r.ok) {
-                if (j.error === 'SETTLEMENT_VIOLATION') {
-                  status.style.color = '#b00020';
-                  status.textContent = '⚠ ' + j.message;
-                } else {
-                  status.style.color = '#b00020';
-                  status.textContent = 'Error: ' + (j.error || r.statusText);
-                }
-                return;
-              }
-              lastSuggestion = j.suggested;
-              result.style.display = 'block';
-              document.getElementById('ai-d-title').value   = j.suggested.title    || '';
-              document.getElementById('ai-d-pattern').value = j.suggested.pattern  || '';
-              document.getElementById('ai-d-color').value   = j.suggested.color_name || '';
-              document.getElementById('ai-d-dig').value     = j.suggested.dig_number || '';
-              document.getElementById('ai-d-tags').value    = (j.suggested.tags || []).join(', ');
-              document.getElementById('ai-d-notes').textContent = j.suggested.designer_notes || '';
-              seamWarn.style.display = j.suggested.seam_visible ? 'block' : 'none';
-              status.style.color = '#1a3';
-              status.textContent = apply ? '✓ Saved' : 'Review & Apply →';
-            } catch (e) {
-              status.style.color = '#b00020';
-              status.textContent = 'Error: ' + e.message;
-            }
-          }
-
-          runBtn.addEventListener('click', function(){ runAI(false); });
-          applyBtn.addEventListener('click', function(){ runAI(true); });
-        })();
-
-        // ── 1. CSS-filter live adjustments ─────────────────────────────────
-        var previewImg = document.getElementById('edit-preview-img');
-        var adjState = { hue:0, sat:100, vib:100, bri:100, con:100, opa:100, sep:0, blur:0 };
-        function applyFilter() {
-          var vibBoost = (adjState.vib - 100) / 200;
-          var filter =
-            'hue-rotate(' + adjState.hue + 'deg) ' +
-            'saturate(' + (adjState.sat / 100) + ') ' +
-            'brightness(' + (adjState.bri / 100) + ') ' +
-            'contrast(' + (adjState.con / 100) + ') ' +
-            'sepia(' + (adjState.sep / 100) + ') ' +
-            'blur(' + adjState.blur + 'px) ' +
-            'saturate(' + (1 + vibBoost) + ')';
-          var opacity = adjState.opa / 100;
-          img.style.filter = filter;
-          img.style.opacity = opacity;
-          if (previewImg) { previewImg.style.filter = filter; previewImg.style.opacity = opacity; }
-          var ro = document.getElementById('edit-preview-readout');
-          if (ro) ro.textContent =
-            'Hue ' + adjState.hue + '°  ·  Sat ' + adjState.sat + '%  ·  Vib ' + adjState.vib +
-            '%  ·  Bri ' + adjState.bri + '%  ·  Con ' + adjState.con +
-            '%  ·  Opa ' + adjState.opa + '%  ·  Sep ' + adjState.sep +
-            '%  ·  Blur ' + adjState.blur + 'px';
-        }
-        var sliders = [
-          ['hue',  '°'], ['sat','%'], ['vib','%'], ['bri','%'],
-          ['con','%'],   ['opa','%'], ['sep','%'], ['blur','px']
-        ];
-        sliders.forEach(function(s){
-          var el = document.getElementById('adj-' + s[0]);
-          var v  = document.getElementById('adj-' + s[0] + '-v');
-          if (!el || !v) return;
-          el.addEventListener('input', function(){
-            adjState[s[0]] = parseFloat(this.value);
-            v.textContent = this.value + s[1];
-            applyFilter();
-          });
-        });
-        document.getElementById('adj-reset').addEventListener('click', function(){
-          adjState = { hue:0, sat:100, vib:100, bri:100, con:100, opa:100, sep:0, blur:0 };
-          sliders.forEach(function(s){
-            var el = document.getElementById('adj-' + s[0]);
-            var v  = document.getElementById('adj-' + s[0] + '-v');
-            var def = { hue:0, sat:100, vib:100, bri:100, con:100, opa:100, sep:0, blur:0 }[s[0]];
-            if (el) el.value = def;
-            if (v) v.textContent = def + s[1];
-          });
-          applyFilter();
-        });
-
-        // ── 2. Palette extraction via canvas ───────────────────────────────
-        var PALETTE = []; // [{hex, count, r, g, b, h, s, l}]
-        var selectedDot = null;
-
-        function rgbToHex(r,g,b){ return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join(''); }
-        function hexToRgb(h){ var n=parseInt(h.replace('#',''),16); return [(n>>16)&255,(n>>8)&255,n&255]; }
-        function rgbToHsl(r,g,b){
-          r/=255; g/=255; b/=255;
-          var max=Math.max(r,g,b), min=Math.min(r,g,b), l=(max+min)/2, h=0, s=0;
-          if (max!==min) {
-            var d=max-min;
-            s = l>0.5 ? d/(2-max-min) : d/(max+min);
-            if (max===r) h = ((g-b)/d + (g<b?6:0));
-            else if (max===g) h = (b-r)/d + 2;
-            else h = (r-g)/d + 4;
-            h *= 60;
-          }
-          return [h, s, l];
-        }
-
-        function extractPalette(n) {
-          var iw = img.naturalWidth, ih = img.naturalHeight;
-          if (!iw) return [];
-          var scale = Math.min(64 / iw, 64 / ih, 1);
-          var w = Math.max(8, Math.round(iw * scale));
-          var h = Math.max(8, Math.round(ih * scale));
-          canvas.width = w; canvas.height = h;
-          var ctx = canvas.getContext('2d');
-          ctx.drawImage(img, 0, 0, w, h);
-          var data;
-          try { data = ctx.getImageData(0, 0, w, h).data; }
-          catch(e) { return []; } // CORS
-          // Bucket by 6-bit color (4*4*4=64 buckets) — fast color quantization
-          var buckets = {};
-          for (var i = 0; i < data.length; i += 4) {
-            var r = data[i], g = data[i+1], b = data[i+2];
-            var key = (r >> 5) + '_' + (g >> 5) + '_' + (b >> 5);
-            if (!buckets[key]) buckets[key] = {r:0,g:0,b:0,count:0};
-            buckets[key].r += r; buckets[key].g += g; buckets[key].b += b; buckets[key].count++;
-          }
-          var palette = Object.values(buckets).map(function(b){
-            return {
-              r: Math.round(b.r/b.count), g: Math.round(b.g/b.count), b: Math.round(b.b/b.count),
-              count: b.count
-            };
-          }).sort(function(a,b){ return b.count - a.count; }).slice(0, n);
-          return palette.map(function(p){
-            var hex = rgbToHex(p.r, p.g, p.b);
-            var hsl = rgbToHsl(p.r, p.g, p.b);
-            return { hex:hex, r:p.r, g:p.g, b:p.b, count:p.count, h:hsl[0], s:hsl[1], l:hsl[2] };
-          });
-        }
-
-        function renderDots(sort) {
-          var c = PALETTE.slice();
-          if (sort === 'hex')   c.sort(function(a,b){ return a.hex.localeCompare(b.hex); });
-          if (sort === 'light') c.sort(function(a,b){ return b.l - a.l; });
-          if (sort === 'hue')   c.sort(function(a,b){ return a.h - b.h; });
-          var container = document.getElementById('palette-dots');
-          container.innerHTML = c.map(function(p, i){
-            return '<div class="palette-dot" draggable="true" data-hex="' + p.hex + '" style="background:' + p.hex + '" title="' + p.hex + ' · ' + p.count + 'px"><span class="dot-label">' + p.hex + '</span></div>';
-          }).join('');
-          // Wire up drag-drop + click-select
-          container.querySelectorAll('.palette-dot').forEach(function(dot){
-            dot.addEventListener('click', function(){
-              container.querySelectorAll('.palette-dot.selected').forEach(function(d){ d.classList.remove('selected'); });
-              dot.classList.add('selected');
-              selectedDot = dot.getAttribute('data-hex');
-              document.getElementById('wheel-readout').textContent =
-                'Selected ' + selectedDot + ' — click wheel or a suggested swatch to recolor';
-              renderSuggested(selectedDot);
-            });
-            dot.addEventListener('dragstart', function(e){ e.dataTransfer.setData('text/hex', dot.getAttribute('data-hex')); });
-            dot.addEventListener('dragover', function(e){ e.preventDefault(); dot.classList.add('drag-over'); });
-            dot.addEventListener('dragleave', function(){ dot.classList.remove('drag-over'); });
-            dot.addEventListener('drop', function(e){
-              e.preventDefault();
-              dot.classList.remove('drag-over');
-              var src = e.dataTransfer.getData('text/hex');
-              var tgt = dot.getAttribute('data-hex');
-              if (src && tgt && src !== tgt) recolor(tgt, src);
-            });
-          });
-        }
-
-        document.querySelectorAll('.dots-sort').forEach(function(btn){
-          btn.addEventListener('click', function(){
-            document.querySelectorAll('.dots-sort').forEach(function(b){ b.classList.remove('active'); });
-            btn.classList.add('active');
-            renderDots(btn.getAttribute('data-sort'));
-          });
-        });
-
-        // ── 3. Recolor a palette bucket — canvas pixel replace ─────────────
-        function recolor(fromHex, toHex) {
-          var iw = img.naturalWidth, ih = img.naturalHeight;
-          if (!iw) return;
-          canvas.width = iw; canvas.height = ih;
-          var ctx = canvas.getContext('2d');
-          ctx.drawImage(img, 0, 0);
-          var imgData;
-          try { imgData = ctx.getImageData(0,0,iw,ih); }
-          catch(e) { alert('Image is CORS-locked — open from the same origin to recolor.'); return; }
-          var d = imgData.data;
-          var from = hexToRgb(fromHex), to = hexToRgb(toHex);
-          // Convert from to HSL to tolerate similar hues
-          var fromHsl = rgbToHsl(from[0], from[1], from[2]);
-          var toHsl   = rgbToHsl(to[0],   to[1],   to[2]);
-          var TOLERANCE_H = 25;   // degrees
-          var TOLERANCE_L = 0.18; // lightness window
-          var changed = 0;
-          for (var i = 0; i < d.length; i += 4) {
-            var hsl = rgbToHsl(d[i], d[i+1], d[i+2]);
-            var dh = Math.min(Math.abs(hsl[0] - fromHsl[0]), 360 - Math.abs(hsl[0] - fromHsl[0]));
-            var dl = Math.abs(hsl[2] - fromHsl[2]);
-            if (dh < TOLERANCE_H && dl < TOLERANCE_L) {
-              // shift hue toward target, preserve original luminance
-              var newH = (toHsl[0] + (hsl[0] - fromHsl[0]) * 0.3 + 360) % 360;
-              var rgb = hslToRgb(newH, hsl[1] * (toHsl[1] / Math.max(fromHsl[1], 0.001)), hsl[2]);
-              d[i] = rgb[0]; d[i+1] = rgb[1]; d[i+2] = rgb[2];
-              changed++;
-            }
-          }
-          ctx.putImageData(imgData, 0, 0);
-          // Replace the img src with the canvas data URL
-          var dataUrl = canvas.toDataURL('image/png');
-          img.src = dataUrl;
-          if (previewImg) previewImg.src = dataUrl;
-          document.getElementById('wheel-readout').textContent =
-            'Recolored ' + changed.toLocaleString() + ' pixels  (' + fromHex + ' → ' + toHex + ')';
-          // After recolor, re-extract palette
-          setTimeout(function(){ PALETTE = extractPalette(14); renderDots('hex'); }, 200);
-        }
-
-        function hslToRgb(h, s, l) {
-          var c = (1 - Math.abs(2*l - 1)) * s, x = c * (1 - Math.abs(((h/60) % 2) - 1)), m = l - c/2;
-          var r, g, b;
-          if (h<60)       [r,g,b]=[c,x,0];
-          else if (h<120) [r,g,b]=[x,c,0];
-          else if (h<180) [r,g,b]=[0,c,x];
-          else if (h<240) [r,g,b]=[0,x,c];
-          else if (h<300) [r,g,b]=[x,0,c];
-          else            [r,g,b]=[c,0,x];
-          return [Math.round((r+m)*255), Math.round((g+m)*255), Math.round((b+m)*255)];
-        }
-
-        // ── 4. HSV color wheel ────────────────────────────────────────────
-        var wheel = document.getElementById('color-wheel');
-        if (wheel) {
-          var wctx = wheel.getContext('2d');
-          var W = wheel.width, H = wheel.height, R = W/2;
-          var imgD = wctx.createImageData(W, H);
-          for (var y = 0; y < H; y++) {
-            for (var x = 0; x < W; x++) {
-              var dx = x - R, dy = y - R, dist = Math.sqrt(dx*dx + dy*dy);
-              if (dist > R - 2) continue;
-              var ang = Math.atan2(dy, dx) * 180 / Math.PI;
-              if (ang < 0) ang += 360;
-              var sat = Math.min(1, dist / (R - 2));
-              var rgb = hslToRgb(ang, sat, 0.55);
-              var idx = (y * W + x) * 4;
-              imgD.data[idx] = rgb[0]; imgD.data[idx+1] = rgb[1]; imgD.data[idx+2] = rgb[2]; imgD.data[idx+3] = 255;
-            }
-          }
-          wctx.putImageData(imgD, 0, 0);
-          wheel.addEventListener('click', function(e){
-            var rect = wheel.getBoundingClientRect();
-            var x = e.clientX - rect.left, y = e.clientY - rect.top;
-            var pix = wctx.getImageData(x, y, 1, 1).data;
-            if (pix[3] === 0) return;
-            var newHex = rgbToHex(pix[0], pix[1], pix[2]);
-            if (!selectedDot) {
-              document.getElementById('wheel-readout').textContent = 'Pick a swatch first (click a dot above).';
-              return;
-            }
-            recolor(selectedDot, newHex);
-            selectedDot = newHex;
-          });
-        }
-
-        // ── 5. Suggested swatches (analogous + complementary) ─────────────
-        function renderSuggested(hex) {
-          var rgb = hexToRgb(hex), hsl = rgbToHsl(rgb[0], rgb[1], rgb[2]);
-          var suggestions = [
-            (hsl[0] +  30) % 360, (hsl[0] -  30 + 360) % 360,  // analogous
-            (hsl[0] + 180) % 360,                               // complementary
-            (hsl[0] + 120) % 360, (hsl[0] + 240) % 360          // triadic
-          ];
-          var sw = suggestions.map(function(h){
-            var rgb = hslToRgb(h, Math.max(hsl[1], 0.4), Math.max(0.3, Math.min(0.7, hsl[2])));
-            return rgbToHex(rgb[0], rgb[1], rgb[2]);
-          });
-          document.getElementById('suggested-swatches').innerHTML = sw.map(function(s){
-            return '<div title="' + s + '" data-hex="' + s + '" style="background:' + s + '"></div>';
-          }).join('');
-          document.querySelectorAll('#suggested-swatches > div').forEach(function(d){
-            d.addEventListener('click', function(){
-              if (!selectedDot) return;
-              recolor(selectedDot, d.getAttribute('data-hex'));
-              selectedDot = d.getAttribute('data-hex');
-            });
-          });
-        }
-
-        // ── 6. Bake — save current state as a new design ──────────────────
-        document.getElementById('adj-bake').addEventListener('click', async function(){
-          var iw = img.naturalWidth, ih = img.naturalHeight;
-          if (!iw) return;
-          canvas.width = iw; canvas.height = ih;
-          var ctx = canvas.getContext('2d');
-          ctx.filter = img.style.filter || '';
-          ctx.globalAlpha = parseFloat(img.style.opacity) || 1;
-          ctx.drawImage(img, 0, 0);
-          var dataUrl = canvas.toDataURL('image/png');
-          this.disabled = true; this.textContent = 'Saving…';
-          try {
-            var r = await fetch('/api/design/' + ${design.id} + '/bake', {
-              method:'POST', headers:{'Content-Type':'application/json'},
-              body: JSON.stringify({ image_b64: dataUrl.split(',')[1], parent_id: ${design.id} })
-            });
-            if (!r.ok) {
-              var t = await r.text();
-              this.textContent = 'Failed (HTTP ' + r.status + ')';
-              console.error('bake HTTP', r.status, t.slice(0,400));
-              this.disabled = false;
-              return;
-            }
-            var j = await r.json();
-            console.log('bake response', j);
-            if (j.ok && j.design_id) {
-              this.textContent = '✓ Saved as #' + j.design_id + ' — redirecting…';
-              window.location.assign('/design/' + j.design_id);
-            } else {
-              this.textContent = 'Failed: ' + (j.error || 'no design_id returned');
-              this.disabled = false;
-            }
-          } catch(e) {
-            this.textContent = 'Error: ' + e.message;
-            console.error('bake error', e);
-            this.disabled = false;
-          }
-        });
-
-        // ── boot: extract palette once image is ready ─────────────────────
-        function boot() {
-          PALETTE = extractPalette(14);
-          if (PALETTE.length === 0) {
-            document.getElementById('palette-dots').innerHTML =
-              '<div class="dots-loading" style="color:#c66">Image CORS-locked. Palette unavailable.</div>';
-            return;
-          }
-          renderDots('hex');
-        }
-        if (img.complete && img.naturalWidth) boot();
-        else img.addEventListener('load', boot);
-      })();
-      </script>
-
-      ${_isAdmin ? `
-      <!-- Provenance / AI-disclosure note — admin-only.
-           A public-facing legal disclaimer on every design tile invites scrutiny
-           where the design itself is the strongest argument; admins still need
-           to see provenance for QA, but customers see a clean design page. -->
-      <div class="provenance-note">
-        <strong>AI-original provenance:</strong> This pattern was generated by <em>wallco.ai</em>.
-        It does not reproduce any existing copyrighted artwork. Every seed is unique — this exact pattern exists only here.
-      </div>
-      ` : ''}
-
-      ${prev || next ? `<div class="detail-nav">
-        ${prev ? `<a href="/design/${prev.id}" class="detail-nav-link prev">&larr; ${prev.title}</a>` : '<span></span>'}
-        ${next ? `<a href="/design/${next.id}" class="detail-nav-link next">${next.title} &rarr;</a>` : '<span></span>'}
-      </div>` : ''}
-    </div>
-  </div>
-
-  <!-- Design Coordinate — pulls coordinating stripes/plaids from the DW catalog
-       that use the design's palette, plus AI-generated coordinates. -->
-  <section
-    id="pairs-well-with"
-    class="dc-section"
-    data-pairs-endpoint="/api/coordinates/pairs"
-    data-ai-endpoint="/api/coordinates/ai"
-    data-source-palette="${design.dominant_hex || ''}"
-    data-source-title="${(design.title || '').replace(/"/g, '&quot;')}"
-    data-source-image="${design.image_url || ''}"
-    style="margin: 48px auto 0; max-width: 1200px; padding: 0 24px; clear: both;"
-    hidden
-  >
-    <button type="button" class="pairs-well__cta" aria-expanded="false">
-      <span class="pairs-well__cta-text">Design Coordinate</span>
-      <span class="pairs-well__cta-icon" aria-hidden="true">+</span>
-    </button>
-
-    <div class="pairs-well__source-palette" id="pairs-well-source-palette" hidden></div>
-    <div class="pairs-well__vendor-picker" id="pairs-well-vendor-picker" hidden>
-      <h4>Paint brands</h4>
-      <div class="chips">
-        <label><input type="checkbox" value="sw"   checked> Sherwin-Williams</label>
-        <label><input type="checkbox" value="de"   checked> Dunn-Edwards</label>
-        <label><input type="checkbox" value="bm"   checked> Benjamin Moore</label>
-        <label><input type="checkbox" value="fb"   checked> Farrow &amp; Ball</label>
-        <label><input type="checkbox" value="behr"        > Behr</label>
-        <label><input type="checkbox" value="ppg"         > PPG</label>
-        <label><input type="checkbox" value="ral"         > RAL</label>
-      </div>
-    </div>
-
-    <div class="pairs-well__catalog" id="pairs-well-catalog" hidden>
-      <div class="pairs-well__panel-head">
-        <h3 style="font:500 22px/1.2 ui-serif,Georgia,serif;">Stripes &amp; Plaids from our catalog <span class="count" id="pairs-well-catalog-count"></span></h3>
-        <div class="pairs-well__controls">
-          <label for="pairs-well-sort">Sort</label>
-          <select id="pairs-well-sort" class="pw-sort"
-            style="padding:5px 10px;border:1px solid #d8d0c0;background:#fff;color:#1a1a1a;border-radius:999px;font:11px ui-sans-serif,system-ui,sans-serif;letter-spacing:.04em;cursor:pointer">
-            <optgroup label="Match quality">
-              <option value="match">Best match (default)</option>
-            </optgroup>
-            <optgroup label="Alphabetical">
-              <option value="title">Title A → Z</option>
-              <option value="title-desc">Title Z → A</option>
-            </optgroup>
-            <optgroup label="Style">
-              <option value="motif">Style / motif</option>
-            </optgroup>
-            <optgroup label="Color">
-              <option value="light-dark">Light → Dark</option>
-              <option value="dark-light">Dark → Light</option>
-              <option value="color-wheel">Color Wheel</option>
-              <option value="vivid">Most Vivid</option>
-              <option value="muted">Most Muted</option>
-            </optgroup>
-            <optgroup label="Other">
-              <option value="random">Random</option>
-            </optgroup>
-          </select>
-          <label for="pairs-well-cols-catalog">Columns</label>
-          <input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="8" step="1">
-          <span class="pairs-well__cols-val" id="pairs-well-cols-catalog-val">8</span>
-        </div>
-      </div>
-      <div id="pairs-well-grid" class="pairs-well__grid"></div>
-    </div>
-
-    <div class="pairs-well__ai" id="pairs-well-ai" hidden>
-      <div class="pairs-well__panel-head">
-        <h3 style="font:500 22px/1.2 ui-serif,Georgia,serif;">AI Generated Coordinates <span class="ai-badge">Preview · 24 options</span></h3>
-      </div>
-      <!-- ── TOP CONTROLS — repeat width + room scale + columns ─────────────
-           All three persist to localStorage. Repeat-width snaps server-side
-           to a multiple of each pattern's pitch so seams always fit. -->
-      <div class="pw-ai-controls">
-        <div class="pw-ctrl">
-          <label for="pw-tile-in">Repeat width</label>
-          <input id="pw-tile-in" type="range" min="12" max="48" step="2" value="24">
-          <span class="pw-ctrl-val"><span id="pw-tile-in-val">24</span>″</span>
-        </div>
-        <div class="pw-ctrl">
-          <label for="pw-room-in">Room scale</label>
-          <input id="pw-room-in" type="range" min="60" max="180" step="6" value="96">
-          <span class="pw-ctrl-val"><span id="pw-room-in-val">96</span>″ wall</span>
-        </div>
-        <div class="pw-ctrl">
-          <label for="pairs-well-cols-ai">Columns</label>
-          <input id="pairs-well-cols-ai" type="range" min="3" max="12" value="8" step="1">
-          <span class="pw-ctrl-val" id="pairs-well-cols-ai-val">8</span>
-        </div>
-      </div>
-      <div id="pairs-well-ai-grid" class="pairs-well__grid pairs-well__grid--ai"></div>
-    </div>
-  </section>
-
-  ${_isAdmin ? `
-  <!-- ── PATTERN EDITOR MODAL (admin) ────────────────────────────────────
-       1–5 color slots that accept drag-drop from any .cs-dot on the page.
-       Live preview re-renders via /api/ai-coordinates/render. Save updates
-       the source card in-place; Save as New appends a custom card. -->
-  <div id="pw-editor" class="pw-editor" hidden>
-    <div class="pw-editor__backdrop" data-close="1"></div>
-    <div class="pw-editor__sheet" role="dialog" aria-label="Pattern editor">
-      <header class="pw-editor__head">
-        <h3>Edit pattern</h3>
-        <button type="button" class="pw-editor__x" data-close="1">×</button>
-      </header>
-      <div class="pw-editor__body">
-        <div class="pw-editor__preview">
-          <img id="pw-editor-img" alt="" src="">
-        </div>
-        <div class="pw-editor__form">
-          <label class="pw-fld">
-            <span>Name</span>
-            <input id="pw-fld-label" type="text" maxlength="80" autocomplete="off">
-          </label>
-          <label class="pw-fld">
-            <span>Motif</span>
-            <select id="pw-fld-motif">
-              <option value="stripe">Stripe</option>
-              <option value="plaid">Plaid</option>
-              <option value="tartan">Tartan</option>
-            </select>
-          </label>
-          <label class="pw-fld">
-            <span>Style</span>
-            <select id="pw-fld-style">
-              <optgroup label="Stripes">
-                <option value="even">Even stripe</option>
-                <option value="triple">Triple stripe (3 colors)</option>
-                <option value="pinstripe">Pinstripe</option>
-                <option value="pinstripe-pair">Twin pinstripe</option>
-                <option value="cabana">Cabana</option>
-                <option value="awning">Awning</option>
-                <option value="wavy">Wavy stripe</option>
-                <option value="hairline">Hairline</option>
-                <option value="block">Block</option>
-              </optgroup>
-              <optgroup label="Plaids / checks">
-                <option value="buffalo">Buffalo check</option>
-                <option value="gingham">Gingham</option>
-                <option value="tattersall">Tattersall</option>
-                <option value="windowpane">Windowpane</option>
-                <option value="madras">Madras</option>
-                <option value="tartan">Tartan</option>
-                <option value="glen">Glen plaid</option>
-                <option value="box">Box plaid</option>
-              </optgroup>
-            </select>
-          </label>
-          <label class="pw-fld">
-            <span>Stripe width <strong id="pw-fld-pitch-val">6″</strong></span>
-            <input id="pw-fld-pitch" type="range" min="0.5" max="18" step="0.5" value="6">
-          </label>
-
-          <div class="pw-fld pw-slot-group">
-            <span>Colors (drag from page)</span>
-            <div class="pw-slots" id="pw-slots"></div>
-            <div class="pw-slot-count">
-              <button type="button" data-n="1">1</button>
-              <button type="button" data-n="2">2</button>
-              <button type="button" data-n="3" class="active">3</button>
-              <button type="button" data-n="4">4</button>
-              <button type="button" data-n="5">5</button>
-              <span class="pw-slot-hint">color slots</span>
-            </div>
-          </div>
-
-          <div class="pw-editor__actions">
-            <button type="button" id="pw-btn-save"     class="pw-btn pw-btn-primary">Save changes</button>
-            <button type="button" id="pw-btn-savenew"  class="pw-btn">Save as new</button>
-            <button type="button" id="pw-btn-cancel"   class="pw-btn pw-btn-ghost" data-close="1">Cancel</button>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-  ` : ''}
-
-  <style>
-    #pairs-well-with { --cols-catalog: 8; --cols-ai: 8; font-family: inherit; }
-    .pairs-well__cta { display:flex; align-items:center; justify-content:space-between; width:100%; padding:18px 24px; margin:0; background:#111; color:#fff; border:0; border-radius:999px; font-size:15px; font-weight:500; letter-spacing:.06em; text-transform:uppercase; cursor:pointer; transition: background 160ms ease, transform 120ms ease; }
-    .pairs-well__cta:hover { background:#2a2a2a; }
-    .pairs-well__cta:active { transform:scale(.995); }
-    .pairs-well__cta[aria-expanded="true"] .pairs-well__cta-icon { transform: rotate(45deg); }
-    .pairs-well__cta-icon { display:inline-block; font-size:22px; line-height:1; transition: transform 200ms ease; }
-    .pairs-well__source-palette { margin: 18px 0 0; }
-    .pairs-well__source-palette h4 { margin: 0 0 8px; font-size: 11px; letter-spacing:.08em; text-transform: uppercase; color:#777; }
-    .pairs-well__source-palette table { border-collapse: collapse; width: 100%; font-size: 12px; }
-    .pairs-well__source-palette th, .pairs-well__source-palette td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #f0ebe2; }
-    .pairs-well__source-palette th { font-weight: 500; color: #777; font-size: 11px; letter-spacing:.06em; text-transform: uppercase; }
-    .pairs-well__source-palette td.swatch { width: 28px; }
-    .pw-dot    { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; }
-    .pw-square { display: inline-block; width: 13px; height: 13px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; margin-right: 4px; }
-    .pw-code   { color: #888; font-variant-numeric: tabular-nums; font-size: 11px; }
-    .pairs-well__vendor-picker { margin: 18px 0 0; }
-    .pairs-well__vendor-picker h4 { margin: 0 0 8px; font-size: 11px; letter-spacing:.08em; text-transform: uppercase; color: #777; }
-    .pairs-well__vendor-picker .chips { display: flex; flex-wrap: wrap; gap: 6px; }
-    .pairs-well__vendor-picker label { font-size: 12px; padding: 5px 10px; border: 1px solid #e7e7e7; border-radius: 999px; cursor: pointer; background: #fff; user-select: none; }
-    .pairs-well__vendor-picker label:has(input:checked) { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
-    .pairs-well__vendor-picker input { display: none; }
-    .pairs-well__panel-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin: 24px 0 12px; }
-    .pairs-well__panel-head h3 { margin: 0; }
-    .pairs-well__panel-head .count { color: #888; font-size: 12px; margin-left: 6px; font-family: ui-sans-serif, system-ui, sans-serif; font-weight: 400; }
-    .pairs-well__panel-head .ai-badge { display: inline-block; padding: 3px 8px; background: #2a2a2a; color: #fff; border-radius: 999px; font-size: 10px; letter-spacing:.08em; text-transform: uppercase; margin-left: 8px; font-family: ui-sans-serif, system-ui, sans-serif; font-weight: 600; }
-    .pairs-well__controls { display: flex; align-items: center; gap: 10px; font-size: 11px; color: #777; text-transform: uppercase; letter-spacing:.06em; }
-    .pairs-well__controls input[type=range] { width: 160px; accent-color: #1a1a1a; }
-    .pairs-well__cols-val { font-variant-numeric: tabular-nums; color: #1a1a1a; min-width: 1.5em; text-align: right; font-weight: 600; }
-    .pairs-well__grid { display: grid; gap: 14px; margin-top: 6px; grid-template-columns: repeat(var(--cols-catalog), minmax(0, 1fr)); }
-    .pairs-well__grid--ai { grid-template-columns: repeat(var(--cols-ai), minmax(0, 1fr)); }
-    @media (max-width: 1100px) { .pairs-well__grid { grid-template-columns: repeat(min(6, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(6, var(--cols-ai)), minmax(0, 1fr)); } }
-    @media (max-width: 900px)  { .pairs-well__grid { grid-template-columns: repeat(min(4, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(4, var(--cols-ai)), minmax(0, 1fr)); } }
-    @media (max-width: 600px)  { .pairs-well__grid { grid-template-columns: repeat(min(2, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(2, var(--cols-ai)), minmax(0, 1fr)); } }
-    .pairs-well__card { display: block; text-decoration: none; color: inherit; background: #fff; border: 1px solid #e7e7e7; border-radius: 6px; overflow: hidden; transition: box-shadow 160ms ease, transform 160ms ease; }
-    .pairs-well__card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.08); transform: translateY(-2px); }
-    .pairs-well__card--ai { border-style: dashed; border-color: #d9c7a8; }
-    .pairs-well__img-wrap { aspect-ratio: 1 / 1; background: #f4f4f4; overflow: hidden; }
-    .pairs-well__img { width: 100%; height: 100%; object-fit: cover; display: block; }
-    .pairs-well__meta { padding: 10px 12px 12px; }
-    .pairs-well__title { margin: 0 0 4px; font-size: 13px; font-weight: 600; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
-    .pairs-well__vendor { margin: 0 0 6px; font-size: 10px; color: #777; letter-spacing:.04em; text-transform: uppercase; }
-    .pairs-well__why { display: inline-block; padding: 2px 7px; background: #f4ede4; color: #5a4628; border-radius: 999px; font-size: 10px; line-height: 1.4; margin-bottom: 8px; }
-    .pairs-well__card--ai .pairs-well__why { background: #efe6f5; color: #5b3478; }
-    .pairs-well__palette { display: flex; gap: 3px; margin: 0 0 8px; flex-wrap: wrap; }
-    .pairs-well__palette .pw-dot { width: 12px; height: 12px; }
-    .pairs-well__paints { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.5; color: #444; }
-    .pairs-well__paints .label { color: #888; font-weight: 600; margin-right: 4px; letter-spacing:.04em; }
-    .pairs-well__paints .row { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-    /* Collapsed paint-match table on each coordinate card. Closed by default
-       so the card surface stays clean — palette dots stay visible above, the
-       full SW/DE/BM/F&B paint NAMES live behind a tap. */
-    .pairs-well__paints-d { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.4; color: #444; }
-    .pairs-well__paints-d > summary {
-      list-style: none; cursor: pointer; color: #888;
-      font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
-      font-weight: 600; padding: 2px 0;
-      display: inline-flex; align-items: center; gap: 6px;
-      user-select: none;
-    }
-    .pairs-well__paints-d > summary::-webkit-details-marker { display: none; }
-    .pairs-well__paints-d > summary:hover { color: #1a1a1a; }
-    .pairs-well__paints-d .pw-d-caret { display:inline-block; transition: transform .15s ease; font-size: 10px; }
-    .pairs-well__paints-d[open] .pw-d-caret { transform: rotate(180deg); }
-    .pairs-well__paints-tbl { width: 100%; border-collapse: collapse; margin-top: 5px; }
-    .pairs-well__paints-tbl th, .pairs-well__paints-tbl td {
-      text-align: left; padding: 3px 6px 3px 0; vertical-align: middle;
-      font-size: 10px; line-height: 1.3;
-    }
-    .pairs-well__paints-tbl th { font-weight: 600; color: #777; width: 32px; letter-spacing:.04em; }
-    .pairs-well__paints-tbl td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0; }
-    .pairs-well__paints-tbl .pw-square { display:inline-block; width: 9px; height: 9px; vertical-align: middle; margin-right: 5px; border: 1px solid rgba(0,0,0,.12); }
-    .pairs-well__paints-tbl .pw-code { color: #999; font-family: ui-monospace, monospace; font-size: 9px; }
-    .pairs-well__skeleton { background: linear-gradient(90deg, #eee 0%, #f6f6f6 50%, #eee 100%); background-size: 200% 100%; animation: pwShim 1.2s linear infinite; aspect-ratio: 1 / 1; border-radius: 6px; }
-    @keyframes pwShim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
-    .pairs-well__empty { padding: 18px; color: #777; font-size: 14px; grid-column: 1 / -1; }
-
-    /* ── Top controls (repeat / room / columns) ──────────────────────────── */
-    .pw-ai-controls {
-      display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
-      padding: 14px 16px; background: #fff; border: 1px solid #e7e7e7;
-      border-radius: 8px; margin: 0 0 16px;
-    }
-    @media (max-width: 800px) { .pw-ai-controls { grid-template-columns: 1fr; } }
-    .pw-ctrl { display: flex; align-items: center; gap: 10px; }
-    .pw-ctrl label { font-size: 10.5px; letter-spacing:.14em; text-transform: uppercase;
-      color: #7a6e5a; min-width: 88px; }
-    .pw-ctrl input[type=range] { flex:1; accent-color: #1a1a1a; }
-    .pw-ctrl-val { font-variant-numeric: tabular-nums; color: #1a1a1a;
-      font-size: 12px; font-weight: 600; min-width: 64px; text-align: right; }
-
-    /* ── AI card actions (Duplicate / Edit) — always visible, touch-friendly ─ */
-    .pairs-well__card { position: relative; }
-    .pw-card-actions {
-      position: absolute; top: 6px; right: 6px; z-index: 3;
-      display: flex; gap: 4px;
-    }
-    .pw-card-actions button {
-      background: rgba(20,20,20,0.92); color: #fff; border: 0;
-      font: 700 10px/1 ui-sans-serif, system-ui, sans-serif;
-      letter-spacing:.08em; text-transform: uppercase;
-      padding: 6px 9px; border-radius: 999px; cursor: pointer;
-      box-shadow: 0 1px 3px rgba(0,0,0,0.2);
-      transition: background .12s ease, transform .12s ease;
-    }
-    .pw-card-actions button:hover { background: #000; transform: translateY(-1px); }
-    .pw-card-actions button.pw-edit  { background: rgba(176,138,58,0.95); }
-    .pw-card-actions button.pw-edit:hover { background: #8a6a2a; }
-    .pw-pitch-chip {
-      display: inline-block; padding: 1px 6px; margin-left: 4px;
-      background: #eef0ee; color: #2a4a2a; border-radius: 999px;
-      font-size: 10px; font-weight: 600; letter-spacing:.04em;
-    }
-
-    /* ── Pattern editor modal ────────────────────────────────────────────── */
-    .pw-editor { position: fixed; inset: 0; z-index: 9000; display: flex;
-      align-items: center; justify-content: center; }
-    .pw-editor[hidden] { display: none; }
-    .pw-editor__backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); cursor: pointer; }
-    .pw-editor__sheet {
-      position: relative; width: 92vw; max-width: 880px; max-height: 92vh; overflow: auto;
-      background: #fdfaf3; border-radius: 12px; border: 1px solid #d8d0c0;
-      box-shadow: 0 24px 60px rgba(0,0,0,0.25);
-    }
-    .pw-editor__head { display: flex; align-items: center; justify-content: space-between;
-      padding: 14px 18px; border-bottom: 1px solid #e9e0c8; }
-    .pw-editor__head h3 { margin: 0; font: 500 18px/1.2 ui-serif, Georgia, serif; }
-    .pw-editor__x { background: none; border: 0; font-size: 26px; line-height: 1; cursor: pointer; color: #5a4628; padding: 0 4px; }
-    .pw-editor__body { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; padding: 18px; }
-    @media (max-width: 700px) { .pw-editor__body { grid-template-columns: 1fr; } }
-    .pw-editor__preview { background: #f0eadc; border-radius: 8px; aspect-ratio: 1/1; overflow: hidden; display:flex; align-items:center; justify-content:center; }
-    .pw-editor__preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
-    .pw-editor__form { display: flex; flex-direction: column; gap: 12px; }
-    .pw-fld { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: #5a4628; }
-    .pw-fld > span { font-size: 10.5px; letter-spacing:.14em; text-transform: uppercase; color: #7a6e5a; }
-    .pw-fld > span strong { font-weight: 700; color: #1a1a1a; letter-spacing: 0; text-transform: none; font-size: 12px; }
-    .pw-fld input[type=text], .pw-fld select { font: inherit; padding: 8px 10px; border: 1px solid #d8d0c0; border-radius: 4px; background: #fff; color: #1a1a1a; }
-    .pw-fld input[type=range] { accent-color: #1a1a1a; }
-    .pw-slots { display: flex; flex-wrap: wrap; gap: 8px; }
-    .pw-slot {
-      width: 42px; height: 42px; border-radius: 50%;
-      border: 2px dashed #c8b88a; background: #fff;
-      display: flex; align-items: center; justify-content: center;
-      cursor: pointer; position: relative;
-      font: 600 9px/1 ui-sans-serif, system-ui, sans-serif; color: #b08a3a;
-      letter-spacing:.06em; text-transform: uppercase;
-    }
-    .pw-slot.filled { border-style: solid; border-color: rgba(0,0,0,0.15); color: transparent; }
-    .pw-slot.drag-over { box-shadow: 0 0 0 3px #b08a3a; border-color: #b08a3a; }
-    .pw-slot input[type=color] {
-      position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: 0; padding: 0;
-    }
-    .pw-slot-count { display: flex; align-items: center; gap: 4px; margin-top: 4px; }
-    .pw-slot-count button {
-      width: 26px; height: 26px; border-radius: 50%;
-      border: 1px solid #d8d0c0; background: #fff; cursor: pointer;
-      font: 600 11px/1 ui-sans-serif, system-ui, sans-serif; color: #5a4628;
-    }
-    .pw-slot-count button.active { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
-    .pw-slot-hint { font-size: 10px; color: #7a6e5a; margin-left: 8px; letter-spacing:.04em; }
-    .pw-editor__actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
-    .pw-btn {
-      padding: 10px 16px; border-radius: 4px; cursor: pointer;
-      font: 600 11px/1 ui-sans-serif, system-ui, sans-serif;
-      letter-spacing:.08em; text-transform: uppercase;
-      background: #fff; border: 1px solid #d8d0c0; color: #1a1a1a;
-    }
-    .pw-btn:hover { background: #f4ede4; }
-    .pw-btn-primary { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
-    .pw-btn-primary:hover { background: #000; }
-    .pw-btn-ghost { background: transparent; border-color: transparent; color: #7a6e5a; }
-  </style>
-
-  <script>
-  (function () {
-    var root = document.getElementById('pairs-well-with'); if (!root) return;
-    var endpointPairs = root.getAttribute('data-pairs-endpoint');
-    var endpointAi    = root.getAttribute('data-ai-endpoint');
-    var palette       = root.getAttribute('data-source-palette');
-    var sourceTitle   = root.getAttribute('data-source-title') || '';
-    var sourceImage   = root.getAttribute('data-source-image') || '';
-    if (!palette) return;
-    root.hidden = false;
-
-    var cta            = root.querySelector('.pairs-well__cta');
-    var catalogPanel   = document.getElementById('pairs-well-catalog');
-    var catalogGrid    = document.getElementById('pairs-well-grid');
-    var catalogCols    = document.getElementById('pairs-well-cols-catalog');
-    var catalogColsVal = document.getElementById('pairs-well-cols-catalog-val');
-    var catalogCount   = document.getElementById('pairs-well-catalog-count');
-    var aiPanel        = document.getElementById('pairs-well-ai');
-    var aiGrid         = document.getElementById('pairs-well-ai-grid');
-    var aiCols         = document.getElementById('pairs-well-cols-ai');
-    var aiColsVal      = document.getElementById('pairs-well-cols-ai-val');
-    var tileIn         = document.getElementById('pw-tile-in');
-    var tileInVal      = document.getElementById('pw-tile-in-val');
-    var roomIn         = document.getElementById('pw-room-in');
-    var roomInVal      = document.getElementById('pw-room-in-val');
-    var sourcePalette  = document.getElementById('pairs-well-source-palette');
-    var vendorPicker   = document.getElementById('pairs-well-vendor-picker');
-    var vendorChecks   = vendorPicker.querySelectorAll('input[type=checkbox]');
-    var editor         = document.getElementById('pw-editor');
-    var loaded=false, cachedPairs=null, cachedAi=null, customCards=[], cachedSource=null;
-    var IS_ADMIN = ${_isAdmin ? 'true' : 'false'};
-    var VENDOR_LABEL={sw:'Sherwin-Williams',de:'Dunn-Edwards',bm:'Benjamin Moore',fb:'Farrow & Ball',behr:'Behr',ppg:'PPG',ral:'RAL'};
-    var VENDOR_SHORT={sw:'SW',de:'DE',bm:'BM',fb:'F&B',behr:'Behr',ppg:'PPG',ral:'RAL'};
-    var DEFAULT_VENDORS=['sw','de','bm','fb'];
-
-    function selectedVendors(){var o=[];vendorChecks.forEach(function(c){if(c.checked)o.push(c.value)});return o.length?o:DEFAULT_VENDORS.slice()}
-    try{var st=JSON.parse(localStorage.getItem('pw.vendors')||'null');if(Array.isArray(st)&&st.length)vendorChecks.forEach(function(c){c.checked=st.indexOf(c.value)!==-1})}catch(_){}
-    function persistVendors(){try{localStorage.setItem('pw.vendors',JSON.stringify(selectedVendors()))}catch(_){}}
-    vendorChecks.forEach(function(c){c.addEventListener('change',function(){persistVendors();if(cachedSource)renderSourcePalette(cachedSource.palette);if(cachedPairs&&!catalogPanel.hidden)renderGrid(catalogGrid,sortPairs(cachedPairs,currentSort),false)})});
-
-    function wireCols(input,valEl,varName,storageKey){function apply(){var n=Math.max(3,Math.min(12,parseInt(input.value,10)||8));valEl.textContent=n;root.style.setProperty('--'+varName,n);try{localStorage.setItem(storageKey,n)}catch(_){}}try{var s=parseInt(localStorage.getItem(storageKey),10);if(!isNaN(s)&&s>=3&&s<=12)input.value=s}catch(_){}input.addEventListener('input',apply);apply()}
-    wireCols(catalogCols,catalogColsVal,'cols-catalog','pw.cols.catalog');
-    wireCols(aiCols,aiColsVal,'cols-ai','pw.cols.ai');
-
-    // Repeat-width + Room-scale sliders. tile_in re-fetches the AI grid.
-    var aiRefetchTimer = null;
-    function wireInchSlider(input, valEl, storageKey, onChange) {
-      try { var s = parseFloat(localStorage.getItem(storageKey)); if (!isNaN(s)) input.value = s; } catch (_) {}
-      function apply() {
-        valEl.textContent = input.value;
-        try { localStorage.setItem(storageKey, input.value); } catch (_) {}
-        if (onChange) onChange(input.value);
-      }
-      input.addEventListener('input', apply);
-      apply();
-    }
-    wireInchSlider(tileIn, tileInVal, 'pw.tile_in', function () {
-      // Debounce re-fetch by 300ms so dragging doesn't hammer the API.
-      clearTimeout(aiRefetchTimer);
-      aiRefetchTimer = setTimeout(refetchAi, 300);
-    });
-    wireInchSlider(roomIn, roomInVal, 'pw.room_in', function (val) {
-      // Room scale only affects display sizing — set a CSS var the cards can use later.
-      root.style.setProperty('--pw-room-in', val);
-    });
-
-    function esc(s){return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
-    function renderSkeletons(g,n){var h='';for(var i=0;i<n;i++)h+='<div class="pairs-well__skeleton"></div>';g.innerHTML=h}
-
-    function renderSourcePalette(pal){if(!pal||!pal.length){sourcePalette.hidden=true;vendorPicker.hidden=true;return}var vs=selectedVendors();var head='<th></th>'+vs.map(function(v){return '<th>'+esc(VENDOR_LABEL[v])+'</th>'}).join('');var body=pal.map(function(c){var hex=typeof c==='string'?c:c.hex;var cells='<td class="swatch"><span class="pw-dot" style="background:'+esc(hex)+'"></span></td>';vs.forEach(function(v){var p=c&&c.paints&&c.paints[v];cells+='<td>'+(p?'<span class="pw-square" style="background:'+esc(p.hex)+'"></span>'+esc(p.name)+' <span class="pw-code">'+esc(p.code)+'</span>':'—')+'</td>'});return '<tr>'+cells+'</tr>'}).join('');sourcePalette.innerHTML='<h4>Color story</h4><table><thead><tr>'+head+'</tr></thead><tbody>'+body+'</tbody></table>';sourcePalette.hidden=false;vendorPicker.hidden=false}
-
-    // ── Card renderer — AI cards get a pitch chip + Duplicate/Edit buttons.
-    function renderCard(p, isAi, idx) {
-      var img = p.image_url ? esc(p.image_url) : '';
-      var why = (p.why && p.why[0]) ? esc(p.why[0]) : (p.motif ? 'AI ' + esc(p.motif) : '');
-      var pitchChip = (isAi && p.pitch_in) ? '<span class="pw-pitch-chip">'+p.pitch_in+'″</span>' : '';
-      var pal = (p.palette||[]).map(function(c){var hex=typeof c==='string'?c:c.hex;return '<span class="pw-dot" style="background:'+esc(hex)+'" title="'+esc(hex)+'"></span>'}).join('');
-      var dom = (p.palette && p.palette[0]) || null;
-      var pb = '';
-      if (dom && dom.paints) {
-        var vs = selectedVendors();
-        // Build the paint rows as a 2-column table (Brand · Color) — no per-row
-        // pill label, since the table header already says it. Hex squares stay
-        // so the table is scannable. Wrapped in <details> so the card surface
-        // stays minimal on load — user opens the table when they want it.
-        var rowsTbl = vs.map(function(v){
-          var pt = dom.paints[v]; if (!pt) return '';
-          return '<tr><th>'+esc(VENDOR_SHORT[v])+'</th>'
-            + '<td><span class="pw-square" style="background:'+esc(pt.hex)+'"></span>'
-            + esc(pt.name)+' <span class="pw-code">'+esc(pt.code)+'</span></td></tr>';
-        }).filter(Boolean).join('');
-        if (rowsTbl) {
-          pb = '<details class="pairs-well__paints-d">'
-             +   '<summary>Paint matches <span class="pw-d-caret" aria-hidden="true">▾</span></summary>'
-             +   '<table class="pairs-well__paints-tbl"><tbody>'+rowsTbl+'</tbody></table>'
-             + '</details>';
-        }
-      }
-      var href = p.url ? esc(p.url) : (p.handle ? 'https://www.designerwallcoverings.com/products/'+esc(p.handle) : '#');
-      var aiOnly = isAi
-        ? '<div class="pw-card-actions">'
-          + (IS_ADMIN ? '<button type="button" class="pw-edit" data-card-action="edit" data-idx="'+idx+'">✎ Edit</button>' : '')
-          + '<button type="button" class="pw-dup" data-card-action="duplicate" data-idx="'+idx+'">⎘ Duplicate</button>'
-          + '</div>'
-        : '';
-      // For AI cards the href doesn't lead anywhere (placeholder) — use a div instead of an <a>
-      // so the Duplicate/Edit buttons don't trigger navigation when clicked.
-      var Tag = isAi ? 'div' : 'a';
-      var openAttrs = isAi ? '' : ' target="_blank" rel="noopener" href="'+href+'"';
-      return '<'+Tag+' class="pairs-well__card'+(isAi?' pairs-well__card--ai':'')+'"'+openAttrs+' data-card-idx="'+idx+'" title="'+esc((p.why||[]).join(' · '))+'">'
-        + aiOnly
-        + '<div class="pairs-well__img-wrap">'+(img?'<img class="pairs-well__img" src="'+img+'" alt="'+esc(p.title)+'" loading="lazy">':'')+'</div>'
-        + '<div class="pairs-well__meta">'
-        +   '<p class="pairs-well__title">'+esc(p.title||'')+pitchChip+'</p>'
-        +   '<p class="pairs-well__vendor">'+(isAi?'AI-Generated':'Designer Wallcoverings')+'</p>'
-        +   (why ? '<span class="pairs-well__why">'+why+'</span>' : '')
-        +   (pal ? '<div class="pairs-well__palette">'+pal+'</div>' : '')
-        +   pb
-        + '</div>'
-      + '</'+Tag+'>';
-    }
-    function renderGrid(g, items, isAi) {
-      if (!items || !items.length) { g.innerHTML = '<div class="pairs-well__empty">No coordinates found.</div>'; return; }
-      g.innerHTML = items.map(function (p, i) { return renderCard(p, isAi, i); }).join('');
-    }
-
-    // ── Client-side sort for the catalog grid. Server returns match-ranked
-    // order ("Best match"); these sorts re-order the cached array in place.
-    function _hex2hsl(hex) {
-      if (!hex || !/^#?[0-9a-fA-F]{6}$/.test(hex.replace('#',''))) return [0,0,0];
-      var c = hex.replace('#','');
-      var r = parseInt(c.slice(0,2),16)/255, g = parseInt(c.slice(2,4),16)/255, b = parseInt(c.slice(4,6),16)/255;
-      var mx = Math.max(r,g,b), mn = Math.min(r,g,b), d = mx-mn;
-      var l = (mx+mn)/2;
-      var s = d === 0 ? 0 : d/(1-Math.abs(2*l-1));
-      var h = 0;
-      if (d) { if (mx===r) h = ((g-b)/d) % 6; else if (mx===g) h = (b-r)/d + 2; else h = (r-g)/d + 4; }
-      h = (h*60+360) % 360;
-      return [h, s, l];
-    }
-    function _firstHex(p) {
-      if (!p || !p.palette || !p.palette.length) return '#888888';
-      var c = p.palette[0]; return typeof c === 'string' ? c : (c && c.hex) || '#888888';
-    }
-    function sortPairs(items, key) {
-      if (!items || !items.length || !key || key === 'match') return items;
-      var arr = items.slice();
-      switch (key) {
-        case 'title':       return arr.sort(function(a,b){ return (a.title||'').localeCompare(b.title||''); });
-        case 'title-desc':  return arr.sort(function(a,b){ return (b.title||'').localeCompare(a.title||''); });
-        case 'motif':       return arr.sort(function(a,b){ return (a.motif||a.style||'').localeCompare(b.motif||b.style||''); });
-        case 'light-dark':  return arr.sort(function(a,b){ return _hex2hsl(_firstHex(b))[2] - _hex2hsl(_firstHex(a))[2]; });
-        case 'dark-light':  return arr.sort(function(a,b){ return _hex2hsl(_firstHex(a))[2] - _hex2hsl(_firstHex(b))[2]; });
-        case 'color-wheel': return arr.sort(function(a,b){
-          var ha = _hex2hsl(_firstHex(a)), hb = _hex2hsl(_firstHex(b));
-          var ba = Math.floor(ha[0]/30), bb = Math.floor(hb[0]/30);
-          return ba !== bb ? ba-bb : ha[2]-hb[2];
-        });
-        case 'vivid':       return arr.sort(function(a,b){ return _hex2hsl(_firstHex(b))[1] - _hex2hsl(_firstHex(a))[1]; });
-        case 'muted':       return arr.sort(function(a,b){ return _hex2hsl(_firstHex(a))[1] - _hex2hsl(_firstHex(b))[1]; });
-        case 'random': {
-          var seed = Math.floor(Date.now() / 60000);
-          var rand = function(n){ var x = Math.sin(seed * 9301 + n * 49297) * 233280; return x - Math.floor(x); };
-          return arr.map(function(x,i){ return { x: x, k: rand(i) }; }).sort(function(a,b){ return a.k - b.k; }).map(function(o){ return o.x; });
-        }
-        default: return arr;
-      }
-    }
-    // Sort dropdown — persist choice in localStorage (per Steve's standing
-    // rule that sort+density choices survive reload).
-    var sortSel = document.getElementById('pairs-well-sort');
-    var currentSort = 'match';
-    try { var s = localStorage.getItem('pw.sort'); if (s) { currentSort = s; if (sortSel) sortSel.value = s; } } catch (_) {}
-    function applySort() {
-      if (!cachedPairs) return;
-      var sorted = sortPairs(cachedPairs, currentSort);
-      renderGrid(catalogGrid, sorted, false);
-    }
-    if (sortSel) {
-      sortSel.addEventListener('change', function () {
-        currentSort = sortSel.value || 'match';
-        try { localStorage.setItem('pw.sort', currentSort); } catch (_) {}
-        applySort();
-      });
-    }
-    function renderAiGrid() {
-      var all = (customCards || []).concat(cachedAi || []);
-      renderGrid(aiGrid, all, true);
-    }
-
-    function buildUrl(base, limit, opts) {
-      var qs = ['palette=' + encodeURIComponent(palette)];
-      if (sourceTitle) qs.push('title=' + encodeURIComponent(sourceTitle));
-      if (sourceImage) qs.push('image_url=' + encodeURIComponent(sourceImage));
-      if (limit)       qs.push('limit=' + limit);
-      if (opts && opts.tile_in) qs.push('tile_in=' + opts.tile_in);
-      return base + '?' + qs.join('&');
-    }
-
-    function refetchAi() {
-      if (!loaded) return;
-      renderSkeletons(aiGrid, 12);
-      fetch(buildUrl(endpointAi, 24, { tile_in: parseFloat(tileIn.value) }), { credentials: 'omit' })
-        .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
-        .then(function (d) { if (!d || d.ok === false) throw new Error('bad payload'); cachedAi = d.items || []; renderAiGrid(); })
-        .catch(function () { aiGrid.innerHTML = '<div class="pairs-well__empty">AI generator not ready.</div>'; });
-    }
-
-    function load() {
-      if (loaded) return Promise.resolve();
-      loaded = true;
-      renderSkeletons(catalogGrid, 12); renderSkeletons(aiGrid, 12);
-      catalogPanel.hidden = false; aiPanel.hidden = false;
-      var pp = fetch(buildUrl(endpointPairs, 24), { credentials: 'omit' })
-        .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
-        .then(function (d) {
-          if (!d || d.ok === false) throw new Error((d && d.error) || 'bad payload');
-          cachedSource = d.source || null; cachedPairs = d.pairs || [];
-          if (cachedSource) renderSourcePalette(cachedSource.palette || []);
-          renderGrid(catalogGrid, sortPairs(cachedPairs, currentSort), false);
-          catalogCount.textContent = '(' + cachedPairs.length + ')';
-          if (!cachedPairs.length && (!cachedSource || !cachedSource.palette || !cachedSource.palette.length)) root.hidden = true;
-        })
-        .catch(function () { catalogGrid.innerHTML = '<div class="pairs-well__empty">Could not load coordinates right now.</div>'; });
-      var ap = fetch(buildUrl(endpointAi, 24, { tile_in: parseFloat(tileIn.value) }), { credentials: 'omit' })
-        .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
-        .then(function (d) { if (!d || d.ok === false) throw new Error('bad payload'); cachedAi = d.items || []; renderAiGrid(); })
-        .catch(function () { aiGrid.innerHTML = '<div class="pairs-well__empty">AI generator not ready.</div>'; });
-      return Promise.all([pp, ap]);
-    }
-
-    cta.addEventListener('click', function () {
-      var op = cta.getAttribute('aria-expanded') === 'true';
-      if (op) {
-        cta.setAttribute('aria-expanded', 'false');
-        sourcePalette.hidden = true; vendorPicker.hidden = true;
-        catalogPanel.hidden = true;  aiPanel.hidden = true;
-        return;
-      }
-      cta.setAttribute('aria-expanded', 'true');
-      load();
-    });
-
-    // ── Duplicate / Edit button handler on AI cards ─────────────────────────
-    function duplicateName(name) {
-      // "X" → "X (copy)" → "X (copy 2)" → "X (copy 3)"...
-      var m = /^(.*?) \(copy(?: (\d+))?\)$/.exec(name);
-      if (m) {
-        var n = parseInt(m[2] || '1', 10) + 1;
-        return m[1] + ' (copy ' + n + ')';
-      }
-      return name + ' (copy)';
-    }
-    aiGrid.addEventListener('click', function (e) {
-      var btn = e.target.closest('[data-card-action]');
-      if (!btn) return;
-      e.preventDefault(); e.stopPropagation();
-      var idx = parseInt(btn.getAttribute('data-idx'), 10);
-      var all = (customCards || []).concat(cachedAi || []);
-      var src = all[idx]; if (!src) return;
-      if (btn.getAttribute('data-card-action') === 'duplicate') {
-        var clone = JSON.parse(JSON.stringify(src));
-        clone.id    = 'custom-' + Date.now();
-        clone.title = duplicateName(src.title || 'Pattern');
-        clone.custom = true;
-        customCards.unshift(clone);
-        renderAiGrid();
-      } else if (btn.getAttribute('data-card-action') === 'edit') {
-        openEditor(src, idx);
-      }
-    });
-
-    ${_isAdmin ? `
-    // ── Pattern editor (admin only) ─────────────────────────────────────────
-    var editorImg    = document.getElementById('pw-editor-img');
-    var fldLabel     = document.getElementById('pw-fld-label');
-    var fldMotif     = document.getElementById('pw-fld-motif');
-    var fldStyle     = document.getElementById('pw-fld-style');
-    var fldPitch     = document.getElementById('pw-fld-pitch');
-    var fldPitchVal  = document.getElementById('pw-fld-pitch-val');
-    var slotsEl      = document.getElementById('pw-slots');
-    var slotCountEls = editor.querySelectorAll('.pw-slot-count button');
-    var btnSave      = document.getElementById('pw-btn-save');
-    var btnSaveNew   = document.getElementById('pw-btn-savenew');
-    var editingState = null;  // { sourceCard, sourceIdx }
-
-    function renderSlots(colors, n) {
-      slotsEl.innerHTML = '';
-      for (var i = 0; i < n; i++) {
-        var hex = colors[i] || '';
-        var slot = document.createElement('div');
-        slot.className = 'pw-slot' + (hex ? ' filled' : '');
-        slot.setAttribute('data-slot', String(i));
-        slot.style.background = hex || '#fff';
-        if (!hex) slot.textContent = '+';
-        var picker = document.createElement('input');
-        picker.type = 'color'; picker.value = hex || '#888888';
-        picker.addEventListener('input', function (ev) {
-          var s = ev.target.parentNode;
-          var ix = parseInt(s.getAttribute('data-slot'), 10);
-          editingState.colors[ix] = ev.target.value;
-          s.style.background = ev.target.value;
-          s.classList.add('filled');
-          s.textContent = '';
-          previewRender();
-        });
-        slot.appendChild(picker);
-        slotsEl.appendChild(slot);
-      }
-    }
-
-    function activeSlotCount() {
-      var n = 3;
-      slotCountEls.forEach(function (b) { if (b.classList.contains('active')) n = parseInt(b.getAttribute('data-n'), 10); });
-      return n;
-    }
-
-    slotCountEls.forEach(function (b) {
-      b.addEventListener('click', function () {
-        slotCountEls.forEach(function (x) { x.classList.toggle('active', x === b); });
-        var n = parseInt(b.getAttribute('data-n'), 10);
-        if (editingState) {
-          editingState.colors = editingState.colors.slice(0, n);
-          while (editingState.colors.length < n) editingState.colors.push('');
-          renderSlots(editingState.colors, n);
-          previewRender();
-        }
-      });
-    });
-
-    slotsEl.addEventListener('dragover', function (e) {
-      var slot = e.target.closest('.pw-slot');
-      if (slot) { e.preventDefault(); slot.classList.add('drag-over'); e.dataTransfer.dropEffect = 'copy'; }
-    });
-    slotsEl.addEventListener('dragleave', function (e) {
-      var slot = e.target.closest('.pw-slot');
-      if (slot) slot.classList.remove('drag-over');
-    });
-    slotsEl.addEventListener('drop', function (e) {
-      var slot = e.target.closest('.pw-slot'); if (!slot) return;
-      e.preventDefault(); slot.classList.remove('drag-over');
-      var hex = e.dataTransfer.getData('application/x-wallco-color') || e.dataTransfer.getData('text/plain');
-      if (!hex || !/^#?[0-9a-fA-F]{3,8}$/.test(hex.replace(/[^#0-9a-fA-F]/g,''))) return;
-      if (!hex.startsWith('#')) hex = '#' + hex;
-      var ix = parseInt(slot.getAttribute('data-slot'), 10);
-      editingState.colors[ix] = hex;
-      slot.style.background = hex;
-      slot.classList.add('filled');
-      slot.textContent = '';
-      var picker = slot.querySelector('input[type=color]'); if (picker) picker.value = hex;
-      previewRender();
-    });
-
-    fldPitch.addEventListener('input', function () { fldPitchVal.textContent = fldPitch.value + '″'; previewRender(); });
-    fldMotif.addEventListener('change', previewRender);
-    fldStyle.addEventListener('change', previewRender);
-
-    var previewTimer = null;
-    function previewRender() {
-      if (!editingState) return;
-      clearTimeout(previewTimer);
-      previewTimer = setTimeout(function () {
-        var body = {
-          motif: fldMotif.value,
-          style: fldStyle.value,
-          pitch_in: parseFloat(fldPitch.value),
-          colors: editingState.colors.filter(Boolean),
-          tile_in: parseFloat(tileIn.value),
-          label: fldLabel.value
-        };
-        fetch(endpointAi.replace('/ai-coordinates', '/ai-coordinates/render'), {
-          method: 'POST', credentials: 'omit',
-          headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify(body)
-        }).then(function (r) { return r.ok ? r.json() : null; })
-          .then(function (d) {
-            if (!d || !d.image_url) return;
-            editorImg.src = d.image_url;
-            editingState.lastRender = d;
-          })
-          .catch(function () {});
-      }, 180);
-    }
-
-    function openEditor(card, idx) {
-      var colors = (card.colors && card.colors.length ? card.colors : (card.palette || [])).slice(0, 5);
-      while (colors.length < 3) colors.push('');
-      editingState = { sourceCard: card, sourceIdx: idx, colors: colors.slice() };
-      fldLabel.value = card.title || '';
-      fldMotif.value = (card.motif === 'tartan') ? 'tartan' : ((card.motif === 'plaid') ? 'plaid' : 'stripe');
-      fldStyle.value = card.style || 'even';
-      fldPitch.value = card.pitch_in || 6;
-      fldPitchVal.textContent = fldPitch.value + '″';
-      // pick active slot-count button
-      var n = colors.length || 3;
-      slotCountEls.forEach(function (b) { b.classList.toggle('active', parseInt(b.getAttribute('data-n'), 10) === n); });
-      renderSlots(editingState.colors, n);
-      editorImg.src = card.image_url || '';
-      editor.hidden = false;
-      document.body.style.overflow = 'hidden';
-    }
-    function closeEditor() { editor.hidden = true; editingState = null; document.body.style.overflow = ''; }
-
-    editor.addEventListener('click', function (e) { if (e.target.getAttribute('data-close') === '1') closeEditor(); });
-    document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !editor.hidden) closeEditor(); });
-
-    btnSave.addEventListener('click', function () {
-      if (!editingState || !editingState.lastRender) return;
-      var src = editingState.sourceCard;
-      src.motif    = editingState.lastRender.motif;
-      src.style    = editingState.lastRender.style;
-      src.pitch_in = editingState.lastRender.pitch_in;
-      src.colors   = editingState.lastRender.colors;
-      src.palette  = editingState.lastRender.colors;
-      src.image_url = editingState.lastRender.image_url;
-      src.title    = fldLabel.value || src.title;
-      src.why      = [src.style + ' · ' + src.pitch_in + '″'];
-      renderAiGrid();
-      closeEditor();
-    });
-    btnSaveNew.addEventListener('click', function () {
-      if (!editingState || !editingState.lastRender) return;
-      var card = {
-        id: 'custom-' + Date.now(), motif: editingState.lastRender.motif,
-        style: editingState.lastRender.style, pitch_in: editingState.lastRender.pitch_in,
-        colors: editingState.lastRender.colors, palette: editingState.lastRender.colors,
-        image_url: editingState.lastRender.image_url,
-        title: (fldLabel.value || (editingState.sourceCard.title || 'Custom')) + (fldLabel.value ? '' : ' (custom)'),
-        why: [editingState.lastRender.style + ' · ' + editingState.lastRender.pitch_in + '″'],
-        custom: true
-      };
-      customCards.unshift(card);
-      renderAiGrid();
-      closeEditor();
-    });
-    ` : ''}
-  })();
-  </script>
-
-  <!-- Generated Rooms gallery — hydrates from /api/rooms?design_id={{id}} -->
-  <section id="rooms-gallery" class="rooms-gallery" data-design-id="${design.id}" style="margin:48px auto 0;max-width:1200px;padding:0 24px;">
-    <div class="rg-head" style="display:flex;align-items:baseline;gap:16px;border-top:1px solid var(--rule,#e5e5e0);padding-top:24px;margin-bottom:18px;flex-wrap:wrap;">
-      <h2 style="margin:0;font:500 22px/1.2 ui-serif,Georgia,serif;">Generated rooms</h2>
-      <span id="rg-count" style="font:11px/1.4 ui-sans-serif,system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint,#6e6e68);"></span>
-      <span id="rg-admin-badge" style="display:none;margin-left:auto;font:10px/1.4 ui-sans-serif,system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:#b00020;">admin mode</span>
-    </div>
-    ${_isAdmin ? `
-    <!-- See in Room generator — admin-only because it hits the upstream
-         renderer at ROOM_API ($/call). Triggers POST /api/room and dispatches
-         wallco:room-generated so the gallery below refreshes in place. -->
-    <div id="rg-gen-bar" style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:18px;padding:12px 14px;background:var(--card-bg,#f4f0e8);border:1px solid var(--rule,#e5e5e0);border-radius:8px;font:13px ui-sans-serif,system-ui,sans-serif;">
-      <strong style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint,#7a6e5a)">See in room</strong>
-      <label style="display:flex;align-items:center;gap:6px;font-size:11px;color:var(--ink-faint,#7a6e5a);">
-        Room
-        <select id="rg-room-type" style="font:12px ui-sans-serif,system-ui,sans-serif;padding:4px 8px;border:1px solid var(--line,#d8d0c0);background:#fff;border-radius:4px;">
-          <option value="bedroom">Bedroom</option>
-          <option value="living_room">Living room</option>
-          <option value="dining_room">Dining room</option>
-          <option value="office">Office</option>
-          <option value="bathroom">Bathroom</option>
-        </select>
-      </label>
-      <label style="display:flex;align-items:center;gap:6px;font-size:11px;color:var(--ink-faint,#7a6e5a);">
-        Angle
-        <select id="rg-angle" style="font:12px ui-sans-serif,system-ui,sans-serif;padding:4px 8px;border:1px solid var(--line,#d8d0c0);background:#fff;border-radius:4px;">
-          <option value="straight_on">Straight on</option>
-          <option value="three_quarter">3/4 view</option>
-          <option value="wide">Wide</option>
-        </select>
-      </label>
-      <button id="rg-generate-btn" type="button"
-        style="background:#1f1808;border:1px solid #1f1808;color:#fff;padding:6px 14px;border-radius:14px;cursor:pointer;font-size:12px;letter-spacing:.06em;text-transform:uppercase;">
-        Generate Room
-      </button>
-      <span id="rg-generate-status" style="font-size:11px;color:var(--ink-faint,#888);margin-left:auto;"></span>
-    </div>
-    <script>
-    (function(){
-      var btn = document.getElementById('rg-generate-btn');
-      var statusEl = document.getElementById('rg-generate-status');
-      var roomTypeSel = document.getElementById('rg-room-type');
-      var angleSel = document.getElementById('rg-angle');
-      var designId = document.getElementById('rooms-gallery').dataset.designId;
-      btn.addEventListener('click', function(){
-        btn.disabled = true;
-        var originalLabel = btn.textContent;
-        btn.textContent = 'Generating…';
-        statusEl.textContent = 'rendering room (15-30s)…';
-        fetch('/api/room', {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
-          credentials: 'same-origin',
-          body: JSON.stringify({
-            design_id: parseInt(designId, 10),
-            roomType: roomTypeSel.value,
-            angle: angleSel.value
-          })
-        }).then(function(r){
-          return r.json().then(function(j){ return { ok: r.ok, j: j }; });
-        }).then(function(wrap){
-          if (!wrap.ok || wrap.j.ok === false) {
-            throw new Error((wrap.j && wrap.j.error) || ('http ' + (wrap.j.status || '?')));
-          }
-          statusEl.textContent = 'saved · refreshing gallery';
-          window.dispatchEvent(new CustomEvent('wallco:room-generated'));
-          setTimeout(function(){ statusEl.textContent = ''; }, 2000);
-        }).catch(function(e){
-          statusEl.textContent = 'error: ' + e.message;
-        }).then(function(){
-          btn.disabled = false;
-          btn.textContent = originalLabel;
-        });
-      });
-    })();
-    </script>
-    ` : ''}
-    <div id="rg-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px;"></div>
-    <div id="rg-empty" style="display:none;padding:32px;text-align:center;color:var(--ink-faint,#6e6e68);font-style:italic;">${_isAdmin ? 'No rooms generated for this pattern yet. Use the "Generate Room" button above to create one.' : 'No rooms generated for this pattern yet.'}</div>
-  </section>
-</main>
-${FOOTER}
-
-<script>
-(function(){
-  const sec = document.getElementById('rooms-gallery');
-  if (!sec) return;
-  const designId = sec.dataset.designId;
-  const params = new URLSearchParams(window.location.search);
-  let adminToken = params.get('admin') || localStorage.getItem('wallco_admin_token') || '';
-  if (params.get('admin')) localStorage.setItem('wallco_admin_token', adminToken);
-  if (adminToken) document.getElementById('rg-admin-badge').style.display = 'block';
-
-  async function refresh() {
-    const q = new URLSearchParams({ design_id: designId });
-    if (adminToken) { q.set('admin', adminToken); q.set('include_removed', '1'); }
-    let data;
-    try { data = await (await fetch('/api/rooms?' + q.toString())).json(); }
-    catch (e) { document.getElementById('rg-grid').innerHTML = '<div style="color:#b00020">' + e.message + '</div>'; return; }
-    const rooms = data.rooms || [];
-    const isAdmin = !!data.admin;
-    document.getElementById('rg-count').textContent = rooms.filter(r => !r.removed_at).length + ' saved';
-    document.getElementById('rg-empty').style.display = rooms.length ? 'none' : 'block';
-    document.getElementById('rg-grid').innerHTML = rooms.map(function(r){
-      const removed = !!r.removed_at;
-      const marketing = !!r.selected_for_marketing;
-      const tag = r.room_type ? r.room_type.replace(/_/g,' ') : '';
-      const dim = removed ? 'opacity:0.35;filter:grayscale(0.7)' : '';
-      const star = marketing ? '★' : '☆';
-      const adminCtrls = isAdmin
-        ? '<div style="position:absolute;top:6px;right:6px;display:flex;gap:4px;">' +
-            '<button data-id="' + r.id + '" data-act="marketing" title="Toggle marketing" style="background:rgba(0,0,0,0.75);color:#fff;border:0;border-radius:3px;padding:3px 7px;font-size:13px;cursor:pointer;">' + star + '</button>' +
-            (removed
-              ? '<button data-id="' + r.id + '" data-act="restore" title="Restore" style="background:rgba(0,0,0,0.75);color:#fff;border:0;border-radius:3px;padding:3px 7px;font-size:11px;cursor:pointer;">↺</button>'
-              : '<button data-id="' + r.id + '" data-act="remove" title="Remove" style="background:rgba(176,0,32,0.85);color:#fff;border:0;border-radius:3px;padding:3px 7px;font-size:11px;cursor:pointer;">×</button>'
-            ) +
-          '</div>'
-        : '';
-      return '<figure style="margin:0;background:var(--paper,#faf8f2);border:1px solid var(--rule,#e5e5e0);position:relative;' + dim + '">' +
-        '<a href="' + r.image_path + '" target="_blank" style="display:block;aspect-ratio:1/1;overflow:hidden;background:#000;" rel="noopener noreferrer">' +
-          '<img src="' + r.image_path + '" alt="Room with this pattern — ' + tag + '" loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block;" />' +
-        '</a>' +
-        adminCtrls +
-        '<figcaption style="padding:8px 10px 10px;font:11px/1.4 ui-sans-serif,system-ui,sans-serif;color:var(--ink-faint,#6e6e68);display:flex;justify-content:space-between;gap:8px;">' +
-          '<span>' + tag + (marketing ? ' · <strong style="color:#9c7100;">marketing</strong>' : '') + '</span>' +
-          '<time>' + new Date(r.created_at).toLocaleDateString('en-US',{month:'short',day:'numeric'}) + '</time>' +
-        '</figcaption>' +
-      '</figure>';
-    }).join('');
-
-    if (isAdmin) {
-      document.querySelectorAll('#rg-grid button[data-act]').forEach(function(btn){
-        btn.addEventListener('click', async function(){
-          const id = btn.dataset.id, act = btn.dataset.act;
-          const headers = { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken };
-          let url, body = '{}';
-          if (act === 'remove') {
-            if (!confirm('Remove this room? (soft-delete — recoverable)')) return;
-            url = '/api/rooms/' + id + '/remove';
-          } else if (act === 'restore') {
-            url = '/api/rooms/' + id + '/restore';
-          } else if (act === 'marketing') {
-            const fig = btn.closest('figure');
-            const wasStar = btn.textContent.trim() === '★';
-            url = '/api/rooms/' + id + '/marketing';
-            body = JSON.stringify({ selected: !wasStar });
-          }
-          try {
-            const r = await fetch(url, { method: 'POST', headers: headers, body: body });
-            const j = await r.json();
-            if (!j.ok) alert('failed: ' + (j.error||'unknown'));
-            refresh();
-          } catch (e) { alert(e.message); }
-        });
-      });
-    }
-  }
-  refresh();
-  window.addEventListener('wallco:room-generated', refresh);
-})();
-</script>
-
-<script type="application/ld+json">${schema}</script>
-${HAMBURGER_JS}
-
-<!-- ── Real-world equivalents — Surface B from the library spec.
-     Lazy-fetches /api/design/${design.id}/library-equivalents and appends a
-     "Pairs well with real designer wallpapers" row to the page. Vendor names
-     are NEVER shown — cards say "Designer Wallcoverings · <SKU>". -->
-<section id="rw-equivalents-section" style="max-width:1200px;margin:48px auto 0;padding:32px 24px;border-top:1px solid var(--line,#e6dfd0);display:none">
-  <div style="margin-bottom:20px">
-    <p style="font:11px/1 var(--sans);letter-spacing:.28em;text-transform:uppercase;color:#8a6f44;margin:0 0 8px">From the designer library</p>
-    <h2 style="font-family:'Cormorant Garamond','Playfair Display',serif;font-weight:300;font-size:clamp(28px,3vw,40px);margin:0 0 6px;color:var(--ink,#1f1808)">Pairs well with real designer wallpapers</h2>
-    <p style="font:13px var(--sans);color:#7a6a52;margin:0">Hand-picked from our curated archive of designer wallcoverings — the closest matches to this pattern's palette.</p>
-  </div>
-  <div id="rw-eq-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px"></div>
-</section>
-<script>
-(function(){
-  fetch('/api/design/${design.id}/library-equivalents?n=5').then(function(r){return r.json();}).then(function(j){
-    if (!j || !j.ok || !j.equivalents || j.equivalents.length === 0) return;
-    var sec = document.getElementById('rw-equivalents-section');
-    var grid = document.getElementById('rw-eq-grid');
-    if (!sec || !grid) return;
-    grid.innerHTML = j.equivalents.map(function(e){
-      var sw = (e.palette||[]).slice(0,4).map(function(h){
-        return '<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:'+h+';margin-right:2px;border:1px solid rgba(0,0,0,.08)"></span>';
-      }).join('');
-      var safeTitle = String(e.title||'').replace(/[<>"]/g,'');
-      return '<a href="/library/'+e.id+'" style="display:block;color:inherit;text-decoration:none;background:#faf8f3;border:1px solid #e6dfd0;border-radius:6px;overflow:hidden;transition:transform .18s,box-shadow .18s" onmouseover="this.style.transform=\\'translateY(-2px)\\';this.style.boxShadow=\\'0 8px 20px rgba(0,0,0,.08)\\'" onmouseout="this.style.transform=\\'\\';this.style.boxShadow=\\'\\'">' +
-        '<span style="display:block;aspect-ratio:1/1;background:url(\\''+e.image_url+'\\') center 12%/cover"></span>' +
-        '<span style="display:block;padding:8px 10px;font:12px var(--sans,system-ui)">' +
-          '<span style="font-weight:500;line-height:1.2;display:block">'+safeTitle+'</span>' +
-          '<span style="font:10px ui-monospace,monospace;color:#8a7a5e">Designer Wallcoverings · '+String(e.sku||'').replace(/[<>"]/g,'')+'</span><br>' +
-          '<span style="display:inline-block;margin-top:4px">'+sw+'</span>' +
-        '</span></a>';
-    }).join('');
-    sec.style.display = 'block';
-  }).catch(function(){ /* library not mounted — no-op */ });
-})();
-</script>
-</body>
-</html>`);
-});
-
-// ── /design/:id/tearsheet — print-ready single-page PDF for trade use
-// Designers email this to clients or print to PDF. Optimized for 8.5×11 portrait.
-// No header, no hamburger — just hero + room + specs + DW contact + QR-style URL block.
-app.get('/design/:id/tearsheet', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(404).send('Not found');
-  let design = DESIGNS.find(d => d.id === id);
-  if (!design) return res.status(404).send('Design not found');
-  const roomKey = (design.room_mockups || []).includes('living_room') ? 'living_room'
-                : (design.room_mockups || []).includes('bedroom') ? 'bedroom'
-                : (design.room_mockups || []).includes('office')  ? 'office'
-                : (design.room_mockups || [])[0];
-  const roomImg = roomKey ? `/designs/room/design_${design.id}_${roomKey}.png` : null;
-  const motifs = (design.motifs || []).slice(0, 6).join(' · ');
-  const created = design.created_at ? new Date(design.created_at).toISOString().slice(0,10) : '';
-  const designUrl = `https://wallco.ai/design/${design.id}`;
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-  // Per-design immutable doc — safe to cache 24h. Override the global no-store HTML rule.
-  // 304 path: ETag from design.created_at (falls back to today's date if missing).
-  const tsLM = design.created_at ? new Date(design.created_at) : new Date();
-  if (maybe304(req, res, tsLM)) return;
-  res.setHeader('Cache-Control', 'public, max-age=86400, must-revalidate');
-  res.type('html').send(`<!doctype html><html lang="en"><head>
-<meta charset="utf-8"><title>${esc(design.title)} — Tear Sheet | wallco.ai</title>
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<meta name="robots" content="noindex,nofollow">
-<style>
-  @page { size: letter portrait; margin: 0.5in; }
-  * { box-sizing: border-box; }
-  body { font-family: 'Georgia', 'Times New Roman', serif; color:#1a1a1a; margin:0; padding:32px; background:#fff; max-width:8.5in; margin-inline:auto; }
-  .ts-header { display:flex; justify-content:space-between; align-items:baseline; border-bottom:2px solid #1a1a1a; padding-bottom:10px; margin-bottom:18px; }
-  .ts-brand { font:14px/1 -apple-system, system-ui, sans-serif; letter-spacing:.12em; text-transform:uppercase; font-weight:600; }
-  .ts-tag { font:11px/1 -apple-system, system-ui, sans-serif; color:#666; letter-spacing:.04em; }
-  .ts-title { font:300 38px/1.1 Georgia, serif; margin:0 0 6px 0; }
-  .ts-sub { font:13px/1.4 -apple-system, system-ui, sans-serif; color:#555; margin-bottom:18px; }
-  .ts-hero { display:grid; grid-template-columns: 1fr 1fr; gap:14px; margin-bottom:18px; }
-  .ts-hero .swatch, .ts-hero .room { aspect-ratio: 1/1; background:#f4f1ea center/cover no-repeat; border:1px solid #d8d2c4; }
-  .ts-specs { display:grid; grid-template-columns: 130px 1fr; gap:6px 14px; font:12px/1.5 -apple-system, system-ui, sans-serif; margin-bottom:16px; }
-  .ts-specs dt { color:#666; text-transform:uppercase; letter-spacing:.06em; font-size:10px; padding-top:2px; }
-  .ts-specs dd { margin:0; color:#1a1a1a; font-weight:500; }
-  .ts-colorblock { display:flex; align-items:center; gap:10px; padding:10px 14px; background:#faf8f3; border:1px solid #ebe5d6; border-radius:4px; margin-bottom:18px; font:12px -apple-system, system-ui, sans-serif; }
-  .ts-colorchip { width:32px; height:32px; border-radius:50%; border:1px solid rgba(0,0,0,.15); flex-shrink:0; }
-  .ts-section { font:11px/1 -apple-system, system-ui, sans-serif; text-transform:uppercase; letter-spacing:.1em; color:#888; margin:14px 0 6px; }
-  .ts-footer { margin-top:24px; padding-top:14px; border-top:1px solid #ddd; display:grid; grid-template-columns: 2fr 1fr; gap:20px; font:11px/1.5 -apple-system, system-ui, sans-serif; color:#444; }
-  .ts-footer strong { color:#1a1a1a; }
-  .ts-url { font:11px/1.4 ui-monospace, Menlo, monospace; color:#1a1a1a; word-break: break-all; }
-  .no-print { margin: 18px 0; }
-  .no-print button { padding:10px 18px; font:13px -apple-system, system-ui, sans-serif; background:#1a1a1a; color:#fff; border:0; border-radius:4px; cursor:pointer; margin-right:8px; }
-  .no-print a { padding:10px 18px; font:13px -apple-system, system-ui, sans-serif; background:#fff; color:#1a1a1a; text-decoration:none; border:1px solid #1a1a1a; border-radius:4px; display:inline-block; }
-  @media print { .no-print { display:none; } body { padding:0; } }
-</style></head><body>
-<div class="no-print">
-  <button onclick="window.print()">Print or Save as PDF</button>
-  <a href="/design/${design.id}">← Back to design</a>
-</div>
-<div class="ts-header">
-  <div class="ts-brand">wallco.ai · Designer Wallcoverings</div>
-  <div class="ts-tag">Tear Sheet · ${created}</div>
-</div>
-<h1 class="ts-title">${esc(design.title)}</h1>
-<div class="ts-sub">Design #${design.id} · ${esc(design.category || 'mixed')}${motifs ? ' · ' + esc(motifs) : ''}</div>
-
-<div class="ts-hero">
-  <div class="swatch" style="background-image:url('${esc(design.image_url)}')" aria-label="${esc(design.title)} pattern"></div>
-  ${roomImg
-    ? `<div class="room" style="background-image:url('${esc(roomImg)}')" aria-label="In-room preview"></div>`
-    : `<div class="room" style="display:flex;align-items:center;justify-content:center;font:11px -apple-system,system-ui,sans-serif;color:#888;text-align:center;padding:20px">Room preview rendered on request</div>`
-  }
-</div>
-
-<div class="ts-colorblock">
-  <div class="ts-colorchip" style="background:${esc(design.dominant_hex || '#888')}"></div>
-  <div><strong>Dominant color:</strong> ${esc(design.dominant_hex || 'n/a')} · This is the design's primary palette anchor; complementary tones are derived from the full image extraction.</div>
-</div>
-
-<div class="ts-section">Pattern Specs</div>
-<dl class="ts-specs">
-  <dt>SKU / Handle</dt><dd>${esc(design.handle || 'wallco-' + String(design.id).padStart(4,'0'))}</dd>
-  <dt>Category</dt><dd>${esc(design.category || 'mixed')}</dd>
-  ${motifs ? `<dt>Motifs</dt><dd>${esc(motifs)}</dd>` : ''}
-  <dt>Tile Size</dt><dd>24×24 in seamless · 150 DPI</dd>
-  <dt>Repeat</dt><dd>24" horizontal · 24" vertical (full drop)</dd>
-  <dt>Format</dt><dd>AI-original · seamless tile · scalable to any wall</dd>
-  <dt>Substrate</dt><dd>Type II vinyl · nonwoven paper · peel-and-stick · canvas mural</dd>
-  <dt>Fire Rating</dt><dd>Class A available (Type II vinyl substrate, ASTM E84)</dd>
-  <dt>Cleanability</dt><dd>Scrubbable (vinyl) · Spongeable (paper)</dd>
-  <dt>Lead Time</dt><dd>Sample: 3–5 business days · Production: ~2 weeks</dd>
-  <dt>Sample Size</dt><dd>8.5 × 11 in</dd>
-</dl>
-
-<div class="ts-section">Direct URL</div>
-<div class="ts-url">${esc(designUrl)}</div>
-
-<div class="ts-footer">
-  <div>
-    <strong>Designer Wallcoverings</strong><br>
-    15442 Ventura Bl #102, Sherman Oaks CA 91403<br>
-    <strong>info@designerwallcoverings.com</strong> · designerwallcoverings.com<br>
-    AI-original wallpaper, hand-curated. Free trade samples.
-  </div>
-  <div style="text-align:right">
-    Generated ${new Date().toISOString().slice(0,10)}<br>
-    <span style="font:10px ui-monospace, Menlo, monospace; color:#888">${esc(design.handle || 'wallco-'+design.id)}</span>
-  </div>
-</div>
-</body></html>`);
-});
-
-// ── /compare — side-by-side compare up to 4 designs
-// ?ids=72,161,139 (server-rendered) OR uses localStorage[wallco-compare] if ids absent + browser hits with JS.
-app.get('/compare', (req, res) => {
-  const rawIds = String(req.query.ids || '').split(',').map(s => parseInt(s.trim(), 10)).filter(n => Number.isFinite(n) && n > 0).slice(0, 4);
-  const items = rawIds.map(id => DESIGNS.find(d => d.id === id)).filter(Boolean);
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-  const cols = items.length || 1;
-  const colWidth = items.length ? `${100/items.length}%` : '100%';
-  // View mode: 'specs' (default — side-by-side spec table) or 'grid' (large 2×2 image grid).
-  // ?view=grid or legacy ?grid=1 enables grid mode.
-  const view = (String(req.query.view || '').toLowerCase() === 'grid' || req.query.grid === '1') ? 'grid' : 'specs';
-  const idsQS = rawIds.join(',');
-  // Deterministic ETag — page output depends only on (catalog state, view, ids in order).
-  // Lets browsers/CDNs skip re-rendering when the comparison hasn't changed (the IDs +
-  // view are stable while a designer drags the link around / re-opens the tab).
-  const _cmpEtag = `"cmp-${CATALOG_LAST_MODIFIED.getTime()}-${view}-${idsQS || 'empty'}"`;
-  res.setHeader('ETag', _cmpEtag);
-  res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
-  if (req.headers['if-none-match'] === _cmpEtag) return res.status(304).end();
-  const cellsForRow = (fn) => items.map(d => `<td style="padding:10px 14px;border-top:1px solid var(--line,#eee);font:13px var(--sans,system-ui);width:${colWidth};vertical-align:top">${fn(d)}</td>`).join('');
-  res.type('html').send(`${htmlHead({
-    title: 'Compare Designs — wallco.ai',
-    description: items.length ? `Side-by-side comparison of ${items.length} wallco.ai designs.` : 'Compare wallco.ai designs side-by-side.',
-    canonical: 'https://wallco.ai/compare'
-  })}
-<body>
-${htmlHeader('/compare')}
-<main>
-<section class="catalog-section" style="padding:24px 40px">
-  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:14px">
-    <div>
-      <h1 style="font:300 36px/1.1 var(--serif,Georgia,serif);margin:0 0 6px">Compare designs</h1>
-      <p style="font:13px var(--sans,system-ui);color:var(--ink-soft,#555);margin:0 0 18px">${items.length ? `${items.length} of 4 selected.` : 'No designs selected. Use "+ Compare" on any design page to add up to 4.'}</p>
-    </div>
-    ${items.length ? `
-    <div role="tablist" aria-label="Compare view" style="display:inline-flex;border:1px solid var(--line,#ddd);border-radius:999px;overflow:hidden;font:12px var(--sans,system-ui)">
-      <a href="/compare?ids=${idsQS}" role="tab" aria-selected="${view==='specs'?'true':'false'}" style="padding:6px 14px;text-decoration:none;background:${view==='specs'?'var(--ink,#111)':'transparent'};color:${view==='specs'?'var(--bg,#fff)':'var(--ink-soft,#555)'}">Specs</a>
-      <a href="/compare?ids=${idsQS}&view=grid" role="tab" aria-selected="${view==='grid'?'true':'false'}" style="padding:6px 14px;text-decoration:none;background:${view==='grid'?'var(--ink,#111)':'transparent'};color:${view==='grid'?'var(--bg,#fff)':'var(--ink-soft,#555)'}">Image grid</a>
-    </div>` : ''}
-  </div>
-
-  ${items.length === 0 ? `
-    <div id="compare-empty" style="padding:40px;border:1px dashed var(--line,#ccc);border-radius:8px;text-align:center;font:14px var(--sans,system-ui);color:var(--ink-faint,#888)">
-      <p>Visit <a href="/designs" style="color:var(--ink,#111)">/designs</a>, click <strong>+ Compare</strong> on the designs you want to evaluate side-by-side, then return here.</p>
-      <p style="font:12px var(--sans,system-ui);margin-top:14px">Or share a link directly: <code>/compare?ids=72,161</code></p>
-    </div>
-
-    <!-- Discovery rail: random designs to start a compare -->
-    <div style="margin-top:32px">
-      <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px">
-        <h2 style="font:300 22px/1.2 var(--serif,Georgia,serif);margin:0">More designs to explore</h2>
-        <button type="button" id="rail-refresh" style="font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);padding:5px 12px;border-radius:999px;cursor:pointer">↻ Shuffle</button>
-      </div>
-      <div id="compare-rail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px"></div>
-      <p style="font:12px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:12px;text-align:center">Click any design to open it, then hit <strong>+ Compare</strong> on the PDP.</p>
-    </div>
-    <script>
-      // If localStorage has stored ids, auto-redirect to the URL form so the share-link works.
-      try {
-        var stored = JSON.parse(localStorage.getItem('wallco-compare') || '[]');
-        if (Array.isArray(stored) && stored.length > 0) {
-          window.location.href = '/compare?ids=' + stored.slice(0,4).join(',');
-        }
-      } catch(e) {}
-      (function(){
-        var rail = document.getElementById('compare-rail');
-        var btn = document.getElementById('rail-refresh');
-        if (!rail) return;
-        var esc = function(s){ return String(s||'').replace(/[<>&"]/g, function(c){ return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]; }); };
-        function load(){
-          rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Loading…</div>';
-          fetch('/api/designs/random?n=12').then(function(r){ return r.json(); }).then(function(j){
-            if (!j.results || !j.results.length) { rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">No designs available yet.</div>'; return; }
-            rail.innerHTML = j.results.map(function(r){
-              var dot = r.dominant_hex ? '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
-              return '<a href="'+esc(r.url)+'" style="display:block;text-decoration:none;color:inherit"><div style="aspect-ratio:1/1.15;background:#f4f1ea url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:6px;border:1px solid var(--line,#e6e2d8);margin-bottom:6px"></div><div style="font:13px var(--sans,system-ui);color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title || ('Design #'+r.id))+'</div><div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:2px">'+dot+esc(r.category||'mixed')+'</div></a>';
-            }).join('');
-          }).catch(function(){ rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Couldn\\'t load — try again.</div>'; });
-        }
-        load();
-        if (btn) btn.addEventListener('click', load);
-      })();
-    </script>
-  ` : `
-    ${view === 'grid' ? `
-    <div class="compare-grid" style="display:grid;grid-template-columns:repeat(${Math.min(items.length, 2)}, 1fr);gap:18px;margin-bottom:24px">
-      ${items.map(d => `
-      <figure style="margin:0;display:flex;flex-direction:column;gap:8px">
-        <a href="/design/${d.id}" style="display:block;aspect-ratio:1;background:#f4f1ea url('${esc(d.image_url)}') center/cover;border-radius:8px;border:1px solid var(--line,#e6e2d8);text-decoration:none"></a>
-        <figcaption style="display:flex;justify-content:space-between;align-items:baseline;gap:8px;flex-wrap:wrap">
-          <div style="min-width:0;flex:1">
-            <div style="font:500 16px/1.3 var(--serif,Georgia,serif);color:var(--ink,#111)">${esc(d.title || ('Design #' + d.id))}</div>
-            <div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#888);margin-top:2px"><span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:${esc(d.dominant_hex||'#888')};border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>${esc(d.category||'mixed')} · <a href="/design/${d.id}/tearsheet" target="_blank" rel="noopener noreferrer" style="color:inherit;text-decoration:underline">tear sheet</a></div>
-          </div>
-          <button type="button" data-rmid="${d.id}" class="compare-rm" style="padding:4px 10px;font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);border-radius:3px;cursor:pointer">Remove</button>
-        </figcaption>
-      </figure>`).join('')}
-    </div>
-    ` : `
-    <table style="width:100%;border-collapse:collapse;table-layout:fixed">
-      <thead><tr>${items.map(d => `<th style="padding:0;width:${colWidth};vertical-align:top">
-        <a href="/design/${d.id}" style="display:block;text-decoration:none;color:inherit">
-          <div style="aspect-ratio:1;background:#f4f1ea url('${esc(d.image_url)}') center/cover;border-radius:6px;border:1px solid var(--line,#ddd);margin-bottom:10px"></div>
-          <div style="font:500 16px/1.3 var(--serif,Georgia,serif);color:var(--ink,#111)">${esc(d.title || 'Design #' + d.id)}</div>
-          <div style="font:11px/1.4 var(--sans,system-ui);color:var(--ink-faint,#888);margin-top:3px"><span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:${esc(d.dominant_hex||'#888')};border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>${esc(d.category || 'mixed')}</div>
-        </a>
-        <button type="button" data-rmid="${d.id}" class="compare-rm" style="margin-top:8px;padding:4px 10px;font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);border-radius:3px;cursor:pointer">Remove</button>
-      </th>`).join('')}</tr></thead>
-      <tbody style="font:13px var(--sans,system-ui)">
-        <tr><td colspan="${cols}" style="padding:14px 0 6px;font:11px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee)">Specs</td></tr>
-        <tr>${cellsForRow(d => `<strong>SKU</strong> · ${esc(d.handle || 'wallco-'+String(d.id).padStart(4,'0'))}`)}</tr>
-        <tr>${cellsForRow(d => `<strong>Category</strong> · ${esc(d.category || 'mixed')}`)}</tr>
-        <tr>${cellsForRow(d => `<strong>Motifs</strong> · ${esc((d.motifs||[]).slice(0,5).join(', ') || '—')}`)}</tr>
-        <tr>${cellsForRow(d => `<strong>Color</strong> · <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${esc(d.dominant_hex||'#888')};border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>${esc(d.dominant_hex || 'n/a')}`)}</tr>
-        <tr>${cellsForRow(d => `<strong>Room previews</strong> · ${(d.room_mockups||[]).length}`)}</tr>
-        <tr>${cellsForRow(d => `<strong>Created</strong> · ${d.created_at ? new Date(d.created_at).toISOString().slice(0,10) : '—'}`)}</tr>
-        <tr>${cellsForRow(_d => `<strong>Tile</strong> · 24×24 in seamless`)}</tr>
-        <tr>${cellsForRow(_d => `<strong>Fire rating</strong> · Class A (Type II vinyl)`)}</tr>
-        <tr><td colspan="${cols}" style="padding:14px 0 6px;font:11px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);border-bottom:1px solid var(--line,#eee);padding-top:18px">Actions</td></tr>
-        <tr>${items.map(d => `<td style="padding:10px 14px;border-top:1px solid var(--line,#eee);font:13px var(--sans,system-ui);vertical-align:top">
-          <a href="/design/${d.id}" style="display:inline-block;padding:6px 12px;font:12px var(--sans,system-ui);background:var(--ink,#111);color:var(--bg,#fff);text-decoration:none;border-radius:4px;margin-right:6px">View</a>
-          <a href="/samples?design=${d.id}" style="display:inline-block;padding:6px 12px;font:12px var(--sans,system-ui);background:transparent;color:var(--ink,#111);text-decoration:none;border:1px solid var(--ink,#111);border-radius:4px;margin-right:6px">Sample</a>
-          <a href="/design/${d.id}/tearsheet" target="_blank" rel="noopener noreferrer" style="display:inline-block;padding:6px 12px;font:12px var(--sans,system-ui);background:transparent;color:var(--ink,#111);text-decoration:none;border:1px solid var(--ink,#111);border-radius:4px">Tear sheet</a>
-        </td>`).join('')}</tr>
-      </tbody>
-    </table>
-    `}
-    <div style="margin-top:24px;display:flex;gap:12px;flex-wrap:wrap;align-items:center">
-      <a href="/brief?ids=${rawIds.join(',')}" target="_blank" rel="noopener noreferrer" id="compare-brief" style="padding:10px 18px;font:13px var(--sans,system-ui);background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);text-decoration:none;border:0;border-radius:4px;font-weight:500" title="Export this comparison as a printable multi-design proposal PDF">Export as Brief →</a>
-      <button type="button" id="compare-print" style="padding:10px 18px;font:13px var(--sans,system-ui);background:var(--ink,#111);color:var(--bg,#fff);border:0;border-radius:4px;cursor:pointer">Print this view</button>
-      <button type="button" id="compare-copy" style="padding:10px 18px;font:13px var(--sans,system-ui);background:transparent;color:var(--ink,#111);border:1px solid var(--ink,#111);border-radius:4px;cursor:pointer">Copy share link</button>
-      <a href="/designs" style="padding:10px 18px;font:13px var(--sans,system-ui);background:transparent;color:var(--ink,#111);text-decoration:none;border:1px solid var(--line,#ddd);border-radius:4px">Browse more designs</a>
-      <details style="margin-left:auto;font:12px var(--sans,system-ui);color:var(--ink-soft,#555)">
-        <summary style="cursor:pointer;padding:6px 10px;border:1px solid var(--line,#ddd);border-radius:4px;list-style:none">Brief options ▾</summary>
-        <div style="margin-top:8px;padding:12px;background:var(--card-bg,#faf8f3);border:1px solid var(--line,#ebe5d6);border-radius:6px;display:grid;gap:8px;min-width:280px">
-          <label>Project name<br><input id="brief-project" type="text" placeholder="e.g. Living Room" style="margin-top:4px;width:100%;padding:6px 8px;font:13px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <label>Client name<br><input id="brief-client" type="text" placeholder="e.g. Smith Residence" style="margin-top:4px;width:100%;padding:6px 8px;font:13px var(--sans);border:1px solid var(--line,#ddd);border-radius:4px"></label>
-          <button type="button" id="brief-go" style="padding:8px 14px;background:var(--ink,#111);color:var(--bg,#fff);border:0;border-radius:4px;cursor:pointer;font:13px var(--sans)">Open Brief →</button>
-        </div>
-      </details>
-    </div>
-    <script>
-      (function(){
-        var stored = ${JSON.stringify(rawIds)};
-        var currentView = ${JSON.stringify(view)};
-        try { localStorage.setItem('wallco-compare', JSON.stringify(stored)); } catch(e){}
-        // Persist whichever view we landed on so the next /compare visit reopens it.
-        try { localStorage.setItem('wallco-compare-view', currentView); } catch(e){}
-        // If the URL did NOT specify a view param and a saved preference exists, redirect
-        // to honor it. Only redirects when redirect target differs from current page.
-        try {
-          var url = new URL(location.href);
-          var hasExplicitView = url.searchParams.has('view') || url.searchParams.get('grid') === '1';
-          var saved = localStorage.getItem('wallco-compare-view');
-          if (!hasExplicitView && saved && saved !== currentView && (saved === 'grid' || saved === 'specs')) {
-            if (saved === 'grid') url.searchParams.set('view', 'grid'); else url.searchParams.delete('view');
-            location.replace(url.toString());
-            return; // bail — page about to reload
-          }
-        } catch(e){}
-        // Preserve current view across Remove clicks too — so removing one design while in
-        // grid view stays in grid view.
-        var viewQS = currentView === 'grid' ? '&view=grid' : '';
-        document.querySelectorAll('.compare-rm').forEach(function(btn){
-          btn.addEventListener('click', function(){
-            var rid = parseInt(btn.dataset.rmid,10);
-            var next = stored.filter(function(x){ return x !== rid; });
-            try { localStorage.setItem('wallco-compare', JSON.stringify(next)); } catch(e){}
-            window.location.href = next.length ? '/compare?ids=' + next.join(',') + viewQS : '/compare';
-          });
-        });
-        var pBtn = document.getElementById('compare-print');
-        if (pBtn) pBtn.addEventListener('click', function(){ window.print(); });
-        var cBtn = document.getElementById('compare-copy');
-        if (cBtn) cBtn.addEventListener('click', async function(){
-          var url = location.origin + '/compare?ids=' + stored.join(',');
-          try { await navigator.clipboard.writeText(url); cBtn.textContent = 'Copied!'; setTimeout(function(){ cBtn.textContent='Copy share link'; }, 1800); }
-          catch(e) { window.prompt('Copy:', url); }
-        });
-        // Brief-options details — assembles /brief URL with project+client query params
-        var bGo = document.getElementById('brief-go');
-        if (bGo) bGo.addEventListener('click', function(){
-          var p = (document.getElementById('brief-project').value || '').trim();
-          var c = (document.getElementById('brief-client').value || '').trim();
-          var qs = ['ids=' + stored.join(',')];
-          if (p) qs.push('project=' + encodeURIComponent(p));
-          if (c) qs.push('client=' + encodeURIComponent(c));
-          window.open('/brief?' + qs.join('&'), '_blank', 'noopener');
-        });
-      })();
-    </script>
-  `}
-</section>
-</main>
-${FOOTER}
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ─────────────────────────────────────────────────────────────────────────
-// SAMPLE BASKET — mirrors the /compare pattern (localStorage[wallco-basket],
-// max 12 items, additive to the single-design /samples flow). Multi-design
-// trade sample requests get logged to data/sample-requests.jsonl. No email,
-// no DB writes — just append-only JSONL (YOLO + comms-compliance rules).
-// ─────────────────────────────────────────────────────────────────────────
-app.get('/basket', (req, res) => {
-  const rawIds = String(req.query.ids || '').split(',').map(s => parseInt(s.trim(), 10)).filter(n => Number.isFinite(n) && n > 0).slice(0, 12);
-  const items = rawIds.map(id => DESIGNS.find(d => d.id === id)).filter(Boolean);
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-
-  res.type('html').send(`${htmlHead({
-    title: 'Sample Basket — wallco.ai',
-    description: items.length ? `Request trade samples for ${items.length} wallco.ai designs.` : 'Build a multi-design sample request basket — up to 12 designs, one trade form.',
-    canonical: 'https://wallco.ai/basket'
-  })}
-<body>
-${htmlHeader('/basket')}
-<main>
-<section class="catalog-section" style="padding:24px 40px;max-width:880px;margin:0 auto">
-  <h1 style="font:300 36px/1.1 var(--serif,Georgia,serif);margin:0 0 6px">Sample basket</h1>
-  <p id="basket-count-line" style="font:13px var(--sans,system-ui);color:var(--ink-soft,#555);margin:0 0 18px">${items.length ? `${items.length} of 12 selected.` : 'Your basket is empty.'}</p>
-
-  ${items.length === 0 ? `
-    <div id="basket-empty" style="padding:48px 28px;border:1px dashed var(--line,#ccc);border-radius:8px;text-align:center;color:var(--ink-soft,#555);font:14px/1.6 var(--sans,system-ui);background:var(--card-bg,#faf8f3)">
-      <div style="font-size:36px;line-height:1;margin-bottom:12px">▢</div>
-      <p style="margin:0 0 8px;font:300 22px/1.2 var(--serif,Georgia,serif);color:var(--ink,#111)">Build a sample basket.</p>
-      <p style="margin:0 0 18px;max-width:38em;margin-inline:auto">Memo samples are <strong>free for the trade</strong>, ship in 3–5 business days, and are 8.5 × 11 in — enough to read the pattern in your project's light. Add up to 12, request once.</p>
-      <p style="margin:0"><a href="/designs" class="btn-outline" style="display:inline-block;padding:9px 18px;font:13px var(--sans);text-decoration:none">Browse the catalog →</a></p>
-      <p style="margin:14px 0 0;font:12px var(--sans);color:var(--ink-faint,#999)">Tap <strong>+ Sample</strong> on any design page · or share a basket link directly: <code>/basket?ids=72,161</code></p>
-    </div>
-
-    <!-- Discovery rail: random designs to start a basket -->
-    <div style="margin-top:32px">
-      <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px">
-        <h2 style="font:300 22px/1.2 var(--serif,Georgia,serif);margin:0">More designs to explore</h2>
-        <button type="button" id="rail-refresh" style="font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);padding:5px 12px;border-radius:999px;cursor:pointer">↻ Shuffle</button>
-      </div>
-      <div id="basket-rail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px"></div>
-      <p style="font:12px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:12px;text-align:center">Click any design to open it, then hit <strong>+ Sample</strong> on the PDP.</p>
-    </div>
-    <script>
-      // If localStorage has stored ids, auto-redirect to the URL form so the share-link works.
-      try {
-        var stored = JSON.parse(localStorage.getItem('wallco-basket') || '[]');
-        if (Array.isArray(stored) && stored.length > 0) {
-          window.location.href = '/basket?ids=' + stored.slice(0,12).join(',');
-        }
-      } catch(e) {}
-      (function(){
-        var rail = document.getElementById('basket-rail');
-        var btn = document.getElementById('rail-refresh');
-        if (!rail) return;
-        var esc = function(s){ return String(s||'').replace(/[<>&"]/g, function(c){ return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]; }); };
-        function load(){
-          rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Loading…</div>';
-          fetch('/api/designs/random?n=12').then(function(r){ return r.json(); }).then(function(j){
-            if (!j.results || !j.results.length) { rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">No designs available yet.</div>'; return; }
-            rail.innerHTML = j.results.map(function(r){
-              var dot = r.dominant_hex ? '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
-              return '<a href="'+esc(r.url)+'" style="display:block;text-decoration:none;color:inherit"><div style="aspect-ratio:1/1.15;background:#f4f1ea url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:6px;border:1px solid var(--line,#e6e2d8);margin-bottom:6px"></div><div style="font:13px var(--sans,system-ui);color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title || ('Design #'+r.id))+'</div><div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:2px">'+dot+esc(r.category||'mixed')+'</div></a>';
-            }).join('');
-          }).catch(function(){ rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Couldn\\'t load — try again.</div>'; });
-        }
-        load();
-        if (btn) btn.addEventListener('click', load);
-      })();
-    </script>
-  ` : `
-    <!-- Vertically stacked mini-cards (thumb + title + category + Remove) -->
-    <ul id="basket-list" style="list-style:none;margin:0 0 24px;padding:0;display:flex;flex-direction:column;gap:10px">
-      ${items.map(d => `<li data-id="${d.id}" style="display:flex;align-items:center;gap:14px;padding:12px;border:1px solid var(--line,#eee);border-radius:8px;background:var(--card-bg,#faf8f3)">
-        <a href="/design/${d.id}" style="flex:0 0 auto;text-decoration:none">
-          <div style="width:72px;height:72px;background:#f4f1ea url('${esc(d.image_url)}') center/cover;border-radius:6px;border:1px solid var(--line,#ddd)"></div>
-        </a>
-        <div style="flex:1 1 auto;min-width:0">
-          <div style="font:500 16px/1.3 var(--serif,Georgia,serif);color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
-            <a href="/design/${d.id}" style="color:inherit;text-decoration:none">${esc(d.title || 'Design #' + d.id)}</a>
-          </div>
-          <div style="font:11px/1.4 var(--sans,system-ui);color:var(--ink-faint,#888);margin-top:3px"><span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:${esc(d.dominant_hex||'#888')};border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>${esc(d.category || 'mixed')} · ${esc(d.handle || 'wallco-'+String(d.id).padStart(4,'0'))}</div>
-        </div>
-        <button type="button" data-rmid="${d.id}" class="basket-rm" style="flex:0 0 auto;padding:6px 12px;font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);border-radius:3px;cursor:pointer">Remove</button>
-      </li>`).join('')}
-    </ul>
-
-    <!-- Multi-design sample request form — re-uses /samples fields -->
-    <div class="sample-form-wrap" style="border-top:1px solid var(--line,#eee);padding-top:24px">
-      <h2 style="font:300 24px/1.2 var(--serif,Georgia,serif);margin:0 0 6px">Request samples for these ${items.length} designs</h2>
-      <p style="font:13px var(--sans,system-ui);color:var(--ink-soft,#555);margin:0 0 18px">We'll send physical swatches or high-res digital proofs. No charge, no obligation. Trade welcome.</p>
-      <form id="basket-form" class="sample-form">
-        <label>Your name <input type="text" name="name" required autocomplete="name" enterkeyhint="next" placeholder="Jane Smith"></label>
-        <label>Email <input type="email" name="email" required autocomplete="email" inputmode="email" enterkeyhint="next" placeholder="you@example.com"></label>
-        <label>Company / firm (optional) <input type="text" name="company" autocomplete="organization" enterkeyhint="next" placeholder="Smith Interiors"></label>
-        <label>Phone (optional) <input type="tel" name="phone" autocomplete="tel" inputmode="tel" enterkeyhint="next" placeholder="+1 (555) 000-0000"></label>
-        <label>Project notes (optional) <textarea name="notes" rows="3" enterkeyhint="send" placeholder="Site address, install date, square footage, anything else…"></textarea></label>
-        <button type="submit" class="btn-primary">Send sample request (${items.length} ${items.length === 1 ? 'design' : 'designs'})</button>
-      </form>
-      <div id="basket-result" style="display:none;margin-top:16px;padding:16px;border-radius:4px"></div>
-    </div>
-
-    <script>
-      (function(){
-        var stored = ${JSON.stringify(rawIds)};
-        try { localStorage.setItem('wallco-basket', JSON.stringify(stored)); } catch(e){}
-        document.querySelectorAll('.basket-rm').forEach(function(btn){
-          btn.addEventListener('click', function(){
-            var rid = parseInt(btn.dataset.rmid,10);
-            var next = stored.filter(function(x){ return x !== rid; });
-            try { localStorage.setItem('wallco-basket', JSON.stringify(next)); } catch(e){}
-            try { window.dispatchEvent(new CustomEvent('wallco-basket-changed', { detail: { count: next.length, design_id: rid, in_basket: false } })); } catch(e){}
-            window.location.href = next.length ? '/basket?ids=' + next.join(',') : '/basket';
-          });
-        });
-        var form = document.getElementById('basket-form');
-        var result = document.getElementById('basket-result');
-        if (form) form.addEventListener('submit', async function(e){
-          e.preventDefault();
-          var data = Object.fromEntries(new FormData(form));
-          data.ids = stored;
-          try {
-            var r = await fetch('/api/basket/submit', {
-              method: 'POST',
-              headers: {'Content-Type':'application/json'},
-              body: JSON.stringify(data)
-            });
-            var json = await r.json();
-            result.style.display = 'block';
-            if (json.ok) {
-              result.style.background = '#d4edda';
-              result.style.color = '#155724';
-              result.textContent = 'Thank you! Request ' + json.request_id + ' received. We\\'ll be in touch shortly.';
-              form.reset();
-              // Clear the basket after a successful submit
-              try { localStorage.removeItem('wallco-basket'); } catch(e){}
-            } else {
-              result.style.background = '#f8d7da';
-              result.style.color = '#721c24';
-              result.textContent = (json.error || 'Something went wrong') + ' — please email info@wallco.ai directly.';
-            }
-          } catch(err){
-            result.style.display = 'block';
-            result.style.background = '#f8d7da';
-            result.style.color = '#721c24';
-            result.textContent = 'Network error — please try again.';
-          }
-        });
-      })();
-    </script>
-  `}
-</section>
-</main>
-${FOOTER}
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── POST /api/basket/submit — multi-design sample request → JSONL (no email)
-app.post('/api/basket/submit', (req, res) => {
-  const body = req.body || {};
-  const name    = typeof body.name    === 'string' ? body.name.trim()    : '';
-  const email   = typeof body.email   === 'string' ? body.email.trim()   : '';
-  const company = typeof body.company === 'string' ? body.company.trim() : '';
-  const phone   = typeof body.phone   === 'string' ? body.phone.trim()   : '';
-  const notes   = typeof body.notes   === 'string' ? body.notes.trim()   : '';
-  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
-    return res.status(400).json({ ok: false, error: 'valid email required' });
-  }
-  const rawIds = Array.isArray(body.ids) ? body.ids : [];
-  const ids = rawIds.map(n => parseInt(n, 10)).filter(n => Number.isFinite(n) && n > 0);
-  const validIds = ids.filter(id => DESIGNS.some(d => d.id === id)).slice(0, 12);
-  if (validIds.length === 0) {
-    return res.status(400).json({ ok: false, error: 'at least one valid design id required' });
-  }
-
-  const ts = Date.now();
-  const request_id = 'sr_' + ts.toString(36) + '_' + Math.random().toString(36).slice(2, 8);
-  const record = {
-    request_id,
-    received_at: new Date(ts).toISOString(),
-    name, email, company, phone, notes,
-    ids: validIds,
-    ip: (req.headers['x-forwarded-for'] || req.ip || '').toString().split(',')[0].trim(),
-    user_agent: (req.headers['user-agent'] || '').toString().slice(0, 300)
-  };
-
-  try {
-    const outPath = path.join(__dirname, 'data', 'sample-requests.jsonl');
-    fs.appendFileSync(outPath, JSON.stringify(record) + '\n', 'utf8');
-  } catch (e) {
-    console.error('basket JSONL append error:', e.message);
-    return res.status(500).json({ ok: false, error: 'failed to record request' });
-  }
-  logEvent(req, 'sample_request_submitted', { request_id, design_count: (record.design_ids || []).length, design_ids: record.design_ids, email: record.email || null });
-
-  res.json({ ok: true, request_id });
-});
-
-// ── /brief?ids=72,161,139 — multi-design proposal/brief PDF (print-friendly)
-// Stacks tearsheets for up to 6 designs with a cover page. Print to PDF and send to a client.
-app.get('/brief', (req, res) => {
-  const rawIds = String(req.query.ids || '').split(',').map(s => parseInt(s.trim(), 10)).filter(n => Number.isFinite(n) && n > 0).slice(0, 6);
-  const designs = rawIds.map(id => DESIGNS.find(d => d.id === id)).filter(Boolean);
-  const projectName = String(req.query.project || '').slice(0, 80);
-  const clientName = String(req.query.client || '').slice(0, 80);
-  const esc = (s) => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
-  if (!designs.length) {
-    res.setHeader('Cache-Control', 'public, max-age=600, must-revalidate'); // landing page — short cache
-    return res.type('html').send(`<!doctype html><html><head><meta charset="utf-8"><title>Brief — wallco.ai</title>
-<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;max-width:600px;margin:60px auto;padding:24px;color:#333}a{color:#111}</style></head><body>
-<h1 style="font:300 32px/1.1 Georgia,serif">Build a design brief</h1>
-<p>Add up to 6 wallco.ai designs to a single proposal PDF — perfect for emailing to a client.</p>
-<p><strong>Usage:</strong> <code>/brief?ids=72,161,139&amp;client=Smith Residence&amp;project=Living Room</code></p>
-<p>Or visit any design page and add it to your <a href="/me/saved">Saved Designs</a>, then click <strong>Build proposal PDF</strong>.</p>
-<p>Alternatively, start a <a href="/compare">/compare</a>, then export the comparison as a brief.</p>
-</body></html>`);
-  }
-  // Cache the populated brief — query string (ids + project + client) is part of the cache key.
-  // max-age=3600 (1h) — shorter than tearsheet (24h) because designers may rotate project/client labels.
-  const briefLM = designs.reduce((acc, d) => {
-    const t = d.created_at ? new Date(d.created_at) : null;
-    return t && (!acc || t > acc) ? t : acc;
-  }, null) || new Date();
-  if (maybe304(req, res, briefLM)) return;
-  // ETag covers (ordered ids, project, client) alongside the briefLM that maybe304 already
-  // handles via If-Modified-Since. If-None-Match short-circuit catches the case where the
-  // designer comes back to the same brief link — share-link reopen, tab restore.
-  const _briefSig = require('crypto').createHash('sha1').update(rawIds.join(',') + '|' + projectName + '|' + clientName).digest('hex').slice(0, 16);
-  const _briefEtag = `"brief-${briefLM.getTime()}-${_briefSig}"`;
-  res.setHeader('ETag', _briefEtag);
-  if (req.headers['if-none-match'] === _briefEtag) return res.status(304).end();
-  res.setHeader('Cache-Control', 'public, max-age=3600, must-revalidate');
-  const today = new Date().toISOString().slice(0, 10);
-  const tearsheet = (d) => {
-    const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
-                  : (d.room_mockups || []).includes('bedroom') ? 'bedroom'
-                  : (d.room_mockups || []).includes('office')  ? 'office'
-                  : (d.room_mockups || [])[0];
-    const roomImg = roomKey ? `/designs/room/design_${d.id}_${roomKey}.png` : null;
-    const motifs = (d.motifs || []).slice(0, 6).join(' · ');
-    return `<section class="brief-page" style="page-break-after:always;padding:36px 28px;border-bottom:1px dashed #ccc">
-      <div style="display:flex;justify-content:space-between;align-items:baseline;border-bottom:1px solid #1a1a1a;padding-bottom:8px;margin-bottom:16px">
-        <div style="font:12px/1 -apple-system,sans-serif;letter-spacing:.1em;text-transform:uppercase">wallco.ai</div>
-        <div style="font:11px/1 -apple-system,sans-serif;color:#666">Design #${d.id} · ${today}</div>
-      </div>
-      <h2 style="font:300 30px/1.1 Georgia,serif;margin:0 0 4px">${esc(d.title || 'Design #' + d.id)}</h2>
-      <div style="font:13px/1.4 -apple-system,sans-serif;color:#555;margin-bottom:14px">${esc(d.category || 'mixed')}${motifs ? ' · ' + esc(motifs) : ''}</div>
-      <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px">
-        <div style="aspect-ratio:1/1;background:#f4f1ea url('${esc(d.image_url)}') center/cover;border:1px solid #d8d2c4;border-radius:4px"></div>
-        ${roomImg
-          ? `<div style="aspect-ratio:1/1;background:#f4f1ea url('${esc(roomImg)}') center/cover;border:1px solid #d8d2c4;border-radius:4px"></div>`
-          : `<div style="aspect-ratio:1/1;background:#f4f1ea;border:1px solid #d8d2c4;border-radius:4px;display:flex;align-items:center;justify-content:center;font:11px -apple-system,sans-serif;color:#888;text-align:center;padding:20px">Room preview on request</div>`
-        }
-      </div>
-      <div style="padding:10px 14px;background:#faf8f3;border:1px solid #ebe5d6;border-radius:4px;margin-bottom:12px;display:flex;align-items:center;gap:10px;font:12px -apple-system,sans-serif">
-        <div style="width:24px;height:24px;border-radius:50%;background:${esc(d.dominant_hex || '#888')};border:1px solid rgba(0,0,0,.15);flex-shrink:0"></div>
-        <div><strong>Dominant color:</strong> ${esc(d.dominant_hex || 'n/a')}</div>
-      </div>
-      <dl style="display:grid;grid-template-columns:110px 1fr;gap:4px 12px;font:11px/1.5 -apple-system,sans-serif;margin:0">
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">SKU</dt><dd style="margin:0;color:#1a1a1a;font-weight:500">${esc(d.handle || 'wallco-' + String(d.id).padStart(4,'0'))}</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">Tile</dt><dd style="margin:0">24×24 in seamless · 150 DPI</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">Substrate</dt><dd style="margin:0">Type II vinyl · nonwoven · peel-and-stick · canvas mural</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">Fire</dt><dd style="margin:0">Class A available (ASTM E84)</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">Cleanability</dt><dd style="margin:0">Scrubbable (vinyl) · Spongeable (paper)</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">Lead Time</dt><dd style="margin:0">Sample 3–5 days · Production ~2 weeks</dd>
-        <dt style="color:#666;text-transform:uppercase;letter-spacing:.06em;font-size:9px;padding-top:2px">URL</dt><dd style="margin:0;font:11px ui-monospace,Menlo,monospace;word-break:break-all">wallco.ai/design/${d.id}</dd>
-      </dl>
-    </section>`;
-  };
-  res.type('html').send(`<!doctype html><html lang="en"><head>
-<meta charset="utf-8"><title>${esc(projectName || clientName || 'Wallpaper Brief')} — wallco.ai</title>
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<meta name="robots" content="noindex,nofollow">
-<style>
-  @page { size: letter portrait; margin: 0.5in; }
-  * { box-sizing: border-box; }
-  body { font-family: Georgia, 'Times New Roman', serif; color:#1a1a1a; margin:0; padding:0; background:#fff; }
-  .brief-cover { padding:60px 40px 40px; min-height:9in; display:flex; flex-direction:column; justify-content:space-between; page-break-after:always }
-  .brief-cover h1 { font:300 56px/1.05 Georgia, serif; margin:0 0 10px }
-  .brief-cover .meta { font:13px/1.6 -apple-system, system-ui, sans-serif; color:#444; margin-top:18px }
-  .brief-cover .meta strong { color:#1a1a1a }
-  .brief-cover .thumbs { display:grid; grid-template-columns: repeat(${Math.min(designs.length, 3)}, 1fr); gap:10px; margin:40px 0 }
-  .brief-cover .thumbs > div { aspect-ratio:1/1; background:#f4f1ea center/cover no-repeat; border:1px solid #d8d2c4; border-radius:4px }
-  .brief-cover .footer { font:11px/1.5 -apple-system, system-ui, sans-serif; color:#555; border-top:1px solid #ddd; padding-top:14px; margin-top:auto }
-  .no-print { padding: 16px 40px; background:#faf8f3; border-bottom:1px solid #eee }
-  .no-print button { padding:10px 20px; font:13px -apple-system, system-ui, sans-serif; background:#1a1a1a; color:#fff; border:0; border-radius:4px; cursor:pointer; margin-right:8px }
-  .no-print a { padding:10px 20px; font:13px -apple-system, system-ui, sans-serif; background:#fff; color:#1a1a1a; text-decoration:none; border:1px solid #1a1a1a; border-radius:4px; display:inline-block }
-  @media print { .no-print { display:none } }
-</style></head><body>
-<div class="no-print">
-  <button onclick="window.print()">Print or Save as PDF</button>
-  <a href="/me/saved">← Back to saved</a>
-</div>
-<section class="brief-cover">
-  <div>
-    <div style="font:11px/1 -apple-system, system-ui, sans-serif; letter-spacing:.14em; text-transform:uppercase; color:#666; margin-bottom:30px">wallco.ai · Design Brief</div>
-    <h1>${esc(projectName || 'Wallpaper Proposal')}</h1>
-    ${clientName ? `<div style="font:18px/1.3 Georgia,serif;color:#555;margin-top:8px">Prepared for ${esc(clientName)}</div>` : ''}
-    <div class="meta">
-      <strong>${designs.length} design${designs.length === 1 ? '' : 's'}</strong> ·
-      ${today} ·
-      Designer Wallcoverings, Sherman Oaks CA
-    </div>
-    <div class="thumbs">
-      ${designs.slice(0, 3).map(d => `<div style="background-image:url('${esc(d.image_url)}')" aria-label="${esc(d.title)}"></div>`).join('')}
-    </div>
-  </div>
-  <div class="footer">
-    <strong>Designer Wallcoverings</strong> · 15442 Ventura Bl #102, Sherman Oaks CA 91403<br>
-    <strong>info@designerwallcoverings.com</strong> · designerwallcoverings.com<br>
-    AI-original wallpaper, hand-curated. Free trade samples on request.
-  </div>
-</section>
-${designs.map(tearsheet).join('')}
-<section style="padding:40px;text-align:center;font:12px/1.6 -apple-system, system-ui, sans-serif;color:#555">
-  <p><strong>Next step</strong></p>
-  <p>Request samples for any of the ${designs.length} designs in this brief —<br>email <strong>info@designerwallcoverings.com</strong> or call to speak with our trade team.</p>
-  <p style="margin-top:18px;color:#888">${esc(designs.map(d => d.handle || 'wallco-' + d.id).join(' · '))}</p>
-</section>
-</body></html>`);
-});
-
-// ── MURALS
-app.get('/murals', (req, res) => {
-  const q = String(req.query.q || '').toLowerCase().trim();
-  const murals = DESIGNS.filter(d => d.kind === 'mural' || d.kind === 'mural_panel');
-  let all = murals.length === 0 ? DESIGNS : murals; // fallback: show all if no mural-kind yet
-  // Server-side ?q= filter — mirrors the search endpoint's logic so the
-  // form-submit fallback works without JS.
-  if (q && q.length >= 2) {
-    const terms = q.split(/\s+/).filter(Boolean);
-    all = all.filter(d => {
-      const hay = [
-        (d.title || '').toLowerCase(),
-        (d.category || '').toLowerCase(),
-        (d.handle || '').toLowerCase(),
-        ...(d.motifs || []).map(m => String(m).toLowerCase())
-      ].join(' ');
-      return terms.every(t => hay.includes(t));
-    });
-  }
-
-  const cards = all.map(d => {
-    const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
-                  : (d.room_mockups || []).includes('bedroom')     ? 'bedroom'
-                  : (d.room_mockups || []).includes('office')      ? 'office'
-                  : (d.room_mockups || [])[0];
-    const roomCount = (d.room_mockups || []).length;
-    const badgeText = roomCount > 1 ? `${roomCount} rooms` : (roomCount === 1 ? 'In a room' : '');
-    const roomOverlay = roomKey
-      ? `<img class="card-room" src="/designs/room/design_${d.id}_${roomKey}.png" loading="lazy" alt="" aria-hidden="true"><span class="card-room-badge">${badgeText}</span>`
-      : '';
-    return `
-    <a href="/design/${d.id}" class="design-card${roomKey ? ' has-room' : ''}" aria-label="${cardAriaLabel(d)}">
-      <div class="card-img" style="background-image:url('${d.image_url}')" role="img" aria-label="${d.title} pattern thumbnail">${roomOverlay}</div>
-      <div class="card-meta">
-        <span class="card-title">${d.title}</span>
-        <span class="card-cat">${murals.length === 0 ? 'available as mural' : d.kind}</span>
-        <span class="color-dot" style="background:${d.dominant_hex}" aria-hidden="true"></span>
-      </div>
-    </a>`;
-  }).join('');
-
-  res.type('html').send(`${htmlHead({
-    title: 'Murals — wallco.ai | Original Wall Murals',
-    description: 'AI-original wall murals in any size, panelized for installation. Every design unique.',
-    canonical: 'https://wallco.ai/murals'
-  })}
-<body>
-${htmlHeader('/murals')}
-<main>
-<section class="catalog-section">
-  <div class="section-header" style="padding:40px 40px 0">
-    <h1>Murals</h1>
-    <p>Any design available as a custom-sized mural, panelized for wall installation.
-    ${murals.length === 0 ? 'All current designs can be ordered as murals — contact us.' : `${murals.length} mural designs available.`}${q ? ` · matching &ldquo;${q.replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]))}&rdquo; (${all.length} found)` : ''}</p>
-  </div>
-  <div class="catalog-toolbar" style="padding:18px 40px 0">
-    <form method="get" action="/murals" class="filter-form">
-      <div class="toolbar-row">
-        <div class="search-wrap" style="position:relative;flex:1;min-width:200px">
-          <input type="search" name="q" value="${q.replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]))}" placeholder="Search murals…" class="search-input" id="murals-search-input" autocomplete="off" aria-autocomplete="list" aria-controls="murals-search-suggest" aria-expanded="false" role="combobox" style="width:100%">
-          <div id="murals-search-suggest" role="listbox" aria-label="Search suggestions" style="display:none;position:absolute;left:0;right:0;top:100%;margin-top:4px;background:var(--bg,#fff);border:1px solid var(--line,#ddd);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.12);z-index:50;max-height:60vh;overflow-y:auto"></div>
-        </div>
-      </div>
-    </form>
-  </div>
-  <div class="design-grid catalog-grid" style="padding:24px 40px">
-    ${cards || '<p class="empty-state">No murals match that search.</p>'}
-  </div>
-</section>
-</main>
-${FOOTER}
-<script>
-(function(){
-  var sIn = document.getElementById('murals-search-input');
-  var sBox = document.getElementById('murals-search-suggest');
-  if (!sIn || !sBox) return;
-  var sTimer = null, sCtl = null, sIdx = -1, sItems = [];
-  var esc = function(s){ return String(s||'').replace(/[&<>"]/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); };
-  var hide = function(){ sBox.style.display='none'; sIn.setAttribute('aria-expanded','false'); sIdx=-1; };
-  var render = function(results, qStr, dym){
-    if (!results || !results.length) {
-      var dymRow = '';
-      if (dym && dym.length) {
-        dymRow = '<div style="padding:10px 14px 12px;border-top:1px solid var(--line,#f0eee9);background:#fafaf7"><div style="font:10px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint,#888);margin-bottom:6px">Did you mean</div><div style="display:flex;flex-wrap:wrap;gap:5px">'
-          + dym.slice(0,5).map(function(s){ return '<a href="/murals?q='+encodeURIComponent(s.word)+'" style="display:inline-flex;align-items:center;padding:4px 10px;font:11px var(--sans,system-ui);background:var(--bg,#fff);color:var(--ink,#111);border:1px solid var(--line,#e6e2d8);border-radius:999px;text-decoration:none">'+esc(s.word)+'</a>'; }).join('')
-          + '</div></div>';
-      }
-      sBox.innerHTML = '<div style="padding:14px 16px;font:13px var(--sans,system-ui);color:var(--ink-faint,#888)">No matches for &ldquo;'+esc(qStr)+'&rdquo;</div>' + dymRow;
-      sBox.style.display='block'; sIn.setAttribute('aria-expanded','true');
-      sItems = []; sIdx = -1; return;
-    }
-    var rows = results.slice(0, 8).map(function(r, i){
-      var img = r.image_url ? '<div style="width:44px;height:44px;flex-shrink:0;background:#f2efe8 url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:4px;border:1px solid var(--line,#eee)"></div>' : '<div style="width:44px;height:44px;flex-shrink:0;background:#f2efe8;border-radius:4px"></div>';
-      var dot = r.dominant_hex ? '<span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
-      return '<a href="'+esc(r.url)+'" role="option" data-idx="'+i+'" style="display:flex;align-items:center;gap:10px;padding:8px 12px;text-decoration:none;color:var(--ink,#111);border-bottom:1px solid var(--line,#f0eee9);font:13px var(--sans,system-ui)">'+img+'<div style="min-width:0;flex:1"><div style="font-weight:500;color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title||('Design #'+r.id))+'</div><div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#888);margin-top:2px">'+dot+esc(r.category||'')+'</div></div></a>';
-    }).join('');
-    var more = results.length > 8 ? '<a href="/murals?q='+encodeURIComponent(qStr)+'" style="display:block;padding:8px 12px;font:12px var(--sans,system-ui);color:var(--ink-soft,#555);text-align:center;background:var(--card-bg,#fafafa);text-decoration:none">See all '+results.length+' results →</a>' : '';
-    sBox.innerHTML = rows + more;
-    sBox.style.display='block'; sIn.setAttribute('aria-expanded','true');
-    sItems = Array.prototype.slice.call(sBox.querySelectorAll('a[role=option]'));
-    sIdx = -1;
-  };
-  var fetchSuggest = function(qStr){
-    if (sCtl) sCtl.abort(); sCtl = new AbortController();
-    fetch('/api/designs/search?q='+encodeURIComponent(qStr)+'&limit=24', { signal: sCtl.signal })
-      .then(function(r){ return r.json(); })
-      .then(function(j){ render(j.results||[], qStr, j.did_you_mean||[]); })
-      .catch(function(){});
-  };
-  sIn.addEventListener('input', function(){ var v = this.value.trim(); if (sTimer) clearTimeout(sTimer); if (v.length<2) { hide(); return; } sTimer = setTimeout(function(){ fetchSuggest(v); }, 180); });
-  sIn.addEventListener('keydown', function(e){
-    if (e.key === 'Escape') { hide(); return; }
-    if (sBox.style.display === 'none' || !sItems.length) return;
-    if (e.key === 'ArrowDown') { e.preventDefault(); sIdx = (sIdx+1) % sItems.length; sItems.forEach(function(a,i){ a.style.background = i===sIdx?'var(--card-bg,#fafafa)':''; }); sItems[sIdx].scrollIntoView({ block:'nearest' }); }
-    else if (e.key === 'ArrowUp') { e.preventDefault(); sIdx = (sIdx-1+sItems.length) % sItems.length; sItems.forEach(function(a,i){ a.style.background = i===sIdx?'var(--card-bg,#fafafa)':''; }); sItems[sIdx].scrollIntoView({ block:'nearest' }); }
-    else if (e.key === 'Enter' && sIdx >= 0) { e.preventDefault(); window.location = sItems[sIdx].href; }
-  });
-  document.addEventListener('click', function(e){ if (!sIn.contains(e.target) && !sBox.contains(e.target)) hide(); });
-})();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// /provenance → /about (content merged 2026-05-14; 301 keeps external links + old
-// sitemap entries pointing at the live About page instead of returning 404).
-app.get('/provenance', (req, res) => res.redirect(301, '/about'));
-
-// ── ABOUT
-app.get('/about', (req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'About — wallco.ai | How AI Wallpaper is Made',
-    description: 'Learn how wallco.ai generates original wallpaper patterns using AI. Every design is unique, AI-original, never licensed from a vendor.',
-    canonical: 'https://wallco.ai/about'
-  })}
-<body>
-${htmlHeader('/about')}
-<main class="prose-main">
-  <article class="prose">
-    <h1>How wallco.ai works</h1>
-
-    <h2>AI-original, never licensed</h2>
-    <p>Every pattern on wallco.ai was generated by an AI model — not sourced from a vendor catalog,
-    not licensed from a studio. Each design is produced from a text prompt and a unique random seed,
-    so the pattern that emerges is mathematically unrepeatable.</p>
-
-    <h2>The generation pipeline</h2>
-    <p>Each pattern is rendered by our proprietary AI-design system, drawing inspiration from
-    public-domain artwork in the Metropolitan Museum of Art, Wikimedia Commons, and the
-    Smithsonian Institution — institutions with open licenses (CC0 / Public Domain) that
-    permit derivative AI generation.</p>
-
-    <h2>Seamless tiling</h2>
-    <p>Each design is generated as a seamless tile — the edges match on all four sides, so it can
-    repeat across a wall without visible seams. Standard tile size is 24×24 inches at 150 DPI.</p>
-
-    <h2>Murals</h2>
-    <p>Any design can be printed as a custom-sized mural, panelized for your wall dimensions.
-    Panel splitting for installation is handled during order processing.</p>
-
-    <h2>About wallco.ai</h2>
-    <p>wallco.ai is a product of <a href="https://designerwallcoverings.com">Designer Wallcoverings</a>,
-    Sherman Oaks, CA. We combine 30 years of wallcovering expertise with frontier AI generation to
-    create patterns that have never existed before.</p>
-
-    <p><a href="/samples" class="btn-primary">Request a Free Sample</a></p>
-  </article>
-</main>
-${FOOTER}
-
-<script type="application/ld+json">${JSON.stringify({
-  "@context":"https://schema.org",
-  "@type":"WebPage",
-  "name":"About wallco.ai",
-  "url":"https://wallco.ai/about",
-  "description":"How wallco.ai generates original AI wallpaper patterns.",
-  "isPartOf":{"@type":"WebSite","url":"https://wallco.ai","name":"wallco.ai"}
-})}</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /age-themes — curated "best theme per age" comparison page
-// Honest framing: AI-generated starting point (ui-ux-designer subagent + debate-team-fast
-// adversarial review). Companion Figma file at figma.com/design/WmwSirHywv530eACPtbqyA.
-// APCA-W3 Lc calculator — extracted to shared module (2026-05-12 architect refactor).
-// Canonical at ~/Projects/wallco-ai/src/apca.js · mirrored at
-// ~/.claude/skills/age-adaptive-theme/lib/apca.js (identical bytes).
-const { apcaLc } = require('./src/apca');
-
-// Canonical band data — project-local (deploy-portable). Skill ships a
-// docs-mirror at ~/.claude/skills/age-adaptive-theme/bands.json (identical bytes;
-// diff to detect drift). Project-canonical because HOME=/root on Kamatera has no
-// ~/.claude (see feedback_deploy_portability_bands 2026-05-12).
-const AGE_THEME_DATA = require('./src/bands.json');
-
-app.get('/age-themes', (req, res) => {
-  const _isAdmin = isAdmin(req);
-  if (!_isAdmin) return res.status(404).type('html').send('<h1>404</h1>');
-
-  const bands = AGE_THEME_DATA.bands;
-
-  const renderBand = (b) => {
-    // Diff chips — surface every prop that changed between current ↔ best so
-    // even subtle deltas (e.g. body +2px with font/colors unchanged) read at a
-    // glance. Fixes architect punch-list #11 (adult band "looked identical").
-    const diffProps = Object.keys(b.best).filter(k => b.current[k] !== b.best[k]);
-    const diffChips = diffProps.map(k => {
-      const cur = b.current[k], next = b.best[k];
-      const isFontFamily = k === 'font';
-      const lhs = isFontFamily ? String(cur).split(',')[0] : cur;
-      const rhs = isFontFamily ? String(next).split(',')[0] : next;
-      return `<span class="diff-chip"><span class="diff-key">${k}</span> <span class="diff-from">${lhs}</span> → <span class="diff-to">${rhs}</span></span>`;
-    }).join('');
-    const diffBar = diffProps.length === 0
-      ? `<div class="diff-bar diff-bar-noop">no change recommended</div>`
-      : `<div class="diff-bar"><span class="diff-count">${diffProps.length} change${diffProps.length===1?'':'s'}:</span>${diffChips}</div>`;
-
-    const swatch = (theme) => `
-      <div class="theme-card" style="background:${theme.bg}; color:${theme.fg}; border:1px solid ${theme.border}; font-family:${theme.font};">
-        <div class="theme-panel" style="background:${theme.panel}; border:1px solid ${theme.border};">
-          <h3 style="font-size:${theme.h}px; line-height:1.1; margin:0 0 12px; color:${theme.fg};">${b.label} · ${b.age}</h3>
-          <p style="font-size:${theme.body}px; line-height:1.4; margin:0 0 14px; color:${theme.fg};">${b.sample}</p>
-          <button style="background:${theme.accent}; color:${theme.bg}; border:none; padding:8px 14px; font-size:${theme.body}px; font-family:inherit; cursor:default;">Tap me</button>
-        </div>
-        <div class="theme-spec">
-          <span class="hex" style="background:${theme.bg}"  title="bg ${theme.bg}"></span>
-          <span class="hex" style="background:${theme.panel}" title="panel ${theme.panel}"></span>
-          <span class="hex" style="background:${theme.fg}"  title="fg ${theme.fg}"></span>
-          <span class="hex" style="background:${theme.accent}" title="accent ${theme.accent}"></span>
-          <span class="hex" style="background:${theme.border}" title="border ${theme.border}"></span>
-          <span class="meta">${theme.body}/${theme.h}px · ${theme.font.split(',')[0]}</span>
-        </div>
-        <div class="theme-apca">
-          ${(() => {
-            const lcText = apcaLc(theme.fg, theme.panel);
-            const lcAccent = apcaLc(theme.accent, theme.panel);
-            const lcBtn = apcaLc(theme.bg, theme.accent);
-            const cls = lc => Math.abs(lc) >= 75 ? 'lc-aaa' : Math.abs(lc) >= 60 ? 'lc-aa' : Math.abs(lc) >= 45 ? 'lc-ui' : 'lc-fail';
-            return `
-              <span class="lc ${cls(lcText)}" title="fg on panel">Lc ${lcText} text</span>
-              <span class="lc ${cls(lcAccent)}" title="accent on panel">Lc ${lcAccent} accent</span>
-              <span class="lc ${cls(lcBtn)}" title="button text on accent">Lc ${lcBtn} btn</span>
-            `;
-          })()}
-        </div>
-      </div>`;
-    return `
-      <section class="band-row" id="${b.id}">
-        <header class="band-header">
-          <h2><span class="age-pill">${b.age}</span> ${b.label}</h2>
-          <p class="band-mood">${b.mood}</p>
-          <p class="band-anchor">anchor → ${b.anchor}</p>
-          ${diffBar}
-        </header>
-        <div class="band-compare">
-          <div class="theme-col">
-            <div class="theme-label theme-label-current">CURRENT</div>
-            ${swatch(b.current)}
-          </div>
-          <div class="theme-col">
-            <div class="theme-label theme-label-best">BEST</div>
-            ${swatch(b.best)}
-          </div>
-          <div class="theme-rationale">
-            <div class="rationale-swap">${b.swap.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')}</div>
-            <p>${b.reasoning}</p>
-          </div>
-        </div>
-      </section>`;
-  };
-
-  res.type('html').send(`<!doctype html>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
-<title>Best themes for every age · wallco.ai</title>
-<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600&family=Nunito:wght@400;700&family=Public+Sans:wght@400;600&family=Space+Grotesk:wght@400;600&family=JetBrains+Mono:wght@400;600&family=Inter:wght@400;500;600&family=Source+Serif+Pro:wght@400;600&family=Atkinson+Hyperlegible:wght@400;700&display=swap" rel="stylesheet">
-<style>
-  * { box-sizing: border-box; }
-  body { margin:0; background:#0f0e0c; color:#e8e2d6; font:13px/1.4 -apple-system, system-ui, sans-serif; }
-  header.top { padding:22px 36px; border-bottom:1px solid #2a2825; background:#1a1816; display:flex; gap:18px; align-items:baseline; flex-wrap:wrap; position:sticky; top:0; z-index:10; }
-  header.top h1 { margin:0; font-weight:300; font-size:24px; letter-spacing:.5px; }
-  header.top .tag { font-size:11px; color:#aaa; text-transform:uppercase; letter-spacing:.1em; }
-  header.top nav { margin-left:auto; }
-  header.top nav a { color:#bba; text-decoration:none; margin-left:18px; font-size:12px; }
-  header.top nav a.on { color:#d2b15c; border-bottom:1px solid #d2b15c; }
-  header.top .credits { width:100%; margin-top:8px; font-size:11px; color:#888; line-height:1.5; }
-  header.top .credits .src { color:#d2b15c; }
-  main { padding: 30px 36px 60px; max-width:1480px; margin:0 auto; }
-  .toc { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:24px; padding:14px 18px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; }
-  .toc a { padding:4px 10px; background:#2a2825; color:#bba; text-decoration:none; border-radius:12px; font-size:11px; transition:all .12s; }
-  .toc a:hover { background:#d2b15c; color:#0f0e0c; }
-  .band-row { margin-bottom:36px; padding-bottom:26px; border-bottom:1px solid #1f1c18; scroll-margin-top:90px; }
-  .band-header { margin-bottom:16px; }
-  .band-header h2 { margin:0 0 6px; font-weight:400; font-size:18px; display:flex; align-items:center; gap:12px; }
-  .age-pill { display:inline-block; padding:3px 10px; background:#d2b15c; color:#0f0e0c; border-radius:14px; font-size:11px; font-weight:600; letter-spacing:.04em; }
-  .band-mood { margin:0 0 4px; color:#bba; font-size:12px; font-style:italic; }
-  .band-anchor { margin:0; color:#888; font-size:11px; }
-  .diff-bar { margin-top:8px; padding:8px 12px; background:rgba(210,177,92,.06); border-left:3px solid #d2b15c; border-radius:2px; display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
-  .diff-bar-noop { background:rgba(255,255,255,.03); border-left-color:#3a3631; color:#666; font-size:11px; font-style:italic; }
-  .diff-count { font-size:10px; color:#d2b15c; letter-spacing:.06em; text-transform:uppercase; margin-right:4px; }
-  .diff-chip { font-size:11px; padding:2px 8px; background:#1a1816; border:1px solid #2a2825; border-radius:11px; color:#bba; font-family:ui-monospace, Menlo, monospace; }
-  .diff-chip .diff-key { color:#888; }
-  .diff-chip .diff-from { color:#d2554a; }
-  .diff-chip .diff-to { color:#5fbf6e; font-weight:600; }
-  .band-compare { display:grid; grid-template-columns: 1fr 1fr 1.1fr; gap:14px; align-items:start; }
-  .theme-col { display:flex; flex-direction:column; gap:6px; }
-  .theme-label { font-size:10px; letter-spacing:.1em; color:#888; text-transform:uppercase; padding-left:4px; }
-  .theme-label-best { color:#d2b15c; }
-  .theme-card { padding:0; border-radius:3px; overflow:hidden; min-height:200px; transition:transform .15s; }
-  .theme-card:hover { transform: scale(1.01); }
-  .theme-panel { padding:18px 20px; min-height:140px; }
-  .theme-spec { padding:6px 10px; background:rgba(0,0,0,.4); display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
-  .theme-spec .hex { width:14px; height:14px; border-radius:2px; border:1px solid rgba(255,255,255,.2); display:inline-block; cursor:help; }
-  .theme-spec .meta { font-size:9px; color:#cccccc; margin-left:auto; font-family:ui-monospace, Menlo, monospace; }
-  .theme-apca { padding:5px 10px; background:rgba(0,0,0,.6); display:flex; gap:6px; flex-wrap:wrap; }
-  .theme-apca .lc { font-family:ui-monospace, Menlo, monospace; font-size:9px; padding:1px 6px; border-radius:2px; }
-  .theme-apca .lc.lc-aaa  { background:#1f3a1f; color:#5fbf6e; }
-  .theme-apca .lc.lc-aa   { background:#1f2f3a; color:#5fa8bf; }
-  .theme-apca .lc.lc-ui   { background:#3a2e1f; color:#d2b15c; }
-  .theme-apca .lc.lc-fail { background:#3a1f1f; color:#d2554a; }
-  .theme-rationale { padding:16px 18px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; min-height:200px; }
-  .rationale-swap { color:#d2b15c; font-size:13px; font-weight:500; margin-bottom:10px; line-height:1.4; }
-  .rationale-swap strong { font-weight:700; }
-  .theme-rationale p { margin:0; color:#bba; font-size:12px; line-height:1.5; }
-  @media (max-width: 900px) { .band-compare { grid-template-columns: 1fr; } }
-  @media (max-width: 640px) {
-    header.top { padding:14px 18px; gap:10px; }
-    header.top h1 { font-size:18px; }
-    header.top .tag { font-size:10px; }
-    header.top nav { width:100%; margin-left:0; display:flex; gap:14px; }
-    header.top nav a { margin-left:0; }
-    header.top .credits { font-size:10.5px; line-height:1.45; }
-    header.top .credits code { font-size:9.5px; }
-    main { padding: 18px 16px 40px; }
-    .toc { padding:10px 12px; gap:5px; }
-    .toc a { font-size:10px; padding:3px 8px; }
-    .band-row { margin-bottom:24px; padding-bottom:18px; scroll-margin-top:78px; }
-    .band-header h2 { font-size:16px; gap:9px; }
-    .age-pill { font-size:10px; padding:2px 8px; }
-    .band-mood { font-size:11px; }
-    .band-anchor { font-size:10px; }
-    .diff-bar { padding:6px 10px; gap:5px; flex-wrap:wrap; }
-    .diff-chip { font-size:10px; padding:2px 6px; }
-    .diff-count { font-size:9px; width:100%; margin:0 0 4px; }
-    .theme-card { min-height:170px; }
-    .theme-panel { padding:14px 16px; min-height:120px; }
-    .theme-spec { padding:5px 8px; gap:4px; }
-    .theme-spec .hex { width:12px; height:12px; }
-    .theme-spec .meta { font-size:8.5px; width:100%; margin-left:0; text-align:right; }
-    .theme-apca { padding:4px 8px; gap:4px; }
-    .theme-apca .lc { font-size:8.5px; padding:1px 5px; }
-    .theme-rationale { padding:12px 14px; min-height:0; }
-    .rationale-swap { font-size:12px; }
-    .theme-rationale p { font-size:11px; }
-    .theme-label { font-size:9px; }
-  }
-</style>
-<header class="top">
-  <h1>Best themes for every age</h1>
-  <span class="tag">8 bands · 3 → 95</span>
-  <nav>
-    <a href="/designs?review=1">/designs</a>
-    <a href="/moodboard">/moodboard</a>
-    <a href="/age-themes" class="on">/age-themes</a>
-  </nav>
-  <div class="credits">
-    <strong style="color:#d2b15c">v${AGE_THEME_DATA.version}</strong> · validated ${AGE_THEME_DATA.validated_at} ·
-    <strong style="color:#d2554a">⚠ AI-generated starting point — not user-tested, not accessibility-tooled.</strong>
-    Recommendations from the <span class="src">ui-ux-designer</span> subagent (font/hex/size swaps with research citations) and reviewed by the <span class="src">debate-team-fast</span> 3-LLM panel (Claude, Kimi K2.5, Qwen3:14b — Codex quota-blocked). Companion <span class="src">Figma file</span> at <a href="https://www.figma.com/design/WmwSirHywv530eACPtbqyA" target="_blank" style="color:#d2b15c" rel="noopener noreferrer nofollow">figma.com/design/WmwSirHywv530eACPtbqyA</a>. Canonical band data: <code style="color:#d2b15c">~/Projects/wallco-ai/src/bands.json</code> (skill mirror: <code style="color:#666">~/.claude/skills/age-adaptive-theme/bands.json</code>) · APCA module: <code style="color:#d2b15c">~/Projects/wallco-ai/src/apca.js</code>.
-    <br><span style="color:#888">Debate-team consensus: Atkinson Hyperlegible / tween indigo / Nunito picks are defensible. The fg #222→#1a1a1a (toddler) and -apple-system→Inter (adult) swaps were LLM "hallucinated precision" — dropped. APCA Lc values shown are calculated by src/apca.js (Myndex 0.1.9), NOT lab-verified. v9 fixes from validation-loop: teen body 16px, line-height 1.5 baseline, btn-min-height 44/48/56px per band.</span>
-  </div>
-</header>
-<main>
-  <nav class="toc">
-    ${bands.map(b => `<a href="#${b.id}">${b.age} · ${b.label}</a>`).join('')}
-  </nav>
-  ${bands.map(renderBand).join('')}
-</main>
-</body></html>`);
-});
-
-// ── MOODBOARD (admin only — collects every pinned pairing across all reviewed designs)
-app.get('/moodboard', (req, res) => {
-  const _isAdmin = isAdmin(req);
-  if (!_isAdmin) return res.status(404).type('html').send('<h1>404</h1><p>Not found.</p>');
-  let moodboards = {};
-  let reviews = {};
-  try { moodboards = JSON.parse(require('fs').readFileSync(require('path').join(__dirname,'data','moodboards.json'),'utf8')); } catch {}
-  try { reviews   = JSON.parse(require('fs').readFileSync(require('path').join(__dirname,'data','reviews.json'),'utf8')); } catch {}
-
-  const filter = (req.query.type || '').toLowerCase();
-  const entries = Object.entries(moodboards).filter(([_,arr]) => Array.isArray(arr) && arr.length > 0);
-  const total = entries.reduce((n, [_, arr]) => n + arr.length, 0);
-
-  const escHTML = s => String(s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[c]));
-
-  // group blocks per design
-  const blocks = entries.map(([designId, pins]) => {
-    const design = DESIGNS.find(d => String(d.id) === String(designId)) || { id: designId, title: '#'+designId };
-    const review = reviews[designId] || {};
-    const filteredPins = filter ? pins.filter(p => p.type === filter) : pins;
-    if (filter && filteredPins.length === 0) return '';
-    const pinCards = filteredPins.map(p => {
-      let thumb;
-      if (p.type === 'design' && p.thumb_url) thumb = `<div class="mb-thumb" style="background-image:url('${p.thumb_url}')"></div>`;
-      else if (p.type === 'color' && p.hex)    thumb = `<div class="mb-thumb" style="background:${p.hex}"></div>`;
-      else                                      thumb = `<div class="mb-thumb mb-thumb-mat">${p.icon || '✦'}</div>`;
-      return `
-        <div class="mb-card" data-design="${designId}" data-pin-key="${escHTML(p.pin_key)}">
-          ${thumb}
-          <div class="mb-body">
-            <span class="mb-type">${p.type}${p.type==='design'&&p.design_id?' · #'+p.design_id:''}</span>
-            <div class="mb-title">${escHTML(p.label || p.hex || '(unnamed)')}</div>
-            <div class="mb-reason">${escHTML(p.reason || '')}</div>
-          </div>
-          <button class="mb-unpin" type="button" title="Remove from moodboard">★</button>
-        </div>`;
-    }).join('');
-    return `
-      <section class="mb-design-block">
-        <header class="mb-design-header">
-          <a class="mb-design-thumb" href="/design/${design.id}" target="_blank" style="background-image:url('${design.image_url || ''}')" rel="noopener noreferrer"></a>
-          <div class="mb-design-info">
-            <h2 class="mb-design-title">${escHTML(design.title || design.prompt || '#'+design.id)}</h2>
-            <p class="mb-design-meta">${escHTML(design.category||'')} · <span class="mb-design-dot" style="background:${design.dominant_hex||'#888'}"></span> ${escHTML(design.dominant_hex||'')}${review.decision?' · <strong style="color:'+(review.decision==='keep'?'#5fbf6e':'#d2554a')+'">'+review.decision.toUpperCase()+'</strong>':''}</p>
-            ${review.why?`<p class="mb-design-why">"${escHTML(review.why)}"</p>`:''}
-          </div>
-          <div class="mb-design-count">${filteredPins.length} pinned</div>
-        </header>
-        <div class="mb-pins-grid">${pinCards}</div>
-      </section>`;
-  }).filter(Boolean).join('');
-
-  res.type('html').send(`<!doctype html>
-<meta charset="utf-8">
-<title>moodboard · wallco.ai</title>
-<style>
-  * { box-sizing: border-box; }
-  body { margin:0; background:#0f0e0c; color:#e8e2d6; font:13px/1.4 -apple-system, system-ui, sans-serif; }
-  header.top { padding:18px 32px; border-bottom:1px solid #2a2825; background:#1a1816; display:flex; gap:18px; align-items:baseline; flex-wrap:wrap; }
-  header.top h1 { margin:0; font-weight:300; font-size:22px; letter-spacing:.5px; }
-  header.top .tag { font-size:11px; color:#aaa; }
-  header.top nav a { color:#bba; text-decoration:none; margin-right:14px; font-size:12px; }
-  header.top nav a.on { color:#d2b15c; border-bottom:1px solid #d2b15c; }
-  header.top .filters { margin-left:auto; display:flex; gap:8px; }
-  header.top .filter-pill { background:#2a2825; padding:3px 10px; border-radius:12px; font-size:11px; cursor:pointer; color:#bba; border:1px solid transparent; text-decoration:none; }
-  header.top .filter-pill.on { background:#d2b15c; color:#0f0e0c; }
-  #mb-search { background:#16140f; border:1px solid #3a3631; border-radius:14px; padding:3px 12px; font:11px/1 -apple-system, system-ui, sans-serif; color:#e8e2d6; min-width:200px; outline:none; transition:border-color .12s; }
-  #mb-search:focus { border-color:#d2b15c; }
-  #mb-search::placeholder { color:#666; }
-  .mb-card.mb-hidden, .mb-design-block.mb-hidden { display:none !important; }
-  .mb-search-empty { padding:32px 24px; text-align:center; color:#666; font-style:italic; font-size:13px; }
-  header.top .export-link { font-size:11px; color:#d2b15c; text-decoration:none; padding:3px 10px; border:1px solid #3a3631; border-radius:2px; }
-  header.top .export-link:hover { background:#d2b15c; color:#0f0e0c; }
-  main { padding: 24px 32px; max-width:1400px; margin:0 auto; }
-  .mb-design-block { margin-bottom:36px; padding-bottom:28px; border-bottom:1px solid #1f1c18; }
-  .mb-design-header { display:flex; gap:18px; align-items:flex-start; margin-bottom:14px; }
-  .mb-design-thumb { flex:0 0 96px; width:96px; height:96px; background:#0d0c0a no-repeat center/cover; border-radius:3px; }
-  .mb-design-info { flex:1; min-width:0; }
-  .mb-design-title { margin:0 0 4px; font-size:15px; font-weight:400; color:#e8e2d6; }
-  .mb-design-meta { margin:0 0 6px; font-size:11px; color:#888; }
-  .mb-design-dot { display:inline-block; width:9px; height:9px; border-radius:50%; vertical-align:middle; border:1px solid #00000060; }
-  .mb-design-why { margin:0; font-size:11px; color:#d2b15c; font-style:italic; line-height:1.4; }
-  .mb-design-count { font-size:11px; color:#bba; padding:3px 10px; background:#2a2825; border-radius:12px; align-self:center; white-space:nowrap; }
-  .mb-pins-grid { display:grid; gap:10px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
-  .mb-card { display:flex; gap:10px; align-items:flex-start; padding:9px 11px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; transition:border-color .15s; }
-  .mb-card:hover { border-color:#3a3631; }
-  .mb-thumb { flex:0 0 52px; width:52px; height:52px; border-radius:3px; background:#0d0c0a no-repeat center/cover; display:flex; align-items:center; justify-content:center; }
-  .mb-thumb.mb-thumb-mat { background:#2a2825; color:#d2b15c; font-size:18px; }
-  .mb-body { flex:1; min-width:0; }
-  .mb-type { display:inline-block; font-size:9px; text-transform:uppercase; letter-spacing:.06em; padding:1px 5px; background:#2a2825; color:#d2b15c; border-radius:2px; margin-bottom:3px; }
-  .mb-title { font-size:11px; color:#e8e2d6; line-height:1.3; margin-bottom:2px; }
-  .mb-reason { font-size:10px; color:#888; line-height:1.4; }
-  .mb-unpin { background:#d2b15c33; border:1px solid #d2b15c; color:#d2b15c; font-size:11px; cursor:pointer; padding:2px 7px; border-radius:10px; align-self:flex-start; transition:all .12s; }
-  .mb-unpin:hover { background:#d2b15c; color:#0f0e0c; }
-  .empty { padding:60px 0; text-align:center; color:#666; }
-  .empty p { font-size:13px; margin:6px 0; }
-  .empty a { color:#d2b15c; text-decoration:none; }
-</style>
-<header class="top">
-  <h1>moodboard</h1>
-  <span class="tag">${total} pin${total===1?'':'s'} across ${entries.length} design${entries.length===1?'':'s'}</span>
-  <nav>
-    <a href="/designs?review=1">Review queue</a>
-    <a href="/moodboard${filter?'?type='+filter:''}" class="on">Moodboard</a>
-  </nav>
-  <div class="filters">
-    <input type="search" id="mb-search" placeholder="search labels + reasons…" autocomplete="off" />
-    <a class="filter-pill ${!filter?'on':''}" href="/moodboard">All</a>
-    <a class="filter-pill ${filter==='design'?'on':''}" href="/moodboard?type=design">Designs</a>
-    <a class="filter-pill ${filter==='color'?'on':''}" href="/moodboard?type=color">Colors</a>
-    <a class="filter-pill ${filter==='material'?'on':''}" href="/moodboard?type=material">Materials</a>
-    <a class="export-link" href="/api/export/keep.csv" download>Kept CSV</a>
-    <a class="export-link" href="/api/export/spoonflower-batch.csv" download title="Spoonflower-batch CSV — feeds upload_spoonflower_new.py --batch">Spoonflower CSV</a>
-  </div>
-</header>
-<main>
-${blocks || `<div class="empty"><p>No pins yet.</p><p>Head to <a href="/designs?review=1">/designs</a>, click <strong>Pair</strong> on any card, then ★-pin the suggestions you like.</p></div>`}
-</main>
-<script>
-document.querySelectorAll('.mb-unpin').forEach(btn => {
-  btn.addEventListener('click', async () => {
-    const card = btn.closest('.mb-card');
-    const designId = card.dataset.design;
-    const pinKey = card.dataset.pinKey;
-    btn.disabled = true;
-    try {
-      const r = await fetch('/api/moodboard/' + designId + '/pin', {
-        method: 'POST', headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({ pin_key: pinKey, suggestion: {} })
-      });
-      if (r.ok) {
-        card.style.transition = 'opacity .2s';
-        card.style.opacity = '0';
-        setTimeout(() => { card.remove();
-          // if section empty, remove it
-          document.querySelectorAll('.mb-design-block').forEach(b => {
-            if (!b.querySelector('.mb-card')) b.remove();
-          });
-          // update count in header
-          const remaining = document.querySelectorAll('.mb-card').length;
-          document.querySelector('.tag').textContent = remaining + ' pin' + (remaining===1?'':'s') + ' across ' + document.querySelectorAll('.mb-design-block').length + ' design' + (document.querySelectorAll('.mb-design-block').length===1?'':'s');
-        }, 200);
-      } else btn.disabled = false;
-    } catch (e) { btn.disabled = false; }
-  });
-});
-
-// R6 — text search across pin labels + reasons. Persists across reloads via
-// localStorage. Pure client-side filter on rendered DOM (no API change needed).
-(function(){
-  var input = document.getElementById('mb-search');
-  if (!input) return;
-  var LS_KEY = 'wallco-mb-search';
-  try { input.value = localStorage.getItem(LS_KEY) || ''; } catch(e) {}
-
-  function applyFilter() {
-    var q = (input.value || '').trim().toLowerCase();
-    try { localStorage.setItem(LS_KEY, q); } catch(e) {}
-    var anyShown = false;
-    document.querySelectorAll('.mb-design-block').forEach(function(block){
-      var blockShown = false;
-      block.querySelectorAll('.mb-card').forEach(function(card){
-        if (!q) { card.classList.remove('mb-hidden'); blockShown = true; return; }
-        var label = (card.querySelector('.mb-title') || {}).textContent || '';
-        var reason = (card.querySelector('.mb-reason') || {}).textContent || '';
-        var match = (label + ' ' + reason).toLowerCase().indexOf(q) !== -1;
-        card.classList.toggle('mb-hidden', !match);
-        if (match) blockShown = true;
-      });
-      // also match against design title in the block header
-      if (q && !blockShown) {
-        var dt = (block.querySelector('.mb-design-title') || {}).textContent || '';
-        if (dt.toLowerCase().indexOf(q) !== -1) {
-          block.querySelectorAll('.mb-card').forEach(function(c){ c.classList.remove('mb-hidden'); });
-          blockShown = true;
-        }
-      }
-      block.classList.toggle('mb-hidden', !blockShown);
-      if (blockShown) anyShown = true;
-    });
-    // empty-state hint
-    var main = document.querySelector('main');
-    var empty = document.getElementById('mb-search-empty');
-    if (q && !anyShown) {
-      if (!empty) {
-        empty = document.createElement('div');
-        empty.id = 'mb-search-empty';
-        empty.className = 'mb-search-empty';
-        main.appendChild(empty);
-      }
-      empty.textContent = 'No pins match "' + q + '"';
-    } else if (empty) {
-      empty.remove();
-    }
-  }
-  input.addEventListener('input', applyFilter);
-  // apply persisted search on load
-  if (input.value) applyFilter();
-})();
-</script>
-</body></html>`);
-});
-
-// ── SAMPLES (lead capture — no commerce in v1)
-app.get('/samples', (req, res) => {
-  const designId = req.query.design || '';
-  const design   = designId ? DESIGNS.find(d => d.id === parseInt(designId,10)) : null;
-  // Roll width chosen on the design page (query param). Constrained to {24,36,52}.
-  const reqW = parseInt(req.query.width, 10);
-  const selectedWidth = [24, 36, 52].includes(reqW) ? reqW : (design && [24,36,52].includes(Math.round(design.width_in||0)) ? Math.round(design.width_in) : 24);
-
-  res.type('html').send(`${htmlHead({
-    title: 'Request a Sample — wallco.ai',
-    description: 'Request a free physical or digital sample of any wallco.ai original design.',
-    canonical: 'https://wallco.ai/samples'
-  })}
-<body>
-${htmlHeader('/samples')}
-<main class="prose-main">
-  <div class="sample-form-wrap">
-    <h1>Request a Free Sample</h1>
-    <p>We'll send you a physical swatch or a high-res digital proof. No charge, no obligation.</p>
-
-    ${design ? `<div class="sample-preview">
-      <img src="${design.image_url}" alt="${design.title}" style="width:120px;height:120px;object-fit:cover;border-radius:4px;">
-      <div><strong>${design.title}</strong><br>Pattern No.${String(design.id).padStart(4,'0')}</div>
-    </div>` : ''}
-
-    <form method="post" action="/api/leads" class="sample-form" id="sample-form">
-      <input type="hidden" name="source" value="samples-page">
-      ${design ? `<input type="hidden" name="design_id" value="${design.id}">` : ''}
-
-      <label>Roll width
-        <div id="sample-width-tabs" style="display:flex;gap:8px;margin-top:6px">
-          ${[24,36,52].map(w => `<button type="button" class="sw-tab${w===selectedWidth?' is-on':''}" data-w="${w}" style="flex:1;padding:9px 12px;border:1px solid ${w===selectedWidth?'var(--accent,#222)':'var(--line,#ddd)'};background:${w===selectedWidth?'var(--accent,#222)':'transparent'};color:${w===selectedWidth?'var(--bg,#fff)':'var(--ink,#222)'};font:13px var(--sans,system-ui);font-weight:500;border-radius:6px;cursor:pointer">${w}"</button>`).join('')}
-        </div>
-        <input type="hidden" name="width" id="sample-width-input" value="${selectedWidth}">
-      </label>
-
-      <label>Design
-        <select name="design_id" ${design ? 'disabled' : ''}>
-          ${design
-            ? `<option value="${design.id}" selected>${design.title}</option>`
-            : DESIGNS.map(d => `<option value="${d.id}" ${d.id===parseInt(designId,10)?'selected':''}>${d.title} (No.${d.id})</option>`).join('')
-          }
-        </select>
-      </label>
-      <label>Your name <input type="text" name="name" required autocomplete="name" enterkeyhint="next" placeholder="Jane Smith"></label>
-      <label>Email <input type="email" name="email" required autocomplete="email" inputmode="email" enterkeyhint="next" placeholder="you@example.com"></label>
-      <label>Phone (optional) <input type="tel" name="phone" autocomplete="tel" inputmode="tel" enterkeyhint="next" placeholder="+1 (555) 000-0000"></label>
-      <label>Message (optional) <textarea name="message" rows="3" placeholder="Any notes about your project…"></textarea></label>
-      <button type="submit" class="btn-primary">Send Sample Request</button>
-    </form>
-    <div id="form-result" style="display:none;margin-top:16px;padding:16px;border-radius:4px;"></div>
-  </div>
-</main>
-${FOOTER}
-
-<script>
-// Roll-width tab wiring (3-way toggle 24/36/52)
-(function(){
-  var tabs = document.querySelectorAll('#sample-width-tabs .sw-tab');
-  var hidden = document.getElementById('sample-width-input');
-  tabs.forEach(function(t){
-    t.addEventListener('click', function(){
-      tabs.forEach(function(x){
-        var on = x === t;
-        x.classList.toggle('is-on', on);
-        x.style.background = on ? 'var(--accent,#222)' : 'transparent';
-        x.style.color      = on ? 'var(--bg,#fff)' : 'var(--ink,#222)';
-        x.style.borderColor= on ? 'var(--accent,#222)' : 'var(--line,#ddd)';
-      });
-      hidden.value = t.dataset.w;
-    });
-  });
-})();
-document.getElementById('sample-form').addEventListener('submit', async function(e) {
-  e.preventDefault();
-  var form = e.target;
-  var result = document.getElementById('form-result');
-  var data = Object.fromEntries(new FormData(form));
-  try {
-    var r = await fetch('/api/leads', {
-      method: 'POST',
-      headers: {'Content-Type':'application/json'},
-      body: JSON.stringify(data)
-    });
-    var json = await r.json();
-    result.style.display = 'block';
-    if (json.ok) {
-      result.style.background = '#d4edda';
-      result.style.color = '#155724';
-      result.textContent = 'Thank you! We\'ll be in touch shortly.';
-      form.reset();
-    } else {
-      result.style.background = '#f8d7da';
-      result.style.color = '#721c24';
-      result.textContent = 'Something went wrong — please email info@wallco.ai directly.';
-    }
-  } catch(err) {
-    result.style.display = 'block';
-    result.textContent = 'Network error — please try again.';
-  }
-});
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── API: Lead capture
-app.post('/api/leads', async (req, res) => {
-  const { name, email, phone, message, design_id, source } = req.body;
-  if (!email) return res.status(400).json({ ok: false, error: 'email required' });
-
-  // Write to dw_unified.wallco_leads (create table if not exists)
-  try {
-    const { execSync } = require('child_process');
-    const sql = `
-      CREATE TABLE IF NOT EXISTS wallco_leads (
-        id BIGSERIAL PRIMARY KEY,
-        name TEXT,
-        email TEXT NOT NULL,
-        phone TEXT,
-        message TEXT,
-        design_id BIGINT,
-        source TEXT,
-        created_at TIMESTAMPTZ DEFAULT now()
-      );
-      INSERT INTO wallco_leads (name, email, phone, message, design_id, source)
-      VALUES (
-        ${name    ? `'${name.replace(/'/g,"''")}'`       : 'NULL'},
-        '${email.replace(/'/g,"''")}',
-        ${phone   ? `'${phone.replace(/'/g,"''")}'`      : 'NULL'},
-        ${message ? `'${message.replace(/'/g,"''")}'`    : 'NULL'},
-        ${design_id ? parseInt(design_id,10) : 'NULL'},
-        ${source  ? `'${source.replace(/'/g,"''")}'`     : 'NULL'}
-      );
-    `;
-    execSync(`${PSQL_CMD} -c "${sql.replace(/\n/g,' ')}"`, { encoding: 'utf8' });
-  } catch (e) {
-    console.error('Lead insert error:', e.message);
-    // Still return ok — don't block the user on a DB error
-  }
-  res.json({ ok: true });
-});
-
-// ── API: designs JSON (for future use / inline editor)
-// File-existence cache — built once at module load, refreshed every 5min.
-// Filters out designs whose PNG isn't on disk so the grid never shows blank
-// tiles. Steve's standing rule (2026-05-13): "hot or not — anything cannot
-// show unless there is a photo or pic". Same rule applies to /designs.
-let _imgExistsCache = null;
-let _imgExistsRefreshedAt = 0;
-function imageFilesOnDisk() {
-  const now = Date.now();
-  if (_imgExistsCache && (now - _imgExistsRefreshedAt) < 5 * 60 * 1000) return _imgExistsCache;
-  try {
-    const files = fs.readdirSync(IMG_DIR);
-    _imgExistsCache = new Set(files);
-    _imgExistsRefreshedAt = now;
-    console.log(`[api/designs] file-cache refreshed: ${files.length} files on disk`);
-  } catch (e) {
-    console.error('[api/designs] readdir fail:', e.message);
-    if (!_imgExistsCache) _imgExistsCache = new Set();
-  }
-  return _imgExistsCache;
-}
-function designHasImage(d) {
-  if (!d) return false;
-  if (BLANK_IDS.has(d.id)) return false;   // failed-generation blank — never show
-  const fileSet = imageFilesOnDisk();
-  if (d.filename) return fileSet.has(d.filename);
-  // /designs/img/by-id/:id pattern — used by night-builder + generate_designs.js
-  // for every new design. Resolve by checking local_path's basename against the
-  // file cache so these rows aren't hidden by the file-existence filter.
-  if (d.image_url && /^\/designs\/img\/by-id\/\d+/.test(d.image_url)) {
-    if (d.local_path) {
-      const fname = String(d.local_path).split('/').pop();
-      if (fname && fileSet.has(fname)) return true;
-    }
-    return false;
-  }
-  if (d.image_url && d.image_url.startsWith('/designs/img/')) {
-    return fileSet.has(d.image_url.replace('/designs/img/', ''));
-  }
-  return false;
-}
-
-app.get('/api/designs', (req, res) => {
-  const sort = req.query.sort || 'newest';
-  const cat  = req.query.cat  || '';
-  const q    = (req.query.q   || '').toLowerCase();
-  const page = Math.max(1, parseInt(req.query.page || '1', 10));
-  const PER  = parseInt(req.query.limit || '60', 10);
-
-  // Hard filter — never serve a row whose image file isn't on disk.
-  let filtered = DESIGNS.filter(designHasImage);
-  if (cat)  filtered = filtered.filter(d => d.category === cat);
-  if (q)    filtered = filtered.filter(d =>
-    d.title.toLowerCase().includes(q) || d.category.includes(q)
-  );
-  filtered = sortDesigns(filtered, sort);
-
-  const total = filtered.length;
-  const pages = Math.ceil(total / PER);
-  const items = filtered.slice((page-1)*PER, page*PER);
-  res.json({ ok: true, total, page, pages, items });
-});
-
-// ── API: inline editor state (admin gate)
-app.get('/api/_inline/state', (req, res) => {
-  if (!isAdmin(req)) return res.status(401).json({ ok: false, error: 'unauthorized' });
-  res.json({ ok: true, site: SITE, designCount: DESIGNS.length });
-});
-
-// ─────────────────────────────────────────────────────────────────────────
-// SPOONFLOWER + ROOM PREVIEW + VOICE
-// ─────────────────────────────────────────────────────────────────────────
-
-function findDesign(id) {
-  return DESIGNS.find(d => d.id === parseInt(id, 10));
-}
-
-function localPathForDesign(d) {
-  // Designs have image_url like /designs/img/<file>.png — map back to data/generated/
-  if (!d) return null;
-  if (d.local_path) return d.local_path;
-  if (d.filename) return path.join(IMG_DIR, d.filename);
-  if (d.image_url && d.image_url.startsWith('/designs/img/')) {
-    return path.join(IMG_DIR, d.image_url.replace('/designs/img/', ''));
-  }
-  return null;
-}
-
-// ── POST /api/room — generate room visualization for a design
-app.post('/api/room', async (req, res) => {
-  const { design_id, roomType = 'bedroom', angle = 'straight_on',
-          cameraDistance = 5, patternWidth = 27, patternHeight = 27 } = req.body || {};
-  // FRESH-PATH FIX (2026-05-12): the in-memory DESIGNS snapshot can be stale
-  // for designs that were composed/baked after server start — leading to the
-  // upstream renderer receiving the OLD pattern file. Always read local_path
-  // live from spoon_all_designs to guarantee we send the CURRENT pattern.
-  let file = null;
-  try {
-    const lp = psqlQuery(`SELECT local_path FROM spoon_all_designs WHERE id=${pgEsc(parseInt(design_id, 10))} LIMIT 1;`);
-    if (lp && lp.trim()) file = lp.trim();
-  } catch (e) { /* fall through to in-memory */ }
-  if (!file) {
-    const d = findDesign(design_id);
-    if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
-    file = localPathForDesign(d);
-  }
-  if (!file || !fs.existsSync(file)) {
-    return res.status(404).json({ ok: false, error: 'design file missing on disk', file });
-  }
-  try {
-    const b64 = fs.readFileSync(file).toString('base64');
-    // Upstream renderer (45.61.58.125:3075) drops ~5-15% of requests with
-    // {ok:false,error:'fetch failed'} or transient TLS hiccups. Retry once
-    // with 800ms backoff before giving up — caught in the 27-render matrix
-    // 2026-05-12 where 5/27 cells failed and would have succeeded on retry.
-    let j = null, lastErr = null;
-    for (let attempt = 1; attempt <= 2; attempt++) {
-      try {
-        const upstream = await fetch(ROOM_API, {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify({
-            patternBase64: b64, roomType, angle,
-            cameraDistance, patternWidth, patternHeight
-          })
-        });
-        const candidate = await upstream.json();
-        if (candidate && candidate.success !== false && candidate.image) { j = candidate; break; }
-        lastErr = (candidate && candidate.error) || 'upstream returned no image';
-      } catch (e) {
-        lastErr = e.message;
-      }
-      if (attempt < 2) await new Promise(r => setTimeout(r, 800));
-    }
-    if (!j) {
-      return res.status(502).json({ ok: false, error: lastErr || 'upstream error', retried: true });
-    }
-    // upstream returns { success, image, ... } — `image` is base64 or data-URL
-    const imgB64 = j.image.replace(/^data:image\/\w+;base64,/, '');
-    const fname = `room_${design_id}_${roomType}_${Date.now()}.png`;
-    const outPath = path.join(ROOMS_DIR, fname);
-    const buf = Buffer.from(imgB64, 'base64');
-    fs.writeFileSync(outPath, buf);
-    const image_url = `/uploads/rooms/${fname}`;
-
-    // Persist to wallco_rooms for gallery + marketing reuse.
-    let room_id = null;
-    try {
-      const row = psqlQuery(`INSERT INTO wallco_rooms
-        (design_id, room_type, angle, camera_distance, pattern_width, pattern_height,
-         filename, image_path, file_size_bytes, c2pa_signed)
-        VALUES (${pgEsc(parseInt(design_id, 10))}, ${pgEsc(roomType)}, ${pgEsc(angle)},
-                ${pgEsc(Number(cameraDistance))}, ${pgEsc(Number(patternWidth))}, ${pgEsc(Number(patternHeight))},
-                ${pgEsc(fname)}, ${pgEsc(image_url)}, ${pgEsc(buf.length)}, TRUE)
-        RETURNING id;`);
-      room_id = parseInt(row, 10) || null;
-    } catch (e) {
-      console.error('[wallco_rooms insert]', e.message);
-    }
-
-    // Bridge to catalog hover-preview: every admin-generated room is also
-    // mirrored to data/rooms/design_<id>_<room>.png + merged into the
-    // spoon_all_designs.room_mockups JSONB column. This is what /designs
-    // reads to render the "In a room" filter chip + the .card-room overlay.
-    // Failures here don't break the gallery (try/catch isolated).
-    try {
-      const canonicalDir = path.join(__dirname, 'data', 'rooms');
-      if (!fs.existsSync(canonicalDir)) fs.mkdirSync(canonicalDir, { recursive: true });
-      const canonicalPath = path.join(canonicalDir, `design_${parseInt(design_id, 10)}_${roomType}.png`);
-      fs.writeFileSync(canonicalPath, buf);
-
-      const merged = psqlQuery(`UPDATE spoon_all_designs
-        SET room_mockups = COALESCE(room_mockups, '{}'::jsonb) || jsonb_build_object(${pgEsc(roomType)}, ${pgEsc(canonicalPath)})
-        WHERE id = ${pgEsc(parseInt(design_id, 10))}
-        RETURNING COALESCE(room_mockups, '{}'::jsonb)::text;`);
-      // Reflect in the in-memory DESIGNS catalog so /designs surfaces the new room
-      // without a server restart.
-      if (merged) {
-        try {
-          const obj = JSON.parse(merged);
-          const target = DESIGNS.find(x => x.id === parseInt(design_id, 10));
-          if (target) target.room_mockups = Object.keys(obj).sort();
-        } catch (_) { /* malformed JSONB — leave DESIGNS untouched */ }
-      }
-    } catch (e) {
-      console.error('[room_mockups bridge]', e.message);
-    }
-
-    res.json({ ok: true, room_id, image_url, roomType, design_id });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── GET /api/rooms?design_id=N&include_removed=0&marketing_only=0
-// Returns generated rooms for a design (or all, if no design_id) with metadata.
-app.get('/api/rooms', (req, res) => {
-  const designId = req.query.design_id ? parseInt(req.query.design_id, 10) : null;
-  const includeRemoved = req.query.include_removed === '1' && isRoomsAdmin(req);
-  const marketingOnly = req.query.marketing_only === '1';
-  try {
-    const where = [];
-    if (Number.isInteger(designId)) where.push(`design_id = ${designId}`);
-    if (!includeRemoved) where.push('removed_at IS NULL');
-    if (marketingOnly) where.push('selected_for_marketing = TRUE');
-    const whereSQL = where.length ? `WHERE ${where.join(' AND ')}` : '';
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC), '[]'::json)
-      FROM (SELECT id, design_id, room_type, angle, image_path, filename, file_size_bytes,
-                   selected_for_marketing, removed_at, removed_reason, notes, created_at
-              FROM wallco_rooms ${whereSQL}) t;`);
-    res.json({ ok: true, rooms: JSON.parse(raw || '[]'), admin: isRoomsAdmin(req) });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── POST /api/rooms/:id/remove  (admin only)
-// Soft-deletes; the image file stays on disk so prior page links continue to work for caching.
-// Pass { hard: true, reason: "..." } to also unlink the file.
-app.post('/api/rooms/:id/remove', (req, res) => {
-  if (!isRoomsAdmin(req)) return res.status(403).json({ ok: false, error: 'admin only' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isInteger(id) || id <= 0) return res.status(400).json({ ok: false, error: 'bad id' });
-  const { reason = '', hard = false } = req.body || {};
-  try {
-    const row = psqlQuery(`UPDATE wallco_rooms SET removed_at = NOW(), removed_reason = ${pgEsc(reason)}
-      WHERE id = ${id} AND removed_at IS NULL
-      RETURNING filename;`);
-    if (!row) return res.status(404).json({ ok: false, error: 'not found or already removed' });
-    if (hard) {
-      try { fs.unlinkSync(path.join(ROOMS_DIR, row)); } catch {}
-    }
-    res.json({ ok: true, id, hard: !!hard, filename: row });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── POST /api/rooms/:id/restore  (admin only) — reverses a soft-delete.
-app.post('/api/rooms/:id/restore', (req, res) => {
-  if (!isRoomsAdmin(req)) return res.status(403).json({ ok: false, error: 'admin only' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isInteger(id) || id <= 0) return res.status(400).json({ ok: false, error: 'bad id' });
-  try {
-    const row = psqlQuery(`UPDATE wallco_rooms SET removed_at = NULL, removed_reason = NULL
-      WHERE id = ${id} RETURNING id;`);
-    if (!row) return res.status(404).json({ ok: false, error: 'not found' });
-    res.json({ ok: true, id });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── POST /api/rooms/:id/marketing  (admin only) — toggle marketing flag + optional notes.
-app.post('/api/rooms/:id/marketing', (req, res) => {
-  if (!isRoomsAdmin(req)) return res.status(403).json({ ok: false, error: 'admin only' });
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isInteger(id) || id <= 0) return res.status(400).json({ ok: false, error: 'bad id' });
-  const { selected = true, notes = null } = req.body || {};
-  try {
-    const row = psqlQuery(`UPDATE wallco_rooms SET selected_for_marketing = ${pgEsc(!!selected)},
-        notes = COALESCE(${pgEsc(notes)}, notes)
-      WHERE id = ${id} RETURNING id, selected_for_marketing;`);
-    if (!row) return res.status(404).json({ ok: false, error: 'not found' });
-    res.json({ ok: true, row });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── POST /api/spoonflower/upload — upload ONE design as new product
-app.post('/api/spoonflower/upload', requireAdmin, (req, res) => {
-  const { design_id, dry = true, tags = '' } = req.body || {};
-  const d = findDesign(design_id);
-  if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
-  const file = localPathForDesign(d);
-  if (!file || !fs.existsSync(file)) {
-    return res.status(404).json({ ok: false, error: 'design file missing on disk', file });
-  }
-  const title = `${d.title} — wallco.ai AI-Original`;
-  const tagList = tags || `wallpaper,wallco-original,${d.category||''},wallco,${d.dominant_hex||''}`.replace(/,+$/,'');
-
-  let jobId;
-  try {
-    jobId = psqlQuery(`INSERT INTO spoonflower_jobs(kind, design_id, status, dry_run, params)
-                       VALUES('upload_new', ${parseInt(design_id,10)}, 'running', ${dry?'TRUE':'FALSE'},
-                       '${JSON.stringify({title, tagList, file}).replace(/'/g,"''")}'::jsonb)
-                       RETURNING id;`);
-  } catch (e) { return res.status(500).json({ ok:false, error: 'db: '+e.message }); }
-
-  const logFile = path.join(SF_JOBS_DIR, `upload_${jobId}.log`);
-  const args = [
-    path.join(__dirname, 'scripts', 'upload_spoonflower_new.py'),
-    file, title, '--tags', tagList,
-    ...(dry ? ['--dry'] : ['--live'])
-  ];
-  const proc = spawn('python3', args, {
-    cwd: __dirname,
-    env: { ...process.env, DATABASE_URL: DB_URL },
-    detached: true, stdio: ['ignore', 'pipe', 'pipe']
-  });
-  const out = fs.createWriteStream(logFile);
-  proc.stdout.pipe(out, { end: false });
-  proc.stderr.pipe(out, { end: false });
-  proc.on('exit', code => {
-    out.end();
-    // Parse the JSON-line log so the new Spoonflower pattern number + URL
-    // can be persisted onto the design row. The python script emits one
-    // JSON object per line; the final `done` event carries sf_design_id
-    // and sf_public_url on a successful LIVE publish.
-    let sfDesignId = null, sfUrl = null;
-    try {
-      const raw = fs.readFileSync(logFile, 'utf8');
-      for (const line of raw.split('\n')) {
-        const s = line.trim();
-        if (!s.startsWith('{')) continue;
-        try {
-          const ev = JSON.parse(s);
-          if (ev && ev.sf_design_id)  sfDesignId = String(ev.sf_design_id);
-          if (ev && ev.sf_public_url) sfUrl      = String(ev.sf_public_url);
-        } catch {}
-      }
-      psqlQuery(`UPDATE spoonflower_jobs SET status='${code===0?'done':'error'}',
-                 finished_at=now(),
-                 log='${raw.replace(/'/g,"''").slice(-8000)}'
-                 WHERE id=${jobId};`);
-      if (code === 0 && !dry && sfDesignId) {
-        const did = parseInt(design_id, 10);
-        psqlQuery(`UPDATE spoon_all_designs SET
-                     spoonflower_design_id   = ${pgEsc(sfDesignId)},
-                     spoonflower_url         = ${pgEsc(sfUrl)},
-                     spoonflower_uploaded_at = NOW()
-                   WHERE id = ${did};`);
-      }
-    } catch {}
-  });
-  proc.unref();
-  res.json({ ok: true, job_id: jobId, dry, log_url: `/api/spoonflower/status/${jobId}` });
-});
-
-// ── POST /api/design/:id/upload-shopify — push a wallco.ai design as a NEW Shopify product
-//
-// Admin-only. Reads the design row, picks the DW Original Number
-// (DIG_AI_xxxxxx — auto-assigned at INSERT-time by trg_spoon_all_designs_assign_dig)
-// as the SKU, and creates a draft Shopify product on
-// designer-laboratory-sandbox.myshopify.com with:
-//   • title       = ai_title || title
-//   • vendor      = "Designer Wallcoverings"
-//   • product_type= "Wallpaper"
-//   • tags        = wallco-ai, wallco-original, <category>, <ai_pattern>
-//   • SKU         = dig_number
-//   • image       = base64 of local_path
-//   • metafields  = dwc.dig_number, dwc.spoonflower_url, dwc.wallco_design_id
-//
-// Persists shopify_product_id + shopify_product_url onto the design row so
-// /designs index and /design/:id can show the linkage.
-app.post('/api/design/:id/upload-shopify', requireAdmin, express.json({ limit: '16kb' }), async (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-
-  const token = process.env.SHOPIFY_ADMIN_TOKEN;
-  const store = process.env.SHOPIFY_STORE;
-  if (!token || !store) {
-    return res.status(503).json({ error: 'SHOPIFY_ADMIN_TOKEN / SHOPIFY_STORE not configured' });
-  }
-
-  let d;
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (
-      SELECT id, ai_title, ai_pattern, ai_color_name, category, tags,
-             dominant_hex, dig_number, local_path, spoonflower_design_id,
-             spoonflower_url, shopify_product_id, shopify_product_url
-      FROM spoon_all_designs WHERE id=${id}) t;`);
-    d = JSON.parse(raw || '{}');
-  } catch (e) { return res.status(500).json({ error: 'db: ' + e.message }); }
-  if (!d || !d.id) return res.status(404).json({ error: 'design not found' });
-
-  if (d.shopify_product_id) {
-    return res.json({
-      ok: true,
-      already_uploaded: true,
-      shopify_product_id: d.shopify_product_id,
-      shopify_product_url: d.shopify_product_url,
-    });
-  }
-
-  if (!d.local_path || !fs.existsSync(d.local_path)) {
-    return res.status(409).json({ error: 'image file missing on disk', local_path: d.local_path });
-  }
-
-  const dig = d.dig_number || `DIG_AI_${String(d.id).padStart(6, '0')}`;
-  const title = d.ai_title || `wallco.ai ${dig}`;
-  const tagList = [
-    'wallco-ai', 'wallco-original',
-    d.category, d.ai_pattern, d.ai_color_name,
-  ].filter(Boolean).join(', ');
-
-  const imgB64 = fs.readFileSync(d.local_path).toString('base64');
-  const imgExt = (path.extname(d.local_path) || '.png').replace('.', '');
-
-  const productBody = {
-    product: {
-      title,
-      vendor: 'Designer Wallcoverings',
-      product_type: 'Wallpaper',
-      status: 'draft',
-      tags: tagList,
-      body_html: `<p>An AI-original wallcovering pattern from wallco.ai. DW Original Number ${dig}.</p>`,
-      variants: [{
-        sku: dig,
-        price: '0.00',
-        inventory_management: null,
-        inventory_policy: 'continue',
-      }],
-      images: [{
-        attachment: imgB64,
-        filename: `${dig}.${imgExt}`,
-        alt: title,
-      }],
-      metafields: [
-        { namespace: 'dwc', key: 'dig_number',         value: dig,               type: 'single_line_text_field' },
-        { namespace: 'dwc', key: 'wallco_design_id',   value: String(d.id),      type: 'single_line_text_field' },
-        ...(d.spoonflower_url ? [
-          { namespace: 'dwc', key: 'spoonflower_url',  value: d.spoonflower_url, type: 'url' },
-        ] : []),
-        ...(d.spoonflower_design_id ? [
-          { namespace: 'dwc', key: 'spoonflower_id',   value: String(d.spoonflower_design_id), type: 'single_line_text_field' },
-        ] : []),
-      ],
-    }
-  };
-
-  const url = `https://${store}/admin/api/2026-01/products.json`;
-  let r, body;
-  try {
-    r = await fetch(url, {
-      method: 'POST',
-      headers: {
-        'X-Shopify-Access-Token': token,
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(productBody),
-    });
-    body = await r.json();
-  } catch (e) {
-    return res.status(502).json({ error: 'shopify fetch failed: ' + e.message });
-  }
-
-  if (!r.ok || !body || !body.product || !body.product.id) {
-    return res.status(502).json({
-      error: 'shopify create failed',
-      status: r.status,
-      shopify_response: body,
-    });
-  }
-
-  const sid = String(body.product.id);
-  const handle = body.product.handle || '';
-  const sUrl = `https://${store.replace('.myshopify.com','')}.myshopify.com/admin/products/${sid}`;
-  const publicUrl = handle ? `https://${store}/products/${handle}` : sUrl;
-
-  try {
-    psqlQuery(`UPDATE spoon_all_designs SET
-                 shopify_product_id   = ${pgEsc(sid)},
-                 shopify_product_url  = ${pgEsc(publicUrl)},
-                 shopify_uploaded_at  = NOW()
-               WHERE id = ${id};`);
-  } catch {}
-
-  res.json({
-    ok: true,
-    id,
-    dig_number: dig,
-    shopify_product_id: sid,
-    shopify_product_url: publicUrl,
-    shopify_admin_url: sUrl,
-    shopify_status: body.product.status,
-  });
-});
-
-// ── POST /api/spoonflower/launch-chrome — open dedicated debug Chrome for cookie reuse
-app.post('/api/spoonflower/launch-chrome', requireAdmin, (req, res) => {
-  try {
-    const out = execSync(`zsh ${path.join(__dirname, 'scripts', 'launch_spoon_chrome.sh')} 2>&1`,
-                         { encoding: 'utf8', timeout: 10000 });
-    res.json({ ok: true, output: out });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message, output: e.stdout || '' });
-  }
-});
-
-// ── GET /api/spoonflower/chrome-status — is debug Chrome reachable?
-app.get('/api/spoonflower/chrome-status', requireAdmin, async (_req, res) => {
-  try {
-    const r = await fetch('http://127.0.0.1:9222/json/version', { signal: AbortSignal.timeout(1500) });
-    if (r.ok) {
-      const j = await r.json();
-      return res.json({ ok: true, running: true, browser: j.Browser });
-    }
-    res.json({ ok: true, running: false });
-  } catch {
-    res.json({ ok: true, running: false });
-  }
-});
-
-// ── POST /api/spoonflower/pull-all — bulk pull DW account designs
-app.post('/api/spoonflower/pull-all', requireAdmin, (req, res) => {
-  const { limit = 0, account = 'dw' } = req.body || {};
-
-  let jobId;
-  try {
-    jobId = psqlQuery(`INSERT INTO spoonflower_jobs(kind, status, params)
-                       VALUES('pull_all', 'running',
-                       '${JSON.stringify({limit, account}).replace(/'/g,"''")}'::jsonb)
-                       RETURNING id;`);
-  } catch (e) { return res.status(500).json({ ok:false, error:'db: '+e.message }); }
-
-  const logFile = path.join(SF_JOBS_DIR, `pull_${jobId}.log`);
-  const args = [
-    path.join(__dirname, 'scripts', 'pull_spoonflower_account.py'),
-    '--account', String(account)
-  ];
-  if (limit && parseInt(limit,10) > 0) { args.push('--limit', String(parseInt(limit,10))); }
-
-  const proc = spawn('python3', args, {
-    cwd: __dirname,
-    env: { ...process.env, DATABASE_URL: DB_URL },
-    detached: true, stdio: ['ignore', 'pipe', 'pipe']
-  });
-  const out = fs.createWriteStream(logFile);
-  proc.stdout.pipe(out, { end:false });
-  proc.stderr.pipe(out, { end:false });
-  proc.on('exit', code => {
-    out.end();
-    try {
-      psqlQuery(`UPDATE spoonflower_jobs SET status='${code===0?'done':'error'}',
-                 finished_at=now(),
-                 log='${fs.readFileSync(logFile,'utf8').replace(/'/g,"''").slice(-8000)}'
-                 WHERE id=${jobId};`);
-    } catch {}
-  });
-  proc.unref();
-  res.json({ ok:true, job_id: jobId, log_url: `/api/spoonflower/status/${jobId}` });
-});
-
-// ── GET /api/spoonflower/status/:job — return job + log tail
-app.get('/api/spoonflower/status/:job', requireAdmin, (req, res) => {
-  const jobId = parseInt(req.params.job, 10);
-  let row;
-  try {
-    row = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id,kind,design_id,status,dry_run,params,created_at,finished_at
-                     FROM spoonflower_jobs WHERE id=${jobId}) t;`);
-  } catch (e) { return res.status(500).json({ ok:false, error:'db: '+e.message }); }
-  if (!row) return res.status(404).json({ ok:false, error:'job not found' });
-
-  // Find log file
-  let log = '';
-  for (const f of ['upload', 'pull']) {
-    const p = path.join(SF_JOBS_DIR, `${f}_${jobId}.log`);
-    if (fs.existsSync(p)) { log = fs.readFileSync(p, 'utf8'); break; }
-  }
-  // Extract the Spoonflower design id + URL from the JSON-line log so the
-  // /design/:id frontend can render "Spoonflower pattern #N → view" after
-  // polling without re-reading the raw log.
-  let sf_design_id = null, sf_public_url = null;
-  for (const line of log.split('\n')) {
-    const s = line.trim();
-    if (!s.startsWith('{')) continue;
-    try {
-      const ev = JSON.parse(s);
-      if (ev.sf_design_id)  sf_design_id  = String(ev.sf_design_id);
-      if (ev.sf_public_url) sf_public_url = String(ev.sf_public_url);
-    } catch {}
-  }
-  res.type('application/json').send(JSON.stringify({
-    ok: true,
-    job: JSON.parse(row),
-    sf_design_id,
-    sf_public_url,
-    log_tail: log.slice(-8000)
-  }, null, 2));
-});
-
-// ── GET /spoonflower — admin page: pulled DW library + per-design publish buttons.
-// HARD-GATED 2026-05-13 — page was leaking the DW Spoonflower library publicly.
-// requireAdmin returns 404 to non-admins; admin can still reach via direct URL.
-app.get('/spoonflower', requireAdmin, (req, res) => {
-  let pulled = [];
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.pulled_at DESC), '[]'::json) FROM (
-      SELECT id, sf_design_id, title, slug, design_url, thumbnail_url, local_path, file_bytes, account, pulled_at
-      FROM spoonflower_pulled_designs LIMIT 2000) t;`);
-    pulled = JSON.parse(raw || '[]');
-  } catch {}
-
-  let recentJobs = [];
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC), '[]'::json) FROM (
-      SELECT id, kind, design_id, status, dry_run, created_at, finished_at
-      FROM spoonflower_jobs ORDER BY created_at DESC LIMIT 30) t;`);
-    recentJobs = JSON.parse(raw || '[]');
-  } catch {}
-
-  res.type('html').send(`${htmlHead({
-    title: 'Spoonflower Library — wallco.ai admin',
-    description: 'DW Spoonflower designs (pulled) + upload jobs',
-    canonical: 'https://wallco.ai/spoonflower'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:40px">
-  <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:32px;margin:0 0 8px">DW Spoonflower Library</h1>
-  <p style="color:#666;margin:0 0 24px">${pulled.length} design${pulled.length===1?'':'s'} pulled · ${recentJobs.length} recent job${recentJobs.length===1?'':'s'}</p>
-
-  <div style="margin-bottom:14px;padding:14px;border:1px solid #ddd;border-radius:6px;background:#fafafa;font-size:13px;line-height:1.55">
-    <strong>One-time setup (proven pattern from months-ago working scraper):</strong>
-    <ol style="margin:8px 0 6px 22px;padding:0">
-      <li>Click <b>Launch Debug Chrome</b> below — opens a 2nd Chrome window (won't disturb your main Chrome).</li>
-      <li>Sign in once with <code>info@designerwallcoverings.com</code> / <code>*Spoonaccess911*</code>.</li>
-      <li>Come back here — <b>Pull ALL DW designs</b> will then attach to that signed-in window automatically.</li>
-    </ol>
-    <span id="chrome-status" style="color:#888">Checking debug-Chrome status…</span>
-  </div>
-  <div style="margin-bottom:24px;display:flex;gap:10px;flex-wrap:wrap">
-    <button id="btn-launch-chrome" class="btn-outline">Launch Debug Chrome</button>
-    <button id="btn-pull-test" class="btn-outline">Pull-all (limit 3, dry test)</button>
-    <button id="btn-pull-all"  class="btn-primary">Pull ALL DW designs</button>
-    <span id="pull-status" style="align-self:center;color:#666;font-size:13px"></span>
-  </div>
-
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:32px 0 12px">Pulled designs</h2>
-  ${pulled.length === 0
-    ? '<p style="color:#999">No designs pulled yet. Click "Pull ALL DW designs" above to start.</p>'
-    : `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:18px">
-        ${pulled.map(p => `<div style="border:1px solid rgba(0,0,0,.08);border-radius:6px;overflow:hidden;background:#fff">
-          ${p.thumbnail_url ? `<img src="${p.thumbnail_url}" style="width:100%;aspect-ratio:1;object-fit:cover" loading="lazy">` : '<div style="aspect-ratio:1;background:#eee"></div>'}
-          <div style="padding:10px 12px;font-size:12px">
-            <div style="font-weight:500">${(p.title||'(no title)').slice(0,48)}</div>
-            <div style="color:#888;margin-top:2px">SF#${p.sf_design_id}${p.file_bytes ? ' · '+Math.round(p.file_bytes/1024)+' KB' : ''}</div>
-            <a href="${p.design_url}" target="_blank" style="color:#888;font-size:11px" rel="noopener noreferrer nofollow">view on spoonflower →</a>
-          </div>
-        </div>`).join('')}
-      </div>`}
-
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:40px 0 12px">Recent jobs</h2>
-  ${recentJobs.length === 0
-    ? '<p style="color:#999">No jobs yet.</p>'
-    : `<table style="width:100%;border-collapse:collapse;font-size:13px">
-        <thead><tr style="text-align:left;border-bottom:1px solid #ddd;color:#666">
-          <th style="padding:8px 6px">ID</th><th>Kind</th><th>Design</th><th>Status</th><th>Dry</th><th>Created</th><th></th>
-        </tr></thead><tbody>
-        ${recentJobs.map(j => `<tr style="border-bottom:1px solid #f0f0f0">
-          <td style="padding:8px 6px">${j.id}</td>
-          <td>${j.kind}</td>
-          <td>${j.design_id || '—'}</td>
-          <td><span style="padding:2px 6px;border-radius:3px;background:${j.status==='done'?'#d4edda':j.status==='error'?'#f8d7da':'#fff3cd'};color:${j.status==='done'?'#155724':j.status==='error'?'#721c24':'#856404'}">${j.status}</span></td>
-          <td>${j.dry_run ? 'yes' : '—'}</td>
-          <td style="color:#888">${j.created_at ? new Date(j.created_at).toLocaleString() : '—'}</td>
-          <td><a href="/api/spoonflower/status/${j.id}" target="_blank" style="color:#888" rel="noopener noreferrer">log →</a></td>
-        </tr>`).join('')}
-        </tbody></table>`}
-</main>
-${FOOTER}
-<script>
-async function post(u, b){ var r = await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})}); return r.json(); }
-async function get(u){ var r = await fetch(u); return r.json(); }
-
-async function refreshChromeStatus() {
-  var el = document.getElementById('chrome-status');
-  try {
-    var j = await get('/api/spoonflower/chrome-status');
-    if (j.running) {
-      el.style.color = '#155724';
-      el.innerHTML = '✓ Debug Chrome running on :9222 (' + (j.browser||'Chrome') + ') — pulls will auto-attach.';
-    } else {
-      el.style.color = '#856404';
-      el.innerHTML = '⚠ Debug Chrome not running. Click "Launch Debug Chrome" to start it.';
-    }
-  } catch(e) {
-    el.style.color = '#c00'; el.textContent = 'Status check failed: ' + e.message;
-  }
-}
-refreshChromeStatus();
-setInterval(refreshChromeStatus, 5000);
-
-document.getElementById('btn-launch-chrome').addEventListener('click', async function(){
-  this.disabled = true;
-  var s = document.getElementById('pull-status');
-  s.textContent = 'Launching debug Chrome…';
-  var j = await post('/api/spoonflower/launch-chrome', {});
-  s.innerHTML = j.ok
-    ? '<pre style="margin:8px 0;font-size:11px;background:#000;color:#0f0;padding:8px;border-radius:4px;white-space:pre-wrap">'+(j.output||'').replace(/[<>]/g,'')+'</pre>'
-    : 'Launch failed: ' + (j.error||'unknown');
-  this.disabled = false;
-  setTimeout(refreshChromeStatus, 2000);
-});
-document.getElementById('btn-pull-test').addEventListener('click', async function(){
-  this.disabled=true;
-  document.getElementById('pull-status').textContent = 'Starting limited pull (3 designs)…';
-  var j = await post('/api/spoonflower/pull-all', { limit: 3, account: 'dw' });
-  document.getElementById('pull-status').innerHTML = j.ok
-    ? 'Job '+j.job_id+' running — <a href="/api/spoonflower/status/'+j.job_id+'" target="_blank" rel="noopener noreferrer">view log</a>. Reload in ~60s.'
-    : 'Failed: ' + (j.error||'unknown');
-  this.disabled=false;
-});
-document.getElementById('btn-pull-all').addEventListener('click', async function(){
-  if (!confirm('Pull ALL DW Spoonflower designs? This may take 10-30+ minutes and downloads every file to disk.')) return;
-  this.disabled=true;
-  document.getElementById('pull-status').textContent = 'Starting full pull…';
-  var j = await post('/api/spoonflower/pull-all', { limit: 0, account: 'dw' });
-  document.getElementById('pull-status').innerHTML = j.ok
-    ? 'Job '+j.job_id+' running — <a href="/api/spoonflower/status/'+j.job_id+'" target="_blank" rel="noopener noreferrer">view log</a>. Reload page to refresh table.'
-    : 'Failed: ' + (j.error||'unknown');
-  this.disabled=false;
-});
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── GET /api/voice/preview — short ElevenLabs narration for a design
-app.get('/api/voice/preview', async (req, res) => {
-  if (!ELEVEN_KEY) return res.status(500).type('text/plain').send('ELEVENLABS_API_KEY not configured');
-  const d = findDesign(req.query.design_id);
-  if (!d) return res.status(404).type('text/plain').send('design not found');
-  const text = `${d.title}. An AI-original wallpaper pattern by wallco. Category: ${d.category}. Dominant color: ${d.dominant_hex}. Generated by ${d.generator}, seed ${d.seed}. Every pattern is unique, never repeated.`;
-  try {
-    const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${ELEVEN_VOICE_ID}`, {
-      method: 'POST',
-      headers: {
-        'xi-api-key': ELEVEN_KEY,
-        'Content-Type': 'application/json',
-        'Accept': 'audio/mpeg'
-      },
-      body: JSON.stringify({
-        text,
-        model_id: 'eleven_turbo_v2_5',
-        voice_settings: { stability: 0.4, similarity_boost: 0.75 }
-      })
-    });
-    if (!r.ok) {
-      const errText = await r.text();
-      return res.status(502).type('text/plain').send('elevenlabs: '+errText.slice(0,400));
-    }
-    res.setHeader('Content-Type', 'audio/mpeg');
-    res.setHeader('Cache-Control', 'no-store');
-    const buf = Buffer.from(await r.arrayBuffer());
-    res.end(buf);
-  } catch (e) {
-    res.status(500).type('text/plain').send('voice error: '+e.message);
-  }
-});
-
-// ─────────────────────────────────────────────────────────────────────────
-// WALLCO GENERATOR (30-min cron + recipe picker)
-// ─────────────────────────────────────────────────────────────────────────
-app.get('/api/generator/recipes', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.id), '[]'::json) FROM (
-      SELECT id, name, source_artists, style_tags, color_hex_seeds,
-             prompt_template, weight, enabled, last_run_at, run_count, created_at
-      FROM wallco_generator_recipe) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.post('/api/generator/recipes', (req, res) => {
-  const { id, name, source_artists = [], style_tags = [],
-          color_hex_seeds = [], prompt_template = null,
-          weight = 1, enabled = true } = req.body || {};
-  if (!name) return res.status(400).json({ error: 'name required' });
-  const arrLit = a => `ARRAY[${(a || []).map(v => "'" + String(v).replace(/'/g, "''") + "'").join(',') || ''}]::text[]`;
-  const tpl = prompt_template ? `'${prompt_template.replace(/'/g, "''")}'` : 'NULL';
-  try {
-    let result;
-    if (id) {
-      result = psqlQuery(`UPDATE wallco_generator_recipe SET
-        name='${name.replace(/'/g,"''")}',
-        source_artists=${arrLit(source_artists)},
-        style_tags=${arrLit(style_tags)},
-        color_hex_seeds=${arrLit(color_hex_seeds)},
-        prompt_template=${tpl},
-        weight=${parseInt(weight,10) || 1},
-        enabled=${enabled ? 'TRUE' : 'FALSE'}
-        WHERE id=${parseInt(id,10)} RETURNING id;`);
-    } else {
-      result = psqlQuery(`INSERT INTO wallco_generator_recipe
-        (name, source_artists, style_tags, color_hex_seeds, prompt_template, weight, enabled)
-        VALUES ('${name.replace(/'/g,"''")}',
-          ${arrLit(source_artists)}, ${arrLit(style_tags)}, ${arrLit(color_hex_seeds)},
-          ${tpl}, ${parseInt(weight,10) || 1}, ${enabled ? 'TRUE' : 'FALSE'})
-        RETURNING id;`);
-    }
-    res.json({ ok: true, id: parseInt(result, 10) });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.delete('/api/generator/recipes/:id', (req, res) => {
-  try {
-    psqlQuery(`DELETE FROM wallco_generator_recipe WHERE id=${parseInt(req.params.id,10)};`);
-    res.json({ ok: true });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// One-step batch generate: colors + elements + styles + base patterns → SDXL
-app.post('/api/generator/batch', (req, res) => {
-  const { colors = '', elements = [], styles = [], patterns = [], extra_text = '' } = req.body || {};
-  // Build the prompt
-  const artistLine  = patterns.map(p => p.artist).filter(Boolean).slice(0, 3);
-  const patternTitles = patterns.map(p => p.title).filter(Boolean).slice(0, 3);
-  const patternHex  = patterns.map(p => p.dominant_hex).filter(Boolean);
-  const allColors = [colors.trim(), ...patternHex.slice(0, 4)].filter(Boolean).join(', ');
-  const promptParts = [
-    'A seamless wallpaper pattern',
-    artistLine.length ? `in the visual lineage of ${artistLine.join(' and ')}` : '',
-    styles.length    ? `with ${styles.slice(0,4).join(' / ')} influence` : '',
-    elements.length  ? `featuring ${elements.slice(0,8).join(', ')}` : '',
-    patternTitles.length ? `referencing the look of ${patternTitles.join(' / ')}` : '',
-    allColors        ? `palette ${allColors}` : '',
-    extra_text || '',
-    'high detail, museum-quality, 24x24 inch tileable, no text, no signature.'
-  ].filter(Boolean);
-  const prompt = promptParts.join(', ').replace(/\s+/g, ' ').trim();
-
-  try {
-    const runId = psqlQuery(`INSERT INTO wallco_generator_runs(recipe_id, prompt, palette_seed_ids, status)
-      VALUES(NULL, '${prompt.replace(/'/g,"''")}', '{}'::bigint[], 'running') RETURNING id;`);
-
-    const category = (styles[0] || 'mixed').toLowerCase();
-    const r = spawnSync('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1', '--kind', 'seamless_tile',
-      '--category', category,
-      '--prompts', prompt,
-    ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
-         encoding: 'utf8', timeout: 120000 });
-
-    let designId = null, filename = null, domHex = null;
-    try {
-      const row = psqlQuery(`SELECT id::text || '|' || COALESCE(local_path,'') || '|' || COALESCE(dominant_hex,'')
-        FROM spoon_all_designs ORDER BY id DESC LIMIT 1;`);
-      const [idStr, lp, dh] = row.split('|');
-      designId = parseInt(idStr, 10);
-      filename = lp ? path.basename(lp) : null;
-      domHex = dh || null;
-    } catch {}
-
-    const ok = r.status === 0;
-    psqlQuery(`UPDATE wallco_generator_runs SET
-      status='${ok ? 'done' : 'error'}',
-      design_id=${designId || 'NULL'},
-      error=${ok ? 'NULL' : "'"+((r.stderr||r.stdout||'').slice(-300).replace(/'/g,"''"))+"'"},
-      finished_at=now() WHERE id=${runId};`);
-
-    res.json({ ok, design_id: designId, run_id: parseInt(runId,10),
-               prompt, filename, dominant_hex: domHex,
-               error: ok ? null : (r.stderr || r.stdout || '').slice(-400) });
-  } catch (e) { res.status(500).json({ ok:false, error: e.message }); }
-});
-
-app.post('/api/generator/run-now', (req, res) => {
-  // Fire-and-return — generator_tick.js runs ~10-30s and the UI doesn't
-  // need to block the click. Buffer the tail of stdout so the response
-  // can include a snippet; finalize via the runs list later.
-  const logFile = path.join(__dirname, 'data', 'logs', `run-now-${Date.now()}.log`);
-  fs.mkdirSync(path.dirname(logFile), { recursive: true });
-  const out = fs.openSync(logFile, 'a');
-  const proc = spawn('node', [path.join(__dirname, 'scripts', 'generator_tick.js')], {
-    cwd: __dirname,
-    detached: true,
-    stdio: ['ignore', out, out],
-  });
-  proc.unref();
-  res.json({
-    ok: true,
-    started_at: new Date().toISOString(),
-    log: path.relative(__dirname, logFile),
-    note: 'tick kicked — check the Runs list in ~30-60s, or tail data/logs/' + path.basename(logFile),
-  });
-});
-
-app.post('/api/generator/refresh-orders', (req, res) => {
-  // Same bug as run-now had: detached + pipe + waiting on 'exit' hangs
-  // forever because the parent owns the pipes but the child is detached.
-  // Two-mode now:
-  //   sync=1 (default for the UI button) — short timeout, stream output
-  //     back inline so the alert() can show real success/failure
-  //   sync=0 — fire & return immediately with a log handle
-  const limit = parseInt(req.body?.limit || '50', 10);
-  const sync = req.body?.sync === false ? false : true;
-  if (!sync) {
-    const logFile = path.join(__dirname, 'data', 'logs', `refresh-orders-${Date.now()}.log`);
-    fs.mkdirSync(path.dirname(logFile), { recursive: true });
-    const out = fs.openSync(logFile, 'a');
-    const proc = spawn('node',
-      [path.join(__dirname, 'scripts', 'fetch_recent_orders_palette.js'), '--limit', String(limit)],
-      { cwd: __dirname, detached: true, stdio: ['ignore', out, out] });
-    proc.unref();
-    return res.json({ ok: true, started_at: new Date().toISOString(), log: path.relative(__dirname, logFile), async: true });
-  }
-  // Sync path — block until the script exits or 2 min, then return its output
-  let buf = '';
-  const proc = spawn('node',
-    [path.join(__dirname, 'scripts', 'fetch_recent_orders_palette.js'), '--limit', String(limit)],
-    { cwd: __dirname, stdio: ['ignore', 'pipe', 'pipe'] });
-  proc.stdout.on('data', d => buf += d);
-  proc.stderr.on('data', d => buf += d);
-  const killer = setTimeout(() => { try { proc.kill('SIGKILL'); } catch {} }, 120_000);
-  proc.on('exit', code => {
-    clearTimeout(killer);
-    res.json({ ok: code === 0, exit: code, output: buf.slice(-3000) });
-  });
-  proc.on('error', err => {
-    clearTimeout(killer);
-    res.json({ ok: false, error: err.message, output: buf.slice(-3000) });
-  });
-});
-
-app.get('/api/generator/runs', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.started_at DESC), '[]'::json) FROM (
-      SELECT r.id, r.recipe_id, rc.name AS recipe_name, r.design_id,
-             LEFT(r.prompt, 180) AS prompt, r.status, r.error,
-             r.started_at, r.finished_at
-      FROM wallco_generator_runs r
-      LEFT JOIN wallco_generator_recipe rc ON rc.id = r.recipe_id
-      ORDER BY r.started_at DESC LIMIT 30) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/generator/palette', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.ordered_at DESC), '[]'::json) FROM (
-      SELECT id, sku, product_title, vendor, dominant_hex, style_tags, ordered_at, image_url
-      FROM wallco_recent_orders_palette
-      WHERE dominant_hex IS NOT NULL
-      ORDER BY ordered_at DESC LIMIT 40) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.post('/api/generator/cron', (req, res) => {
-  const { action } = req.body || {};
-  const plist = `${require('os').homedir()}/Library/LaunchAgents/com.steve.wallco-generator.plist`;
-  if (!['start', 'stop', 'status'].includes(action)) {
-    return res.status(400).json({ error: 'action must be start|stop|status' });
-  }
-  try {
-    if (action === 'start') {
-      execSync(`launchctl bootstrap gui/$(id -u) "${plist}" 2>&1 || launchctl load "${plist}" 2>&1 || true`);
-      const listed = execSync(`launchctl list | grep com.steve.wallco-generator || echo "(not loaded)"`, { encoding: 'utf8' }).trim();
-      res.json({ ok: true, action, status: listed });
-    } else if (action === 'stop') {
-      execSync(`launchctl bootout gui/$(id -u)/com.steve.wallco-generator 2>&1 || launchctl unload "${plist}" 2>&1 || true`);
-      res.json({ ok: true, action });
-    } else {
-      const listed = execSync(`launchctl list | grep com.steve.wallco-generator || echo "(not loaded)"`, { encoding: 'utf8' }).trim();
-      res.json({ ok: true, action, status: listed });
-    }
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── /generator admin page
-app.get('/generator', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Generator — wallco.ai admin',
-    description: '30-min recipe-driven pattern generator',
-    canonical: 'https://wallco.ai/generator'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:32px 40px;max-width:1300px;margin:0 auto">
-  <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:32px;margin:0 0 4px">Pattern Generator</h1>
-  <p style="color:#666;margin:0 0 28px">Every 30 min, the cron picks a recipe → fetches a palette from recent DW Shopify orders → renders a new design.</p>
-
-  <div style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:28px">
-    <div style="padding:18px;border:1px solid #ddd;border-radius:8px;background:#fafafa">
-      <h2 style="margin:0 0 8px;font-size:16px">30-minute cron</h2>
-      <p id="cron-status" style="margin:0 0 10px;font-size:13px;color:#555">Checking…</p>
-      <div style="display:flex;gap:8px;flex-wrap:wrap">
-        <button class="btn-primary" id="btn-cron-start">Start cron</button>
-        <button class="btn-outline" id="btn-cron-stop">Stop cron</button>
-        <button class="btn-outline" id="btn-run-now">Run tick NOW</button>
-      </div>
-    </div>
-    <div style="padding:18px;border:1px solid #ddd;border-radius:8px;background:#fafafa">
-      <h2 style="margin:0 0 8px;font-size:16px">DW orders palette</h2>
-      <p id="palette-status" style="margin:0 0 10px;font-size:13px;color:#555">Loading…</p>
-      <button class="btn-outline" id="btn-refresh-orders">Refresh from Shopify (last 50 orders)</button>
-    </div>
-  </div>
-
-  <!-- ── ONE-STEP BATCH GENERATE ────────────────────────────────────────── -->
-  <div style="margin-bottom:28px;padding:20px;border:1px solid #1a1a1a;border-radius:10px;background:#1a1a1a;color:#f0eadc">
-    <h2 style="margin:0 0 4px;font-family:'Cormorant Garamond',serif;font-weight:300;font-size:24px;color:#d2b15c">Batch generate · one step</h2>
-    <p style="margin:0 0 16px;font-size:13px;opacity:.75">Pick how many · pick colors · pick elements · pick styles · pick base patterns · go.</p>
-
-    <div style="display:grid;grid-template-columns:80px 1fr;gap:10px 18px;align-items:center;margin-bottom:14px">
-      <label style="font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#aaa">Count</label>
-      <input id="batch-n" type="number" min="1" max="50" value="10" style="width:80px;padding:6px 10px;background:#0f0e0c;color:#fff;border:1px solid #444;border-radius:4px;font-family:inherit;font-size:14px">
-
-      <label style="font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#aaa">Colors</label>
-      <div>
-        <input id="batch-colors" type="text" placeholder="e.g. terracotta, sage, midnight blue, gold" style="width:100%;padding:8px 10px;background:#0f0e0c;color:#fff;border:1px solid #444;border-radius:4px;font-family:inherit;font-size:13px">
-        <div id="batch-color-suggest" style="font-size:11px;color:#888;margin-top:4px"></div>
-      </div>
-
-      <label style="font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#aaa">Elements</label>
-      <div id="batch-elements-picker" style="display:flex;flex-wrap:wrap;gap:5px;max-height:120px;overflow-y:auto;padding:4px"></div>
-
-      <label style="font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#aaa">Styles</label>
-      <div id="batch-styles-picker" style="display:flex;flex-wrap:wrap;gap:5px;max-height:80px;overflow-y:auto;padding:4px"></div>
-
-      <label style="font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#aaa">Patterns</label>
-      <div>
-        <input id="batch-patterns" type="text" placeholder="Search base patterns (peacock, baroque, Morris…)" style="width:100%;padding:6px 10px;background:#0f0e0c;color:#fff;border:1px solid #444;border-radius:4px;font-family:inherit;font-size:13px">
-        <div id="batch-patterns-results" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:4px;max-height:140px;overflow-y:auto;margin-top:6px"></div>
-        <div id="batch-patterns-picked" style="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px"></div>
-      </div>
-    </div>
-
-    <div style="display:flex;gap:10px;align-items:center;margin-top:12px">
-      <button id="btn-batch-go" style="padding:12px 28px;background:#d2b15c;color:#1a1a1a;border:0;border-radius:6px;font-weight:600;font-size:14px;cursor:pointer;font-family:inherit">Generate batch</button>
-      <span id="batch-status" style="font-size:12px;color:#aaa"></span>
-    </div>
-    <div id="batch-results" style="margin-top:14px;display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px"></div>
-  </div>
-
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:24px 0 10px">Recipes</h2>
-  <div id="recipes" style="margin-bottom:16px"></div>
-
-  <details style="margin-bottom:32px;padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff">
-    <summary style="cursor:pointer;font-weight:500">+ Add new recipe</summary>
-    <form id="recipe-form" style="margin-top:14px;display:grid;gap:10px;max-width:680px">
-      <input name="name" placeholder="Recipe name (e.g., Botanical × Damask Mix)" required style="padding:10px;border:1px solid #ccc;border-radius:4px;font-size:14px">
-      <textarea name="source_artists" placeholder="Source artists, comma-separated" rows="2" style="padding:10px;border:1px solid #ccc;border-radius:4px;font-size:14px"></textarea>
-      <textarea name="style_tags" placeholder="Style tags, comma-separated (damask, floral, geometric, toile)" rows="2" style="padding:10px;border:1px solid #ccc;border-radius:4px;font-size:14px"></textarea>
-      <input name="color_hex_seeds" placeholder="Optional color hex seeds (#ab1234, #cd5678)" style="padding:10px;border:1px solid #ccc;border-radius:4px;font-size:14px">
-      <textarea name="prompt_template" placeholder="Prompt template (use {artist}, {style}, {palette})" rows="3" style="padding:10px;border:1px solid #ccc;border-radius:4px;font-size:14px;font-family:monospace"></textarea>
-      <button type="submit" class="btn-primary">Save recipe</button>
-    </form>
-  </details>
-
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:24px 0 10px">Recent runs</h2>
-  <div id="runs"></div>
-
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:32px 0 6px">DW Orders palette (last 40)</h2>
-  <div style="display:flex;gap:14px;align-items:center;margin:6px 0 12px;font:11px var(--sans,system-ui)">
-    <span id="palette-status-2" style="color:#555">Loading…</span>
-    <label style="display:inline-flex;align-items:center;gap:8px;letter-spacing:.08em;text-transform:uppercase;color:#888;margin-left:auto">Tile size
-      <input type="range" id="palette-density" min="80" max="240" step="20" value="160" style="flex:0 0 120px">
-    </label>
-    <span id="palette-density-readout" style="font-variant-numeric:tabular-nums;color:#555">160px</span>
-  </div>
-  <div id="palette" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px"></div>
-  <script>
-  (function(){
-    var sl = document.getElementById('palette-density');
-    var grid = document.getElementById('palette');
-    var ro = document.getElementById('palette-density-readout');
-    var saved = parseInt(localStorage.getItem('wallco-palette-density')||'',10);
-    if (saved >= 80 && saved <= 240) sl.value = saved;
-    function paint(v){ grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax('+v+'px, 1fr))'; ro.textContent = v+'px'; }
-    paint(sl.value);
-    sl.addEventListener('input', function(){ paint(this.value); localStorage.setItem('wallco-palette-density', this.value); });
-  })();
-  </script>
-</main>
-${FOOTER}
-
-<script>
-async function get(u){ return (await fetch(u)).json(); }
-async function post(u,b){ return (await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})})).json(); }
-async function del(u){ return (await fetch(u,{method:'DELETE'})).json(); }
-
-async function loadCron() {
-  var j = await post('/api/generator/cron', { action: 'status' });
-  var el = document.getElementById('cron-status');
-  el.innerHTML = j.status && !j.status.includes('not loaded')
-    ? '<span style="color:#155724;font-weight:500">✓ LOADED</span> — <code style="font-size:11px">'+j.status+'</code>'
-    : '<span style="color:#856404;font-weight:500">○ STOPPED</span> — click Start cron to enable 30-min auto-generation.';
-}
-async function loadPaletteStatus() {
-  var p = await get('/api/generator/palette');
-  var msg = p.length + ' palette swatches from recent DW orders';
-  var s1 = document.getElementById('palette-status'); if (s1) s1.textContent = msg;
-  var s2 = document.getElementById('palette-status-2'); if (s2) s2.textContent = msg;
-  document.getElementById('palette').innerHTML = p.map(function(x){
-    var title = (x.product_title||x.sku||'').replace(/"/g,'&quot;');
-    var tagSummary = ((x.style_tags||[]).slice(0,3).join(' · '));
-    return '<button type="button" data-hex="'+x.dominant_hex+'" title="'+title+(tagSummary?' — '+tagSummary:'')+'"' +
-      ' onclick="(function(h){var el=document.getElementById(\\'batch-colors\\');if(!el)return;var cur=el.value.trim();el.value=cur?(cur+\\', \\'+h):h;el.focus();})(this.dataset.hex)"' +
-      ' style="text-align:left;background:transparent;border:0;padding:0;cursor:pointer">' +
-      '<div style="aspect-ratio:1;background:'+x.dominant_hex+';border:1px solid rgba(0,0,0,.08);border-radius:6px;transition:transform .15s"></div>' +
-      '<div style="font-size:10px;color:#666;margin-top:4px;font-family:ui-monospace,monospace">'+x.dominant_hex+'</div>' +
-      '<div style="font-size:10px;color:#333;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+title+'</div>' +
-      (x.vendor ? '<div style="font-size:9px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+x.vendor+'</div>' : '') +
-      '</button>';
-  }).join('') || '<p style="color:#999;grid-column:1/-1">No palette data yet. Click "Refresh from Shopify" above.</p>';
-}
-async function loadRecipes() {
-  var r = await get('/api/generator/recipes');
-  document.getElementById('recipes').innerHTML = r.map(function(x){
-    var artists = (x.source_artists||[]).join(', ');
-    var styles  = (x.style_tags||[]).join(', ');
-    var ran = x.last_run_at ? new Date(x.last_run_at).toLocaleString() : 'never';
-    return '<div style="padding:14px;border:1px solid #ddd;border-radius:6px;margin-bottom:10px;background:'+(x.enabled?'#fff':'#f5f5f5')+'">' +
-      '<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:14px">' +
-        '<div style="flex:1">' +
-          '<div style="font-weight:500;font-size:15px">'+x.name+(x.enabled?'':' <span style="color:#999;font-size:11px;font-weight:400">(disabled)</span>')+'</div>' +
-          '<div style="font-size:12px;color:#666;margin-top:4px"><b>Artists:</b> '+artists+'</div>' +
-          '<div style="font-size:12px;color:#666"><b>Styles:</b> '+styles+'</div>' +
-          (x.prompt_template ? '<div style="font-size:11px;color:#888;margin-top:6px;font-family:monospace;background:#fafafa;padding:6px;border-radius:3px">'+x.prompt_template.replace(/[<>]/g,'')+'</div>' : '') +
-          '<div style="font-size:11px;color:#999;margin-top:4px">Last run: '+ran+' · '+(x.run_count||0)+' runs</div>' +
-        '</div>' +
-        '<div style="display:flex;flex-direction:column;gap:4px">' +
-          '<button onclick="toggleRecipe('+x.id+','+(!x.enabled)+')" style="padding:4px 10px;font-size:12px">'+(x.enabled?'Disable':'Enable')+'</button>' +
-          '<button onclick="deleteRecipe('+x.id+')" style="padding:4px 10px;font-size:12px;color:#c00">Delete</button>' +
-        '</div>' +
-      '</div></div>';
-  }).join('') || '<p style="color:#999">No recipes yet — add one below.</p>';
-}
-async function loadRuns() {
-  var r = await get('/api/generator/runs');
-  document.getElementById('runs').innerHTML = r.length ? '<table style="width:100%;border-collapse:collapse;font-size:13px"><thead><tr style="text-align:left;border-bottom:1px solid #ddd"><th style="padding:6px">When</th><th>Recipe</th><th>Status</th><th>Design</th><th>Prompt</th></tr></thead><tbody>' +
-    r.map(function(x){
-      var when = x.started_at ? new Date(x.started_at).toLocaleString() : '?';
-      var statusBg = x.status==='done'?'#d4edda':x.status==='error'?'#f8d7da':'#fff3cd';
-      var statusFg = x.status==='done'?'#155724':x.status==='error'?'#721c24':'#856404';
-      return '<tr style="border-bottom:1px solid #f0f0f0"><td style="padding:8px 4px;color:#888">'+when+'</td><td>'+(x.recipe_name||'—')+'</td><td><span style="padding:2px 6px;border-radius:3px;background:'+statusBg+';color:'+statusFg+'">'+x.status+'</span></td><td>'+(x.design_id?'<a href="/design/'+x.design_id+'">#'+x.design_id+'</a>':'—')+'</td><td style="font-size:11px;color:#666;max-width:380px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(x.prompt||'').slice(0,80)+'</td></tr>';
-    }).join('') + '</tbody></table>' : '<p style="color:#999">No runs yet.</p>';
-}
-
-window.toggleRecipe = async function(id, on){
-  var rs = await get('/api/generator/recipes');
-  var r = rs.find(function(x){return x.id===id;});
-  if (!r) return;
-  r.enabled = on;
-  await post('/api/generator/recipes', r);
-  loadRecipes();
-};
-window.deleteRecipe = async function(id){
-  if (!confirm('Delete this recipe?')) return;
-  await del('/api/generator/recipes/'+id);
-  loadRecipes();
-};
-
-document.getElementById('btn-cron-start').addEventListener('click', async function(){
-  await post('/api/generator/cron', { action: 'start' });
-  setTimeout(loadCron, 600);
-});
-document.getElementById('btn-cron-stop').addEventListener('click', async function(){
-  await post('/api/generator/cron', { action: 'stop' });
-  setTimeout(loadCron, 600);
-});
-document.getElementById('btn-run-now').addEventListener('click', async function(){
-  this.disabled = true;
-  this.textContent = 'Running…';
-  var j = await post('/api/generator/run-now');
-  this.disabled = false;
-  this.textContent = 'Run tick NOW';
-  alert((j.ok?'OK':'FAIL')+'\\n\\n'+(j.output||'').slice(-800));
-  loadRuns();
-});
-document.getElementById('btn-refresh-orders').addEventListener('click', async function(){
-  this.disabled = true;
-  this.textContent = 'Fetching…';
-  var j = await post('/api/generator/refresh-orders', { limit: 50 });
-  this.disabled = false;
-  this.textContent = 'Refresh from Shopify (last 50 orders)';
-  alert((j.ok?'OK':'FAIL')+'\\n\\n'+(j.output||'').slice(-800));
-  loadPaletteStatus();
-});
-
-document.getElementById('recipe-form').addEventListener('submit', async function(e){
-  e.preventDefault();
-  var fd = new FormData(e.target);
-  var body = {
-    name: fd.get('name'),
-    source_artists: (fd.get('source_artists')||'').split(',').map(function(s){return s.trim();}).filter(Boolean),
-    style_tags: (fd.get('style_tags')||'').split(',').map(function(s){return s.trim();}).filter(Boolean),
-    color_hex_seeds: (fd.get('color_hex_seeds')||'').split(',').map(function(s){return s.trim();}).filter(Boolean),
-    prompt_template: fd.get('prompt_template') || null,
-    enabled: true
-  };
-  await post('/api/generator/recipes', body);
-  e.target.reset();
-  loadRecipes();
-});
-
-// ── ONE-STEP BATCH GENERATE ────────────────────────────────────────────
-var BATCH = { elements: new Set(), styles: new Set(), patterns: [] };
-
-async function loadBatchPickers(){
-  var [styles, elements] = await Promise.all([
-    fetch('/api/studio/styles').then(r=>r.json()),
-    fetch('/api/studio/elements').then(r=>r.json())
-  ]);
-  document.getElementById('batch-styles-picker').innerHTML = styles.map(function(s){
-    return '<button onclick="toggleBatchStyle(\''+(s.name.replace(/[\\\\\']/g,''))+'\')" data-style="'+s.name+'" style="padding:3px 9px;font-size:11px;border-radius:11px;border:1px solid #555;background:#0f0e0c;color:#aaa;cursor:pointer">'+s.name+'</button>';
-  }).join('');
-  // Group elements by category for tighter display
-  var byCat = {};
-  elements.forEach(function(e){ (byCat[e.category]=byCat[e.category]||[]).push(e); });
-  document.getElementById('batch-elements-picker').innerHTML = Object.keys(byCat).sort().map(function(c){
-    return '<div style="width:100%;font-size:9px;text-transform:uppercase;color:#666;letter-spacing:.08em;margin-top:4px">'+c+'</div>' +
-      byCat[c].map(function(e){
-        return '<button onclick="toggleBatchElement(\''+e.name+'\')" data-el="'+e.name+'" style="padding:2px 8px;font-size:11px;border-radius:9px;border:1px solid #555;background:#0f0e0c;color:#aaa;cursor:pointer;margin:1px">'+e.name+'</button>';
-      }).join('');
-  }).join('');
-  // Suggest 3 starter palettes for color hint
-  var pj = await fetch('/api/studio/suggest-palettes',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}).then(r=>r.json());
-  document.getElementById('batch-color-suggest').innerHTML =
-    'Suggested: ' + (pj.palettes||[]).slice(0,3).map(function(p){
-      return '<span style="cursor:pointer;text-decoration:underline" onclick="document.getElementById(\'batch-colors\').value=\''+(p.colors||[]).join(', ')+'\'">'+p.name+'</span>';
-    }).join(' · ');
-}
-
-window.toggleBatchElement = function(name){
-  if (BATCH.elements.has(name)) BATCH.elements.delete(name); else BATCH.elements.add(name);
-  var btn = document.querySelector('button[data-el="'+name+'"]');
-  if (btn) {
-    var on = BATCH.elements.has(name);
-    btn.style.background = on ? '#d2b15c' : '#0f0e0c';
-    btn.style.color = on ? '#1a1a1a' : '#aaa';
-    btn.style.borderColor = on ? '#d2b15c' : '#555';
-  }
-};
-window.toggleBatchStyle = function(name){
-  if (BATCH.styles.has(name)) BATCH.styles.delete(name); else BATCH.styles.add(name);
-  var btn = document.querySelector('button[data-style="'+name.replace(/'/g,"'")+'"]');
-  if (btn) {
-    var on = BATCH.styles.has(name);
-    btn.style.background = on ? '#d2b15c' : '#0f0e0c';
-    btn.style.color = on ? '#1a1a1a' : '#aaa';
-    btn.style.borderColor = on ? '#d2b15c' : '#555';
-  }
-};
-
-document.getElementById('batch-patterns').addEventListener('input', async function(e){
-  var q = e.target.value.trim();
-  if (q.length < 2) { document.getElementById('batch-patterns-results').innerHTML=''; return; }
-  var r = await fetch('/api/studio/search?q='+encodeURIComponent(q)+'&limit=24').then(x=>x.json());
-  document.getElementById('batch-patterns-results').innerHTML = r.map(function(x){
-    return '<div onclick="pickBatchPattern('+JSON.stringify(x).replace(/"/g,'&quot;').replace(/'/g,'\\\\\'')+')" style="cursor:pointer;border:1px solid #444;border-radius:4px;padding:2px;background:#0f0e0c" title="'+((x.title||'').slice(0,60))+'">'+
-      (x.thumb_url?'<img src="'+x.thumb_url+'" loading="lazy" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:3px">':'<div style="aspect-ratio:1;background:'+(x.dominant_hex||'#222')+'"></div>')+
-      '</div>';
-  }).join('');
-});
-window.pickBatchPattern = function(p){
-  if (BATCH.patterns.find(function(x){return x.id===p.id && x.source===p.source;})) return;
-  BATCH.patterns.push(p);
-  renderBatchPicked();
-};
-window.unpickBatchPattern = function(idx){
-  BATCH.patterns.splice(idx, 1);
-  renderBatchPicked();
-};
-function renderBatchPicked(){
-  document.getElementById('batch-patterns-picked').innerHTML = BATCH.patterns.map(function(p,i){
-    return '<div style="display:flex;align-items:center;gap:5px;padding:3px 8px;background:#d2b15c;color:#1a1a1a;border-radius:11px;font-size:11px"><span>'+((p.title||'pattern').slice(0,18))+'</span><span onclick="unpickBatchPattern('+i+')" style="cursor:pointer;font-weight:700">✕</span></div>';
-  }).join('');
-}
-
-document.getElementById('btn-batch-go').addEventListener('click', async function(){
-  var n = Math.max(1, Math.min(50, parseInt(document.getElementById('batch-n').value, 10) || 1));
-  var colors = document.getElementById('batch-colors').value.trim();
-  var elements = Array.from(BATCH.elements);
-  var styles = Array.from(BATCH.styles);
-  var patterns = BATCH.patterns;
-  if (!colors && !elements.length && !styles.length && !patterns.length) {
-    if (!confirm('No selections — use the default Morris/Merian recipe for all ' + n + ' designs?')) return;
-  }
-  this.disabled = true;
-  var s = document.getElementById('batch-status');
-  document.getElementById('batch-results').innerHTML = '';
-  for (var i = 1; i <= n; i++) {
-    s.textContent = 'Generating ' + i + ' / ' + n + ' …';
-    try {
-      var j = await fetch('/api/generator/batch', {
-        method:'POST', headers:{'Content-Type':'application/json'},
-        body: JSON.stringify({ colors, elements, styles,
-          patterns: patterns.map(function(p){return {source:p.source, id:p.id, title:p.title, artist:p.artist, tags:p.tags, dominant_hex:p.dominant_hex};})
-        })
-      }).then(r=>r.json());
-      if (j.ok && j.design_id) {
-        var imgUrl = '/designs/img/'+(j.filename || '');
-        var html = '<a href="/design/'+j.design_id+'" target="_blank" style="text-decoration:none;color:inherit" rel="noopener noreferrer">'+
-          '<div style="border:1px solid #444;border-radius:4px;overflow:hidden;background:#0f0e0c">'+
-            (j.filename ? '<img src="'+imgUrl+'" style="width:100%;aspect-ratio:1;object-fit:cover">' : '<div style="aspect-ratio:1;background:'+(j.dominant_hex||'#222')+'"></div>')+
-            '<div style="padding:4px 6px;font-size:10px;color:#eee">#'+j.design_id+' '+(j.dominant_hex||'')+'</div>'+
-          '</div></a>';
-        document.getElementById('batch-results').insertAdjacentHTML('beforeend', html);
-      } else {
-        document.getElementById('batch-results').insertAdjacentHTML('beforeend', '<div style="border:1px solid #c00;border-radius:4px;padding:6px;font-size:10px;color:#faa">err: '+((j.error||'?').slice(0,80))+'</div>');
-      }
-    } catch (e) {
-      document.getElementById('batch-results').insertAdjacentHTML('beforeend', '<div style="border:1px solid #c00;border-radius:4px;padding:6px;font-size:10px;color:#faa">err: '+e.message+'</div>');
-    }
-  }
-  s.textContent = 'Done. ' + n + ' generated.';
-  this.disabled = false;
-  loadRuns();
-});
-
-loadBatchPickers();
-loadCron(); loadPaletteStatus(); loadRecipes(); loadRuns();
-setInterval(loadRuns, 30000);
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ─────────────────────────────────────────────────────────────────────────
-// DESIGNER STUDIO (multi-source composition)
-// ─────────────────────────────────────────────────────────────────────────
-
-const STUDIO_UPLOAD_DIR = path.join(__dirname, 'public', 'uploads', 'studio');
-fs.mkdirSync(STUDIO_UPLOAD_DIR, { recursive: true });
-
-function studioSessionId(req) {
-  // Simple session: use cookie or generate. Persisted via JS in localStorage too.
-  const raw = (req.headers.cookie || '');
-  const m = raw.match(/wc-studio-sid=([a-z0-9]+)/i);
-  return m ? m[1] : null;
-}
-
-app.get('/api/studio/artists', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.name), '[]'::json) FROM
-      (SELECT id, name, movement, era, one_line, thumb_url FROM wallco_famous_artists) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/studio/styles', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.name), '[]'::json) FROM
-      (SELECT id, name, description, era FROM wallco_style_lexicon) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/studio/textures', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.category, t.name), '[]'::json) FROM
-      (SELECT id, name, category, image_url, thumb_url, source, dominant_hex,
-              description, prompt_phrase
-       FROM wallco_textures) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Suggest 3 color palettes — pulled from board sources + extracted from PD corpus
-app.post('/api/studio/suggest-palettes', (req, res) => {
-  const { sid } = req.body || {};
-  try {
-    const boardRaw = sid ? psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM
-      (SELECT color_hex_seeds FROM wallco_inspiration_board WHERE session_id='${String(sid).replace(/'/g,"''")}' ) t;`) : '[]';
-    const board = JSON.parse(boardRaw || '[]');
-    const seedHexes = [...new Set(board.flatMap(b => b.color_hex_seeds || []))];
-
-    // Strategy: 3 palettes — (1) board-driven, (2) complementary, (3) random PD-derived
-    function pickN(arr, n) {
-      const out = []; const pool = [...arr];
-      while (out.length < n && pool.length) {
-        out.push(pool.splice(Math.floor(Math.random()*pool.length),1)[0]);
-      }
-      return out;
-    }
-
-    // Palette A: from the board, padded if short
-    let a = pickN(seedHexes, 5);
-    if (a.length < 5) {
-      const extra = JSON.parse(psqlQuery(`SELECT COALESCE(json_agg(dominant_hex), '[]'::json)
-        FROM (SELECT dominant_hex FROM pd_source_designs
-              WHERE dominant_hex IS NOT NULL ORDER BY random() LIMIT 20) t;`));
-      a = a.concat(pickN(extra, 5 - a.length));
-    }
-
-    // Palette B: complementary — hue-shifted from palette A
-    function shiftHue(hex, deg) {
-      const r = parseInt(hex.slice(1,3),16)/255, g = parseInt(hex.slice(3,5),16)/255, b = parseInt(hex.slice(5,7),16)/255;
-      const max = Math.max(r,g,b), min = Math.min(r,g,b), d = max-min;
-      let h, l = (max+min)/2, s = d===0 ? 0 : (l>0.5 ? d/(2-max-min) : d/(max+min));
-      if (d===0) h = 0;
-      else if (max===r) h = ((g-b)/d + (g<b?6:0))*60;
-      else if (max===g) h = ((b-r)/d + 2)*60;
-      else h = ((r-g)/d + 4)*60;
-      h = (h + deg + 360) % 360;
-      // back to rgb
-      const c = (1 - Math.abs(2*l-1))*s, x = c*(1 - Math.abs(((h/60)%2)-1)), m = l - c/2;
-      let rr, gg, bb;
-      if (h < 60)      [rr,gg,bb] = [c,x,0];
-      else if (h<120)  [rr,gg,bb] = [x,c,0];
-      else if (h<180)  [rr,gg,bb] = [0,c,x];
-      else if (h<240)  [rr,gg,bb] = [0,x,c];
-      else if (h<300)  [rr,gg,bb] = [x,0,c];
-      else             [rr,gg,bb] = [c,0,x];
-      const toHex = v => Math.round((v+m)*255).toString(16).padStart(2,'0');
-      return '#' + toHex(rr) + toHex(gg) + toHex(bb);
-    }
-    const b2 = a.map(c => /^#[0-9a-f]{6}$/i.test(c) ? shiftHue(c, 180) : c);
-
-    // Palette C: from random PD design's palette JSONB
-    const pdRow = psqlQuery(`SELECT palette::text FROM pd_source_designs
-      WHERE palette IS NOT NULL AND jsonb_typeof(palette) = 'array'
-      ORDER BY random() LIMIT 1;`);
-    let c = [];
-    try {
-      const p = JSON.parse(pdRow);
-      c = (p || []).map(x => x.hex).filter(Boolean).slice(0, 5);
-    } catch {}
-    if (c.length < 5) c = pickN(JSON.parse(psqlQuery(
-      `SELECT COALESCE(json_agg(dominant_hex),'[]'::json) FROM (SELECT DISTINCT dominant_hex FROM pd_source_designs
-       WHERE dominant_hex IS NOT NULL ORDER BY random() LIMIT 20) t;`)), 5);
-
-    res.json({
-      palettes: [
-        { name: 'From your board',  colors: a, rationale: 'Built from the colors you already pinned.' },
-        { name: 'Complementary',     colors: b2, rationale: 'Hue-shifted 180° for contrast.' },
-        { name: 'Museum-pulled',     colors: c.slice(0,5), rationale: 'Pulled from a random Met / Smithsonian work.' }
-      ]
-    });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Interior-designer-style critique: combines basic image analysis + the
-// interior-designer skill's framing. Returns advisory, not blocking.
-app.post('/api/studio/interior-design-review', async (req, res) => {
-  const { design_id, prompt } = req.body || {};
-  if (!design_id) return res.status(400).json({ error: 'design_id required' });
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, prompt, local_path, dominant_hex, palette, category
-      FROM spoon_all_designs WHERE id=${parseInt(design_id,10)}) t;`);
-    const d = JSON.parse(raw || '{}');
-    if (!d.id) return res.status(404).json({ error: 'design not found' });
-
-    // Gemini vision review (mandatory per /interior-designer guidance)
-    const geminiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
-    let critique = { strengths: [], concerns: [], recommendation: '' };
-    if (geminiKey && d.local_path && fs.existsSync(d.local_path)) {
-      try {
-        const b64 = fs.readFileSync(d.local_path).toString('base64');
-        const r = await fetch(
-          `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${geminiKey}`,
-          {
-            method: 'POST',
-            headers: { 'Content-Type': 'application/json' },
-            body: JSON.stringify({
-              contents: [{
-                parts: [
-                  { inlineData: { mimeType: 'image/jpeg', data: b64 } },
-                  { text: `You are a luxury interior designer reviewing a new wallpaper pattern.
-Original prompt: "${(prompt || d.prompt || '').slice(0,400)}"
-
-Critique as a designer who places this in HIGH-END interiors (Hermès, Soho House, private hotel suites). STRICT JSON:
-{
-  "strengths": ["<1-line strength>", "<1-line strength>", "<1-line strength>"],
-  "concerns": ["<1-line concern>", "<1-line concern>"],
-  "recommendation": "<one sentence, would a designer specify this for a $1500/yard project, yes/no/maybe + why>",
-  "room_fit": ["<best room: bedroom|living|dining|powder|library|...>", "<second-best>"],
-  "lighting": "<best lighting: north light, candlelight, gallery, sunny>",
-  "scale": "<small repeat / medium / large / mural>"
-}` }
-                ]
-              }],
-              generationConfig: { temperature: 0.2, maxOutputTokens: 600, responseMimeType: 'application/json' }
-            })
-          }
-        );
-        const j = await r.json();
-        try {
-          const { logGemini } = require(require('path').join(require('os').homedir(), '.claude/skills/cost-tracker/scripts/log-gemini.js'));
-          logGemini(j, { app: 'wallco-ai', model: 'gemini-2.0-flash', note: 'design-critique ' + (d.id || '') });
-        } catch {}
-        const t = j.candidates?.[0]?.content?.parts?.[0]?.text;
-        if (t) {
-          try { critique = JSON.parse(t.replace(/^```json\s*|\s*```$/g, '')); } catch {}
-        }
-      } catch (e) { critique._err = e.message; }
-    }
-    res.json({ ok: true, design: d, critique });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── AI Interior Designer: assign metadata (title, pattern, color name, DIG#, tags)
-// Admin-only. Reads the design image, runs the interior-designer skill's
-// vision pass via Gemini, and persists ai_title / ai_pattern / ai_color_name /
-// dig_number / merged tags. The DIG number, when freshly assigned, slots into
-// the DW DIG_AI_xxxxxx namespace (no collision with vendor DIG_ SKUs because
-// those don't carry the AI_ infix). Returns the suggested values for a
-// confirm-before-save UI on /design/:id; pass `apply: true` to persist.
-app.post('/api/design/:id/ai-designer', requireAdmin, express.json({ limit: '16kb' }), async (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  const apply = req.body && req.body.apply === true;
-  try {
-    const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, prompt, local_path,
-      dominant_hex, palette, category, motifs, tags, dig_number, ai_title, ai_pattern,
-      ai_color_name FROM spoon_all_designs WHERE id=${id}) t;`);
-    const d = JSON.parse(raw || '{}');
-    if (!d.id) return res.status(404).json({ error: 'design not found' });
-    if (!d.local_path || !fs.existsSync(d.local_path)) {
-      return res.status(409).json({ error: 'image file missing on disk', local_path: d.local_path });
-    }
-
-    const geminiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
-    if (!geminiKey) return res.status(503).json({ error: 'GEMINI_API_KEY not configured' });
-
-    const b64 = fs.readFileSync(d.local_path).toString('base64');
-    const r = await fetch(
-      `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${geminiKey}`,
-      {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({
-          contents: [{
-            parts: [
-              { inlineData: { mimeType: 'image/jpeg', data: b64 } },
-              { text: `You are a luxury interior designer cataloging a new wallpaper pattern for Designer Wallcoverings.
-Look at the image and answer with STRICT JSON only. The current generation prompt is provided as a HINT only — your authority is what you actually SEE in the image, not what the prompt requested. If the image contains a motif the prompt tried to remove (e.g. leaves, plants, banana, monstera, palm leaves, palm fronds), say so — accuracy matters.
-
-Generation prompt hint: "${(d.prompt || '').slice(0, 500)}"
-
-Return STRICT JSON:
-{
-  "title": "<2-5 word evocative title, designer language, e.g. 'Iridescent Butterfly Trellis'>",
-  "pattern": "<one canonical pattern name from this list: Damask, Floral, Trellis, Botanical, Geometric, Stripe, Toile, Chinoiserie, Foulard, Abstract, Texture, Animal, Figurative, Mural, Other>",
-  "color_name": "<2-4 word color description, e.g. 'Charcoal & Jewel Tones', 'Bone White Champagne'>",
-  "tags": ["<5-10 short lowercase tags describing motifs and style>"],
-  "contains_leaves": <true|false — does the image visibly contain leaves, fronds, or banana/monstera/tropical plant content?>,
-  "leaf_kind": "<if contains_leaves true: 'banana'|'monstera'|'palm'|'tropical'|'generic'; else 'none'>",
-  "seam_visible": <true|false — does the image show visible horizontal or vertical seam lines / tile cut-marks indicating bad tiling?>,
-  "designer_notes": "<one sentence the catalog can quote>"
-}` }
-            ]
-          }],
-          generationConfig: { temperature: 0.2, maxOutputTokens: 500, responseMimeType: 'application/json' }
-        })
-      }
-    );
-    const j = await r.json();
-    try {
-      const { logGemini } = require(require('path').join(require('os').homedir(), '.claude/skills/cost-tracker/scripts/log-gemini.js'));
-      logGemini(j, { app: 'wallco-ai', model: 'gemini-2.0-flash', note: 'ai-designer ' + id });
-    } catch {}
-    const t = j.candidates?.[0]?.content?.parts?.[0]?.text;
-    let suggested = null;
-    if (t) {
-      try { suggested = JSON.parse(t.replace(/^```json\s*|\s*```$/g, '')); }
-      catch { return res.status(502).json({ error: 'gemini JSON parse failed', raw: t.slice(0, 400) }); }
-    }
-    if (!suggested) return res.status(502).json({ error: 'no candidate text from gemini' });
-
-    // Hard settlement gate — if Gemini reports leaves/banana, refuse to persist
-    if (suggested.contains_leaves === true && /^(banana|monstera|palm|tropical)$/i.test(suggested.leaf_kind || '')) {
-      return res.status(422).json({
-        error: 'SETTLEMENT_VIOLATION',
-        message: `Image contains ${suggested.leaf_kind} leaf content — settlement-blocked. Image will not be tagged or published.`,
-        suggested,
-      });
-    }
-
-    // Merge tags — preserve existing user/auto tags, add new lowercase tags from AI
-    const existingTags = Array.isArray(d.tags) ? d.tags : [];
-    const mergedTags = Array.from(new Set([
-      ...existingTags.map(s => String(s).toLowerCase().trim()).filter(Boolean),
-      ...((suggested.tags || []).map(s => String(s).toLowerCase().trim()).filter(Boolean))
-    ]));
-
-    // Assign DIG# if missing — DW digital-AI namespace
-    let digNumber = d.dig_number;
-    if (!digNumber) {
-      digNumber = `DIG_AI_${String(id).padStart(6, '0')}`;
-    }
-
-    if (apply) {
-      const tagsLit = "ARRAY[" + mergedTags.map(t => "'" + String(t).replace(/'/g,"''") + "'").join(',') + "]::text[]";
-      psqlQuery(`UPDATE spoon_all_designs SET
-        ai_title       = ${pgEsc(suggested.title || null)},
-        ai_pattern     = ${pgEsc(suggested.pattern || null)},
-        ai_color_name  = ${pgEsc(suggested.color_name || null)},
-        dig_number     = ${pgEsc(digNumber)},
-        tags           = ${mergedTags.length ? tagsLit : 'tags'},
-        ai_reviewed_at = NOW(),
-        ai_review_raw  = ${pgEsc(JSON.stringify(suggested))}::jsonb
-        WHERE id = ${id};`);
-    }
-
-    res.json({
-      ok: true,
-      id,
-      applied: apply,
-      suggested: {
-        title:      suggested.title || null,
-        pattern:    suggested.pattern || null,
-        color_name: suggested.color_name || null,
-        dig_number: digNumber,
-        tags:       mergedTags,
-        seam_visible: suggested.seam_visible === true,
-        designer_notes: suggested.designer_notes || null,
-      },
-      raw: suggested
-    });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Category insights: trends + best-sellers grid (stub today, wireable to ad-signals skill)
-app.get('/api/studio/category-insights', async (req, res) => {
-  const cat = String(req.query.cat || '').toLowerCase().trim();
-  if (!cat) return res.json({ trends: [], best_sellers: [], note: 'pass ?cat=floral|damask|...' });
-  try {
-    // Best-sellers from DW catalog (dw_unified.products) — schema is vendor/SKU/pattern, not Shopify.
-    let bestSellers = [];
-    try {
-      const safe = cat.replace(/'/g,"''");
-      const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM
-        (SELECT id,
-                COALESCE(name, pattern_name, sku) AS title,
-                vendor_id AS vendor,
-                material  AS product_type,
-                thumbnail_url AS image_url,
-                product_url   AS handle,
-                price
-         FROM products
-         WHERE LOWER(COALESCE(name,''))         LIKE '%${safe}%'
-            OR LOWER(COALESCE(pattern_name,'')) LIKE '%${safe}%'
-            OR LOWER(COALESCE(material,''))     LIKE '%${safe}%'
-            OR LOWER(COALESCE(color_family,'')) LIKE '%${safe}%'
-         LIMIT 12) t;`);
-      bestSellers = JSON.parse(raw || '[]');
-    } catch (e) {
-      // products table may not exist on prod; fall back to recent PD images in this category
-      const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM
-        (SELECT id, title, creator AS vendor, source AS product_type,
-                image_url, source_url AS handle, NULL AS price
-         FROM pd_source_designs
-         WHERE LOWER(COALESCE(category,'')) LIKE '%${cat.replace(/'/g,"''")}%'
-            OR LOWER(COALESCE(title,''))    LIKE '%${cat.replace(/'/g,"''")}%'
-         ORDER BY ingested_at DESC LIMIT 12) t;`);
-      bestSellers = JSON.parse(raw || '[]');
-    }
-
-    // Trends — return canonical search terms + Google Trends links
-    const trends = [
-      { term: `${cat} wallpaper 2026`, link: `https://trends.google.com/trends/explore?q=${encodeURIComponent(cat+' wallpaper')}` },
-      { term: `${cat} pattern interior`, link: `https://trends.google.com/trends/explore?q=${encodeURIComponent(cat+' pattern interior')}` },
-      { term: `${cat} maximalist`, link: `https://trends.google.com/trends/explore?q=${encodeURIComponent(cat+' maximalist')}` },
-    ];
-
-    // Ad signal placeholder (real wiring to ad-signals skill is a follow-up)
-    res.json({
-      category: cat,
-      trends,
-      best_sellers: bestSellers,
-      ad_signals_note: 'Live Google Ads / Meta pixel data via the ad-signals skill — wireable on demand.'
-    });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/studio/elements', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.category, t.name), '[]'::json) FROM
-      (SELECT id, category, name, prompt_phrase FROM wallco_element_lexicon) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Search public-domain corpus + Spoonflower-pulled + wallco generated
-app.get('/api/studio/search', (req, res) => {
-  const q = String(req.query.q || '').trim().toLowerCase().replace(/'/g, "''");
-  const limit = Math.min(parseInt(req.query.limit || '60', 10), 200);
-  if (!q) return res.json([]);
-  try {
-    // Build a unified result via three subqueries
-    const sql = `
-      WITH pd AS (
-        SELECT 'pd' AS source, id::text AS id, title, creator AS artist,
-               source AS subsource, image_url, local_path, dominant_hex,
-               category, tags
-        FROM pd_source_designs
-        WHERE LOWER(COALESCE(title,'')) LIKE '%${q}%'
-           OR LOWER(COALESCE(creator,'')) LIKE '%${q}%'
-           OR LOWER(COALESCE(category,'')) LIKE '%${q}%'
-           OR EXISTS (SELECT 1 FROM unnest(COALESCE(tags,ARRAY[]::text[])) AS t WHERE LOWER(t) LIKE '%${q}%')
-        LIMIT ${limit}
-      ),
-      sp AS (
-        SELECT 'spoonflower' AS source, sf_design_id AS id, title, NULL AS artist,
-               'spoonflower' AS subsource, design_url AS image_url, local_path,
-               NULL AS dominant_hex, NULL AS category, NULL::text[] AS tags
-        FROM spoonflower_pulled_designs
-        WHERE LOWER(COALESCE(title,'')) LIKE '%${q}%'
-        LIMIT 30
-      ),
-      gen AS (
-        SELECT 'wallco' AS source, id::text AS id, prompt AS title, generator AS artist,
-               'wallco' AS subsource, image_url, local_path, dominant_hex,
-               category, tags
-        FROM spoon_all_designs
-        WHERE LOWER(COALESCE(prompt,'')) LIKE '%${q}%'
-           OR LOWER(COALESCE(category,'')) LIKE '%${q}%'
-        LIMIT 30
-      )
-      SELECT COALESCE(json_agg(t), '[]'::json) FROM
-        (SELECT * FROM pd UNION ALL SELECT * FROM sp UNION ALL SELECT * FROM gen) t;`;
-    const raw = psqlQuery(sql);
-    const items = JSON.parse(raw || '[]').map(it => {
-      // Map local_path → /img URL when applicable
-      let url = it.image_url || '';
-      if (it.local_path && it.local_path.startsWith(path.join(__dirname, 'data') + path.sep)) {
-        url = '/img/' + it.local_path.replace(path.join(__dirname, 'data') + path.sep, '');
-      } else if (it.local_path && it.local_path.includes('/generated/')) {
-        url = '/designs/img/' + path.basename(it.local_path);
-      }
-      return { ...it, thumb_url: url };
-    });
-    res.json(items);
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Add /img static for PD source images (loaded from data/)
-app.use('/img', express.static(path.join(__dirname, 'data'), { maxAge: '7d' }));
-
-// Image upload — accepts multipart OR base64
-app.post('/api/studio/upload', express.raw({ type: 'application/octet-stream', limit: '20mb' }), async (req, res) => {
-  try {
-    // SECURITY (path traversal fix 2026-05-12): sid is user-controlled and was being
-    // joined directly into a filesystem path. Malicious sid like "../../etc/passwd_"
-    // would escape STUDIO_UPLOAD_DIR. Also: filename's old regex allowed dots so ".."
-    // slipped through. Plus the sips + python execSync had shell-injectable interpolation.
-    // Fix: strict allowlist on sid + filename; verify resolved path is inside the upload
-    // dir; use execFileSync (no shell) for the palette pipeline.
-    const rawSid = String(req.query.sid || '').slice(0, 40);
-    const sid = /^[a-z0-9-]{1,40}$/i.test(rawSid) ? rawSid : ('anon-' + Date.now());
-    const rawName = String(req.query.name || `upload-${Date.now()}.jpg`);
-    // Strip every char that isn't [a-z0-9._-], collapse "..", trim leading dots/slashes
-    const filename = rawName
-      .replace(/[^a-z0-9._-]/gi, '')
-      .replace(/\.\.+/g, '.')
-      .replace(/^[.\-]+/, '') || `upload-${Date.now()}.jpg`;
-    const target = path.join(STUDIO_UPLOAD_DIR, `${sid}_${filename}`);
-    // Canonicalize + verify containment (defense in depth)
-    const safeDir = path.resolve(STUDIO_UPLOAD_DIR);
-    const safeTarget = path.resolve(target);
-    if (!safeTarget.startsWith(safeDir + path.sep)) {
-      return res.status(400).json({ error: 'invalid path' });
-    }
-    fs.writeFileSync(safeTarget, req.body);
-    // Quick palette extract via sips + Python — execFileSync avoids shell interpolation
-    let domHex = null;
-    try {
-      const small = safeTarget + '.32.png';
-      execFileSync('sips', ['-Z', '32', safeTarget, '--out', small], { stdio: 'ignore' });
-      // Read file path from argv[1] so the path never crosses a shell or python source-string boundary
-      const pyScript = `
-import sys
-from PIL import Image
-img = Image.open(sys.argv[1]).convert("RGB")
-pix = list(img.getdata())
-n = len(pix)
-r = sum(p[0] for p in pix)//n
-g = sum(p[1] for p in pix)//n
-b = sum(p[2] for p in pix)//n
-print(f"#{r:02x}{g:02x}{b:02x}")
-`;
-      domHex = execFileSync('python3', ['-c', pyScript, small], { encoding: 'utf8' }).trim();
-      fs.unlinkSync(small);
-    } catch {}
-
-    // AI style + element analysis via Gemini (mandatory per dw standing rule)
-    let ai = { style: null, colors: [], elements: [], summary: null };
-    const geminiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
-    if (geminiKey) {
-      try {
-        const b64 = fs.readFileSync(target).toString('base64');
-        const r = await fetch(
-          `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${geminiKey}`,
-          {
-            method: 'POST',
-            headers: { 'Content-Type': 'application/json' },
-            body: JSON.stringify({
-              contents: [{
-                parts: [
-                  { inlineData: { mimeType: 'image/jpeg', data: b64 } },
-                  { text: `Analyze this design. Return STRICT JSON:
-{
-  "style": "<one of: Baroque, Rococo, Neoclassical, Art Nouveau, Art Deco, Arts and Crafts, Mid-century Modern, Bauhaus, Memphis, Maximalist, Minimalist, Chinoiserie, Toile, Damask, Ikat, Suzani, Moroccan, Japonisme, Folk Scandi, Tropical, Other>",
-  "colors": ["#hex","#hex","#hex"],
-  "elements": ["<element keywords like bird, peony, monogram, trellis, etc — max 8>"],
-  "summary": "<one sentence describing the design>"
-}` }
-                ]
-              }],
-              generationConfig: { temperature: 0.1, maxOutputTokens: 400, responseMimeType: 'application/json' }
-            })
-          }
-        );
-        const j = await r.json();
-        try {
-          const { logGemini } = require(require('path').join(require('os').homedir(), '.claude/skills/cost-tracker/scripts/log-gemini.js'));
-          logGemini(j, { app: 'wallco-ai', model: 'gemini-2.0-flash', note: 'style-element-analysis' });
-        } catch {}
-        const t = j.candidates && j.candidates[0] && j.candidates[0].content
-                  && j.candidates[0].content.parts && j.candidates[0].content.parts[0]
-                  && j.candidates[0].content.parts[0].text;
-        if (t) {
-          const cleaned = t.replace(/^```json\s*|\s*```$/g, '');
-          try { ai = { ...ai, ...JSON.parse(cleaned) }; } catch {}
-        }
-      } catch (e) { console.error('gemini upload error:', e.message); }
-    }
-
-    let stat = fs.statSync(target);
-    const row = psqlQuery(`INSERT INTO wallco_user_uploads
-      (session_id, filename, local_path, ai_style, ai_colors, ai_elements, ai_summary, bytes)
-      VALUES (${sid ? "'"+String(sid).replace(/'/g,"''")+"'" : 'NULL'},
-              '${filename.replace(/'/g,"''")}',
-              '${target.replace(/'/g,"''")}',
-              ${ai.style ? "'"+ai.style.replace(/'/g,"''")+"'" : 'NULL'},
-              ARRAY[${(ai.colors||[]).map(c=>"'"+String(c).replace(/'/g,"''")+"'").join(',') || ''}]::text[],
-              ARRAY[${(ai.elements||[]).map(c=>"'"+String(c).replace(/'/g,"''")+"'").join(',') || ''}]::text[],
-              ${ai.summary ? "'"+ai.summary.replace(/'/g,"''")+"'" : 'NULL'},
-              ${stat.size})
-      RETURNING id;`);
-
-    res.json({
-      ok: true,
-      id: parseInt(row, 10),
-      url: `/uploads/studio/${sid}_${filename}`,
-      dominant_hex: domHex,
-      ai_style: ai.style,
-      ai_colors: ai.colors,
-      ai_elements: ai.elements,
-      ai_summary: ai.summary,
-    });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// Save/get the 6-slot inspiration board
-app.get('/api/studio/board', (req, res) => {
-  const sid = req.query.sid;
-  if (!sid) return res.json([]);
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.slot_idx), '[]'::json) FROM
-      (SELECT * FROM wallco_inspiration_board WHERE session_id='${String(sid).replace(/'/g,"''")}' ) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.post('/api/studio/board/slot', (req, res) => {
-  const { sid, slot_idx, source_type, source_id, image_url, thumb_url, title,
-          artist, style_tags = [], color_hex_seeds = [], selected_elements = [] } = req.body || {};
-  if (!sid || slot_idx == null) return res.status(400).json({ error: 'sid + slot_idx required' });
-  const arr = a => `ARRAY[${(a||[]).map(v=>"'"+String(v).replace(/'/g,"''")+"'").join(',') || ''}]::text[]`;
-  const s = v => v == null ? 'NULL' : "'"+String(v).replace(/'/g,"''")+"'";
-  try {
-    psqlQuery(`INSERT INTO wallco_inspiration_board
-      (session_id, slot_idx, source_type, source_id, image_url, thumb_url, title,
-       artist, style_tags, color_hex_seeds, selected_elements)
-      VALUES (${s(sid)}, ${parseInt(slot_idx,10)}, ${s(source_type)}, ${s(source_id)},
-              ${s(image_url)}, ${s(thumb_url)}, ${s(title)}, ${s(artist)},
-              ${arr(style_tags)}, ${arr(color_hex_seeds)}, ${arr(selected_elements)})
-      ON CONFLICT (session_id, slot_idx) DO UPDATE SET
-        source_type=EXCLUDED.source_type, source_id=EXCLUDED.source_id,
-        image_url=EXCLUDED.image_url, thumb_url=EXCLUDED.thumb_url, title=EXCLUDED.title,
-        artist=EXCLUDED.artist, style_tags=EXCLUDED.style_tags,
-        color_hex_seeds=EXCLUDED.color_hex_seeds, selected_elements=EXCLUDED.selected_elements;`);
-    res.json({ ok: true });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.delete('/api/studio/board/slot/:slot', (req, res) => {
-  const sid = req.query.sid;
-  if (!sid) return res.status(400).json({ error: 'sid required' });
-  try {
-    psqlQuery(`DELETE FROM wallco_inspiration_board
-      WHERE session_id='${String(sid).replace(/'/g,"''")}' AND slot_idx=${parseInt(req.params.slot,10)};`);
-    res.json({ ok: true });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// Compose: build a prompt from the board + extra elements, fire SDXL
-app.post('/api/studio/compose', (req, res) => {
-  const { sid, extra_elements = [], extra_text = '' } = req.body || {};
-  if (!sid) return res.status(400).json({ error: 'sid required' });
-  try {
-    const boardRaw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.slot_idx), '[]'::json) FROM
-      (SELECT * FROM wallco_inspiration_board WHERE session_id='${String(sid).replace(/'/g,"''")}' ) t;`);
-    const board = JSON.parse(boardRaw || '[]');
-    if (!board.length) return res.status(400).json({ error: 'inspiration board is empty — add at least 1 slot' });
-
-    const artists = [...new Set(board.flatMap(b => [b.artist].filter(Boolean)))];
-    const styles  = [...new Set(board.flatMap(b => (b.style_tags || []).concat(b.selected_elements || []).filter(Boolean)))];
-    const colors  = [...new Set(board.flatMap(b => b.color_hex_seeds || []))].slice(0, 6);
-    const elems   = [...new Set((board.flatMap(b => b.selected_elements || [])).concat(extra_elements))];
-
-    const promptParts = [
-      'A seamless wallpaper pattern',
-      artists.length ? `in the visual lineage of ${artists.slice(0,3).join(' and ')}` : '',
-      styles.length ? `with ${styles.slice(0,4).join(' / ')} influence` : '',
-      elems.length ? `featuring ${elems.slice(0,6).join(', ')}` : '',
-      colors.length ? `palette ${colors.join(', ')}` : '',
-      extra_text || '',
-      'high detail, museum-quality, 24x24 inch tileable, no text, no signature.'
-    ].filter(Boolean);
-    const prompt = promptParts.join(', ').replace(/\s+/g, ' ').trim();
-
-    // Log the run as recipe-less (recipe_id=NULL)
-    const runId = psqlQuery(`INSERT INTO wallco_generator_runs
-      (recipe_id, prompt, palette_seed_ids, status)
-      VALUES (NULL, '${prompt.replace(/'/g,"''")}', '{}'::bigint[], 'running')
-      RETURNING id;`);
-
-    // Spawn the generator script directly
-    const r = spawnSync('node', [
-      path.join(__dirname, 'scripts', 'generate_designs.js'),
-      '--n', '1', '--kind', 'seamless_tile',
-      '--category', (styles[0] || 'mixed').toLowerCase(),
-      '--prompts', prompt,
-    ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
-         encoding: 'utf8', timeout: 120000 });
-
-    let designId = null;
-    try {
-      designId = parseInt(psqlQuery(`SELECT id FROM spoon_all_designs ORDER BY id DESC LIMIT 1;`), 10);
-    } catch {}
-
-    const ok = r.status === 0;
-    psqlQuery(`UPDATE wallco_generator_runs SET
-      status='${ok ? 'done' : 'error'}',
-      design_id=${designId || 'NULL'},
-      error=${ok ? 'NULL' : "'"+((r.stderr||r.stdout||'').slice(-400).replace(/'/g,"''"))+"'"},
-      finished_at=now()
-      WHERE id=${runId};`);
-
-    // Look up the freshly-inserted design's image so the /studio payoff frame
-    // can render the final pattern (R0a — Steve ask 2026-05-12 late).
-    let imageUrl = null;
-    let imageFilename = null;
-    if (ok && designId) {
-      try {
-        const lp = psqlQuery(`SELECT local_path FROM spoon_all_designs WHERE id=${designId} LIMIT 1;`);
-        if (lp) {
-          imageFilename = path.basename(lp);
-          imageUrl = '/designs/img/' + imageFilename;
-        }
-      } catch {}
-    }
-    // R0c — persist every render to studio_renders so the user can revisit/delete.
-    let renderId = null;
-    if (ok && designId) {
-      try {
-        const safePrompt = prompt.replace(/'/g, "''");
-        const safeSid = String(sid).replace(/'/g, "''");
-        const out = psqlQuery(`INSERT INTO studio_renders
-          (session_uuid, design_id, prompt) VALUES
-          ('${safeSid}', ${designId}, '${safePrompt}')
-          RETURNING id;`);
-        renderId = parseInt(out, 10) || null;
-      } catch (e) { console.error('[studio_renders insert]', e.message); }
-    }
-    res.json({ ok, prompt, design_id: designId, run_id: parseInt(runId, 10),
-               render_id: renderId,
-               image_url: imageUrl, image_filename: imageFilename,
-               error: ok ? null : (r.stderr || r.stdout || '').slice(-600) });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── R0c/R0d studio_renders endpoints ─────────────────────────────────
-// GET /api/studio-renders?sid=X   — list a user's renders (newest first, live by default)
-// POST /api/studio-renders/:designId/delete  — soft-delete (matches session_uuid)
-// POST /api/studio-renders/:designId/restore — restore within 7d (any matching session)
-// POST /api/studio-renders/:designId/link-room — attach a room_id once the room renders
-app.get('/api/studio-renders', (req, res) => {
-  const sid = req.query.sid || req.headers['x-studio-sid'];
-  if (!sid) return res.status(400).json({ ok: false, error: 'sid required' });
-  const includeDeleted = req.query.include_deleted === '1';
-  const safeSid = String(sid).replace(/'/g, "''");
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.created_at DESC), '[]'::json) FROM (
-      SELECT sr.id AS render_id, sr.design_id, sr.room_id, sr.prompt,
-             sr.created_at, sr.deleted_at,
-             d.local_path AS pattern_path,
-             d.dominant_hex, d.category,
-             r.image_path AS room_path, r.room_type
-      FROM studio_renders sr
-      LEFT JOIN spoon_all_designs d ON d.id = sr.design_id
-      LEFT JOIN wallco_rooms       r ON r.id = sr.room_id
-      WHERE sr.session_uuid = '${safeSid}'
-        ${includeDeleted ? '' : 'AND sr.deleted_at IS NULL'}
-    ) t;`);
-    const rows = JSON.parse(raw || '[]');
-    const renders = rows.map(r => ({
-      render_id: r.render_id,
-      design_id: r.design_id,
-      room_id:   r.room_id,
-      prompt:    r.prompt,
-      category:  r.category,
-      dominant_hex: r.dominant_hex,
-      image_url: r.pattern_path ? '/designs/img/' + path.basename(r.pattern_path) : null,
-      room_image_url: r.room_path || null,
-      room_type: r.room_type || null,
-      created_at: r.created_at,
-      deleted_at: r.deleted_at
-    }));
-    res.json({ ok: true, renders });
-  } catch (e) { res.status(500).json({ ok: false, error: e.message }); }
-});
-
-app.post('/api/studio-renders/:designId/delete', (req, res) => {
-  const designId = parseInt(req.params.designId, 10);
-  const sid = (req.body && req.body.sid) || req.headers['x-studio-sid'];
-  if (!designId) return res.status(400).json({ ok: false, error: 'designId required' });
-  if (!sid)      return res.status(400).json({ ok: false, error: 'sid required (owner check)' });
-  const safeSid = String(sid).replace(/'/g, "''");
-  try {
-    const out = psqlQuery(`UPDATE studio_renders
-      SET deleted_at = NOW()
-      WHERE design_id = ${designId}
-        AND session_uuid = '${safeSid}'
-        AND deleted_at IS NULL
-      RETURNING id;`);
-    if (!out) return res.status(404).json({ ok: false, error: 'render not found or already deleted (or wrong owner)' });
-    res.json({ ok: true, render_id: parseInt(out, 10), grace_days: 7 });
-  } catch (e) { res.status(500).json({ ok: false, error: e.message }); }
-});
-
-app.post('/api/studio-renders/:designId/restore', (req, res) => {
-  const designId = parseInt(req.params.designId, 10);
-  const sid = (req.body && req.body.sid) || req.headers['x-studio-sid'];
-  if (!designId) return res.status(400).json({ ok: false, error: 'designId required' });
-  if (!sid)      return res.status(400).json({ ok: false, error: 'sid required' });
-  const safeSid = String(sid).replace(/'/g, "''");
-  try {
-    const out = psqlQuery(`UPDATE studio_renders
-      SET deleted_at = NULL
-      WHERE design_id = ${designId}
-        AND session_uuid = '${safeSid}'
-        AND deleted_at IS NOT NULL
-        AND deleted_at > NOW() - INTERVAL '7 days'
-      RETURNING id;`);
-    if (!out) return res.status(404).json({ ok: false, error: 'no soft-deleted render found within 7d for this session' });
-    res.json({ ok: true, render_id: parseInt(out, 10) });
-  } catch (e) { res.status(500).json({ ok: false, error: e.message }); }
-});
-
-app.post('/api/studio-renders/:designId/link-room', (req, res) => {
-  const designId = parseInt(req.params.designId, 10);
-  const roomId = parseInt((req.body && req.body.room_id) || '', 10);
-  const sid = (req.body && req.body.sid) || req.headers['x-studio-sid'];
-  if (!designId || !roomId || !sid) {
-    return res.status(400).json({ ok: false, error: 'designId, room_id, sid all required' });
-  }
-  const safeSid = String(sid).replace(/'/g, "''");
-  try {
-    const out = psqlQuery(`UPDATE studio_renders
-      SET room_id = ${roomId}
-      WHERE design_id = ${designId}
-        AND session_uuid = '${safeSid}'
-        AND deleted_at IS NULL
-      RETURNING id;`);
-    if (!out) return res.status(404).json({ ok: false, error: 'render not found for this session' });
-    res.json({ ok: true, render_id: parseInt(out, 10), room_id: roomId });
-  } catch (e) { res.status(500).json({ ok: false, error: e.message }); }
-});
-
-// /studio HTML page
-app.get('/studio', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Designer Studio — wallco.ai',
-    description: 'Compose a new wallpaper design from artists, styles, and your own inspiration images',
-    canonical: 'https://wallco.ai/studio'
-  })}
-<style>
-  /* Studio-scoped overflow hygiene — forces single-col stack on phones by
-     preventing the body from growing past viewport. Without this, any
-     fixed/absolute element (trade-nav, FAB) silently widens the body and
-     auto-fit grids miscalculate column count. */
-  html, body { overflow-x: clip; max-width: 100vw; }
-  @media (max-width: 700px) {
-    .studio-3col { grid-template-columns: 1fr !important; }
-    /* Trade-nav clip fix — pull Sign-In pill inside viewport on phones */
-    #trade-nav { right: 56px !important; }
-  }
-</style>
-<body>
-${htmlHeader('')}
-<main style="padding:88px 18px 24px;max-width:1500px;margin:0 auto;overflow-x:clip;box-sizing:border-box">
-  <h1 class="studio-display-title" style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:clamp(30px,6.4vw,48px);letter-spacing:-0.01em;margin:6px 0 4px;line-height:1.05">Designer Studio</h1>
-  <p style="color:#666;margin:0 0 22px;font-size:14px">Pick artists, styles, elements, or upload your own. Add up to 6 sources to the board → Compose generates a new pattern.</p>
-
-  <!-- Top: 3-column reference panels — auto-stacks below ~640px so chips never clip mid-word on mobile -->
-  <div class="studio-3col" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:14px;margin-bottom:22px">
-
-    <!-- Artists -->
-    <div style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fafafa;max-height:320px;overflow-y:auto;min-width:0;overflow-wrap:anywhere">
-      <h3 style="margin:0 0 8px;font-size:14px">Famous Artists</h3>
-      <div id="artists-list" style="font-size:12px"></div>
-    </div>
-
-    <!-- Styles -->
-    <div style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fafafa;max-height:320px;overflow-y:auto;min-width:0;overflow-wrap:anywhere">
-      <h3 style="margin:0 0 8px;font-size:14px">Styles</h3>
-      <div id="styles-list" style="font-size:12px"></div>
-    </div>
-
-    <!-- Elements -->
-    <div style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fafafa;max-height:320px;overflow-y:auto;min-width:0;overflow-wrap:anywhere">
-      <h3 style="margin:0 0 8px;font-size:14px">Elements <span style="font-size:10px;color:#999;font-weight:400">(animals, monograms, …)</span></h3>
-      <div id="elements-list" style="font-size:12px"></div>
-    </div>
-  </div>
-
-  <!-- Textures grid (drag-droppable as bg or fg) -->
-  <div style="margin-bottom:18px;padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff">
-    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
-      <h3 style="margin:0;font-size:14px">Textures <span style="font-size:11px;color:#999;font-weight:400">(drag onto an empty slot, or click to add as background)</span></h3>
-      <span style="font-size:11px;color:#666">Foreground opacity <input type="range" id="overlay-opacity" min="0" max="100" value="40" style="vertical-align:middle"> <span id="opacity-val">40%</span></span>
-    </div>
-    <div id="textures-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(86px,1fr));gap:6px;max-height:260px;overflow-y:auto"></div>
-  </div>
-
-  <!-- Search bar -->
-  <div style="margin-bottom:18px;display:flex;gap:10px;align-items:center">
-    <input id="search-q" type="text" placeholder="Search public-domain images (e.g. peacock, baroque, Morris)" style="flex:1;padding:10px 14px;font-size:14px;border:1px solid #ccc;border-radius:6px">
-    <button id="btn-search" class="btn-primary">Search</button>
-    <label class="btn-outline" style="cursor:pointer;margin:0">
-      Upload image
-      <input id="upload-input" type="file" accept="image/*" hidden>
-    </label>
-  </div>
-  <div id="search-results" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-bottom:24px;max-height:340px;overflow-y:auto;border:1px solid #eee;border-radius:6px;padding:10px;background:#fff"></div>
-
-  <!-- 6-slot board -->
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:20px;margin:24px 0 10px">Inspiration board (6 slots)</h2>
-  <div id="board" style="display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:22px"></div>
-
-  <!-- 3-palette suggestions -->
-  <div style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff;margin-bottom:22px">
-    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
-      <h3 style="margin:0;font-size:14px">3 Suggested palettes</h3>
-      <button id="btn-suggest" class="btn-outline" style="font-size:12px;padding:4px 10px">Refresh suggestions</button>
-    </div>
-    <div id="palette-suggestions" style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px"></div>
-  </div>
-
-  <!-- Extra elements + compose -->
-  <div style="padding:18px;border:1px solid #ddd;border-radius:8px;background:#fafafa;margin-bottom:22px">
-    <h3 style="margin:0 0 8px;font-size:14px">Add more elements to the prompt</h3>
-    <div id="extra-elements-pills" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px"></div>
-    <textarea id="extra-text" placeholder="Extra prompt text (optional)" rows="2" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;font-size:13px;font-family:inherit"></textarea>
-    <div style="display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap">
-      <button id="btn-compose" class="btn-primary">Compose new design</button>
-      <label style="font-size:12px;color:#555;display:flex;align-items:center;gap:6px"><input type="checkbox" id="auto-review" checked> auto-review with interior-designer</label>
-    </div>
-    <div id="compose-result" style="margin-top:14px"></div>
-    <div id="design-review" style="margin-top:14px"></div>
-  </div>
-
-  <!-- R0c/R0d — Personal gallery of saved renders -->
-  <div id="my-renders-section" style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff;margin-bottom:24px;display:none">
-    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
-      <h3 style="margin:0;font-size:14px">Your saved renders <span id="my-renders-count" style="font-size:11px;color:#999;font-weight:400"></span></h3>
-      <label style="font-size:11px;color:#666;cursor:pointer"><input type="checkbox" id="show-deleted-toggle"> show recently deleted (last 7d)</label>
-    </div>
-    <div id="my-renders-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px"></div>
-  </div>
-
-  <!-- R0c — personal renders gallery (this browser's session only) -->
-  <div id="studio-gallery" style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff;margin-bottom:24px;display:none">
-    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
-      <h3 style="margin:0;font-size:14px">Your renders <span style="font-size:11px;color:#999;font-weight:400" id="studio-gallery-count"></span></h3>
-      <span style="font-size:11px;color:#999">soft-deletes recoverable for 7 days</span>
-    </div>
-    <div id="studio-gallery-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px"></div>
-  </div>
-
-  <!-- Category insights -->
-  <div style="padding:14px;border:1px solid #ddd;border-radius:8px;background:#fff;margin-bottom:24px">
-    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
-      <h3 style="margin:0;font-size:14px">Category insights — trends, best-sellers, ad signals</h3>
-      <span style="font-size:11px;color:#666">Category <select id="cat-select" style="font:inherit;padding:4px 6px;border:1px solid #ccc;border-radius:3px">
-        <option>floral</option><option>damask</option><option>geometric</option><option>botanical</option><option>baroque</option><option>art deco</option><option>chinoiserie</option><option>toile</option><option>maximalist</option><option>minimalist</option>
-      </select></span>
-    </div>
-    <div id="insights-body"></div>
-  </div>
-</main>
-${FOOTER}
-
-<script>
-var SID = localStorage.getItem('wc-studio-sid') || (Math.random().toString(36).slice(2)+Date.now().toString(36));
-localStorage.setItem('wc-studio-sid', SID);
-document.cookie = 'wc-studio-sid='+SID+'; path=/; max-age=2592000';
-
-async function get(u){ return (await fetch(u)).json(); }
-async function post(u,b){ return (await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})})).json(); }
-async function del(u){ return (await fetch(u,{method:'DELETE'})).json(); }
-
-var EXTRA = new Set();
-
-function pill(text, on, onclick){
-  return '<button onclick="'+onclick+'" style="padding:3px 9px;font-size:11px;border-radius:11px;border:1px solid '+(on?'#1a1a1a':'#ccc')+';background:'+(on?'#1a1a1a':'#fff')+';color:'+(on?'#fff':'#333')+';cursor:pointer;margin:2px 1px">'+text+'</button>';
-}
-
-async function loadRefs(){
-  var [artists, styles, elements] = await Promise.all([
-    get('/api/studio/artists'), get('/api/studio/styles'), get('/api/studio/elements')
-  ]);
-  document.getElementById('artists-list').innerHTML = artists.map(function(a){
-    return '<div style="padding:5px 6px;border-bottom:1px solid #eee;cursor:pointer" onclick="addArtist('+JSON.stringify(a).replace(/"/g,'&quot;')+')"><b>'+a.name+'</b> <span style="color:#999">— '+(a.movement||'')+' '+(a.era||'')+'</span><br><span style="font-size:10px;color:#777">'+(a.one_line||'')+'</span></div>';
-  }).join('');
-  document.getElementById('styles-list').innerHTML = styles.map(function(s){
-    return '<div style="padding:5px 6px;border-bottom:1px solid #eee;cursor:pointer" onclick="addStyle('+JSON.stringify(s).replace(/"/g,'&quot;')+')"><b>'+s.name+'</b> <span style="color:#999">'+(s.era||'')+'</span><br><span style="font-size:10px;color:#777">'+(s.description||'')+'</span></div>';
-  }).join('');
-  // Elements grouped by category, with pills
-  var byCat = {};
-  elements.forEach(function(e){ (byCat[e.category]=byCat[e.category]||[]).push(e); });
-  window._ELEMENTS_BY_CAT = byCat;
-  renderElementsList();
-  renderExtra();
-}
-
-// Renders the elements-list pills with active/inactive styling based on EXTRA.
-// Pulled out so toggleExtra can re-render to show visual selected state.
-function renderElementsList() {
-  var byCat = window._ELEMENTS_BY_CAT || {};
-  document.getElementById('elements-list').innerHTML = Object.keys(byCat).sort().map(function(c){
-    return '<div style="margin-bottom:6px"><div class="eyebrow" style="margin-bottom:3px">'+c+'</div><div style="display:flex;flex-wrap:wrap">'+byCat[c].map(function(e){
-      var on = EXTRA.has(e.name);
-      var bg = on ? '#1a1a1a' : '#fff';
-      var fg = on ? '#fff' : '#333';
-      var bd = on ? '#1a1a1a' : '#ccc';
-      return '<button data-extra="'+e.name+'" onclick="toggleExtra(\\''+e.name+'\\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid '+bd+';background:'+bg+';color:'+fg+';cursor:pointer;margin:1px;transition:all .12s">'+e.name+'</button>';
-    }).join('')+'</div></div>';
-  }).join('');
-}
-
-function renderExtra() {
-  var html = Array.from(EXTRA).map(function(e){
-    return pill(e + ' ✕', true, "toggleExtra('"+e+"')");
-  }).join('') || '<span style="color:#999;font-size:12px">No extra elements selected yet — click pills above.</span>';
-  document.getElementById('extra-elements-pills').innerHTML = html;
-}
-
-window.toggleExtra = function(name){
-  if (EXTRA.has(name)) EXTRA.delete(name); else EXTRA.add(name);
-  renderExtra();
-  renderElementsList(); // re-render so the pill that was just clicked flips state
-};
-
-window.addArtist = function(a){
-  addToBoardNextSlot({source_type:'artist', source_id:String(a.id), title:a.name, artist:a.name, style_tags:[a.movement].filter(Boolean), color_hex_seeds:[], selected_elements:[]});
-};
-window.addStyle = function(s){
-  addToBoardNextSlot({source_type:'style', source_id:String(s.id), title:s.name, artist:null, style_tags:[s.name], color_hex_seeds:[], selected_elements:[]});
-};
-
-var BOARD = [];
-async function loadBoard(){
-  BOARD = await get('/api/studio/board?sid='+encodeURIComponent(SID));
-  renderBoard();
-}
-function nextEmptySlot(){
-  for (var i=0;i<6;i++){ if (!BOARD.find(function(b){return b.slot_idx===i;})) return i; }
-  return null;
-}
-async function addToBoardNextSlot(payload){
-  var idx = nextEmptySlot();
-  if (idx == null) { alert('Board is full (6 slots). Remove one first.'); return; }
-  await post('/api/studio/board/slot', Object.assign({sid:SID, slot_idx:idx}, payload));
-  loadBoard();
-}
-window.removeSlot = async function(slot){
-  await del('/api/studio/board/slot/'+slot+'?sid='+encodeURIComponent(SID));
-  loadBoard();
-};
-function renderBoard(){
-  var html = '';
-  for (var i=0;i<6;i++){
-    var b = BOARD.find(function(x){return x.slot_idx===i;});
-    if (b) {
-      var thumb = b.thumb_url || b.image_url || '';
-      var thumbHtml = thumb ? '<img src="'+thumb+'" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:4px">' :
-        '<div style="aspect-ratio:1;background:#'+((b.color_hex_seeds||[])[0]?(b.color_hex_seeds[0].replace('#','')):'2a2a2a')+';display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;border-radius:4px;text-align:center;padding:8px">'+(b.title||b.artist||'slot '+(i+1))+'</div>';
-      var tags = (b.style_tags||[]).concat(b.selected_elements||[]).slice(0,3).map(function(t){return '<span style="font-size:9px;background:#eee;padding:1px 5px;border-radius:7px;margin-right:2px">'+t+'</span>';}).join('');
-      html += '<div style="position:relative;border:1px solid #ddd;border-radius:6px;padding:6px;background:#fff">'+
-        thumbHtml +
-        '<div style="font-size:11px;margin-top:6px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+(b.title||'')+'">'+(b.title||'(no title)').slice(0,30)+'</div>'+
-        '<div style="font-size:10px;color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(b.artist||b.source_type)+'</div>'+
-        '<div style="margin-top:4px">'+tags+'</div>'+
-        '<button onclick="removeSlot('+i+')" style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);color:#fff;border:0;border-radius:50%;width:20px;height:20px;font-size:11px;cursor:pointer;line-height:1;padding:0">✕</button>'+
-        '</div>';
-    } else {
-      html += '<div style="border:2px dashed #ccc;border-radius:6px;padding:6px;background:#fafafa;display:flex;align-items:center;justify-content:center;aspect-ratio:1;color:#aaa;font-size:11px">Slot '+(i+1)+'<br>(empty)</div>';
-    }
-  }
-  document.getElementById('board').innerHTML = html;
-}
-
-async function doSearch(){
-  var q = document.getElementById('search-q').value.trim();
-  if (!q) return;
-  var results = await get('/api/studio/search?q='+encodeURIComponent(q)+'&limit=60');
-  document.getElementById('search-results').innerHTML = results.map(function(r){
-    var thumb = r.thumb_url || '';
-    var label = (r.title||'').slice(0,40);
-    var meta = (r.artist||r.subsource||'') + (r.dominant_hex ? ' · '+r.dominant_hex : '');
-    return '<div onclick="addSearch('+JSON.stringify(r).replace(/"/g,'&quot;')+')" style="cursor:pointer;border:1px solid #eee;border-radius:4px;padding:4px;background:#fff;transition:transform .15s" onmouseover="this.style.transform=\\'scale(1.03)\\'" onmouseout="this.style.transform=\\'\\'">'+
-      (thumb?'<img src="'+thumb+'" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:3px" loading="lazy">':'<div style="aspect-ratio:1;background:#'+((r.dominant_hex||'#888').replace('#',''))+'"></div>')+
-      '<div style="font-size:10px;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+label+'">'+label+'</div>'+
-      '<div style="font-size:9px;color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+meta+'</div>'+
-      '</div>';
-  }).join('') || '<p style="color:#999;grid-column:1/-1">No matches for "'+q+'".</p>';
-}
-window.addSearch = function(r){
-  addToBoardNextSlot({
-    source_type: r.source,
-    source_id: String(r.id),
-    image_url: r.image_url,
-    thumb_url: r.thumb_url,
-    title: r.title,
-    artist: r.artist,
-    style_tags: (r.tags||[]).slice(0,4),
-    color_hex_seeds: r.dominant_hex ? [r.dominant_hex] : [],
-    selected_elements: []
-  });
-};
-
-document.getElementById('btn-search').addEventListener('click', doSearch);
-document.getElementById('search-q').addEventListener('keydown', function(e){ if (e.key==='Enter') doSearch(); });
-
-document.getElementById('upload-input').addEventListener('change', async function(e){
-  var f = e.target.files[0]; if (!f) return;
-  var stat = document.getElementById('compose-result');
-  stat.style.display='block'; stat.innerHTML = '<div style="padding:10px;background:#fff3cd;color:#856404;border-radius:4px">Uploading + analyzing… (5-15s)</div>';
-  var buf = await f.arrayBuffer();
-  var r = await fetch('/api/studio/upload?sid='+encodeURIComponent(SID)+'&name='+encodeURIComponent(f.name),
-    { method:'POST', headers:{'Content-Type':'application/octet-stream'}, body: buf });
-  var j = await r.json();
-  if (j.ok) {
-    addToBoardNextSlot({
-      source_type:'upload', source_id:String(j.id),
-      image_url: j.url, thumb_url: j.url,
-      title: f.name + (j.ai_summary ? ' — ' + j.ai_summary.slice(0,40) : ''),
-      artist: null,
-      style_tags: j.ai_style ? [j.ai_style] : [],
-      color_hex_seeds: (j.ai_colors||[]).concat(j.dominant_hex ? [j.dominant_hex] : []),
-      selected_elements: j.ai_elements || []
-    });
-    stat.innerHTML = '<div style="padding:10px;background:#d4edda;color:#155724;border-radius:4px">✓ Uploaded. AI detected style: <b>'+(j.ai_style||'unknown')+'</b>, colors: '+(j.ai_colors||[]).join(' ')+', elements: '+(j.ai_elements||[]).join(', ')+'</div>';
-  } else {
-    stat.innerHTML = '<div style="padding:10px;background:#f8d7da;color:#721c24;border-radius:4px">Upload failed: '+(j.error||'unknown')+'</div>';
-  }
-});
-
-document.getElementById('btn-compose').addEventListener('click', async function(){
-  this.disabled = true;
-  var btn = this; btn.textContent = 'Composing (10-30s)…';
-  var stat = document.getElementById('compose-result');
-  stat.style.display='block'; stat.innerHTML = '<div style="padding:10px;background:#fff3cd;color:#856404;border-radius:4px">Generating…</div>';
-  var j = await post('/api/studio/compose', {
-    sid: SID,
-    extra_elements: Array.from(EXTRA),
-    extra_text: document.getElementById('extra-text').value || ''
-  });
-  btn.disabled = false; btn.textContent = 'Compose new design';
-  if (j.ok) {
-    // R0a — payoff frame: big pattern preview + actions. Replaces the old "✓ N generated" toast.
-    var imgUrl = j.image_url || ('/design/'+j.design_id);
-    var safePrompt = (j.prompt||'').replace(/[<>]/g,'');
-    stat.innerHTML = (
-      '<div id="studio-payoff" style="margin:18px 0;padding:0;border:1px solid #e2e2e2;border-radius:10px;background:#fff;overflow:hidden">'+
-        '<div style="padding:10px 14px;background:#0f0e0c;color:#d2b15c;font-size:11px;letter-spacing:.08em;text-transform:uppercase">Final pattern · #'+j.design_id+'</div>'+
-        '<div style="display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:0;align-items:stretch">'+
-          // Big pattern preview (left)
-          '<a href="/design/'+j.design_id+'" target="_blank" style="display:block;aspect-ratio:1;background:url(\\''+imgUrl+'\\') center/cover #f2efe8;text-decoration:none;cursor:zoom-in" title="Open detail page" rel="noopener noreferrer"></a>'+
-          // Sidebar (right) — prompt + actions
-          '<div style="padding:18px;background:#fafafa;border-left:1px solid #eee;display:flex;flex-direction:column;gap:12px">'+
-            '<div>'+
-              '<div class="eyebrow" style="margin-bottom:4px">Prompt</div>'+
-              '<div style="font-size:11px;font-family:ui-monospace,Menlo,monospace;color:#333;line-height:1.45;max-height:140px;overflow-y:auto">'+safePrompt+'</div>'+
-            '</div>'+
-            '<div style="display:flex;flex-direction:column;gap:8px;margin-top:auto">'+
-              '<a href="/design/'+j.design_id+'" target="_blank" class="btn-primary" style="text-align:center" rel="noopener noreferrer">View design →</a>'+
-              '<a href="'+imgUrl+'" download class="btn-outline" style="text-align:center">Download pattern</a>'+
-              '<button id="studio-delete-btn" data-design-id="'+j.design_id+'" class="btn-outline" style="color:#a44;border-color:#d99">Delete this render</button>'+
-            '</div>'+
-          '</div>'+
-        '</div>'+
-        // Repeat-size control — pattern stays seamless; user picks how big a single repeat
-        // shows on the wall. Slider min = lots of small repeats; max = one giant pattern.
-        // Default sits at the larger end so the first render shows the design near full-size.
-        '<div style="padding:10px 14px;background:#fff;border-top:1px solid #eee">'+
-          '<label style="font-size:11px;color:#444;display:flex;align-items:center;gap:10px;cursor:pointer">'+
-            '<span style="white-space:nowrap;letter-spacing:.04em">Repeat size</span>'+
-            '<input type="range" id="studio-repeat-size" min="12" max="108" value="72" step="6" style="flex:1;accent-color:#0f0e0c">'+
-            '<span id="studio-repeat-val" style="font-family:ui-monospace,Menlo,monospace;color:#0f0e0c;min-width:70px;text-align:right;font-size:11px">72&quot; wide</span>'+
-          '</label>'+
-          '<div style="font-size:10px;color:#999;margin-top:3px">← more repeats (small tile) &nbsp;·&nbsp; one giant pattern →. Pattern stays seamless at every size.</div>'+
-        '</div>'+
-        // Instant client-side tile preview — pure CSS background-repeat so the slider
-        // gives 60fps feedback while the slow /api/room renders fire on the change event.
-        // Wall reference: ~120" wide (10 ft). The preview strip is 360px tall and
-        // proportional. Background-size in % maps slider inches → strip width:
-        //   tile_pct = (slider_inches / 120) * 100  →  e.g. 72" → 60% wide tile.
-        '<div style="padding:0 14px 12px;background:#fff;border-bottom:1px solid #eee">'+
-          '<div class="eyebrow" style="margin:8px 0 6px">Live tile preview · 10 ft wide wall</div>'+
-          '<div id="studio-tile-preview" data-pattern-url="'+imgUrl+'" '+
-               'style="height:140px;border:1px solid #eee;border-radius:4px;background-image:url(\\''+imgUrl+'\\');background-repeat:repeat;background-position:center;background-size:60% auto"></div>'+
-        '</div>'+
-        // 3 room settings row — one per category, in parallel.
-        '<div style="padding:14px 14px 12px;background:#fafafa">'+
-          '<div class="eyebrow" style="margin-bottom:8px">See it in a room · 3 categories</div>'+
-          '<div id="studio-rooms-grid" style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px">'+
-            '<div class="studio-room-cell" data-room="bedroom"     data-label="Bedroom"      style="position:relative;border:1px solid #eee;border-radius:6px;aspect-ratio:4/3;background:#fafafa center/cover no-repeat;display:flex;align-items:center;justify-content:center;color:#999;font-size:11px;overflow:hidden">Bedroom · loading…</div>'+
-            '<div class="studio-room-cell" data-room="living_room" data-label="Living Room"  style="position:relative;border:1px solid #eee;border-radius:6px;aspect-ratio:4/3;background:#fafafa center/cover no-repeat;display:flex;align-items:center;justify-content:center;color:#999;font-size:11px;overflow:hidden">Living Room · loading…</div>'+
-            '<div class="studio-room-cell" data-room="dining_room" data-label="Dining Room"  style="position:relative;border:1px solid #eee;border-radius:6px;aspect-ratio:4/3;background:#fafafa center/cover no-repeat;display:flex;align-items:center;justify-content:center;color:#999;font-size:11px;overflow:hidden">Dining Room · loading…</div>'+
-          '</div>'+
-          '<div style="font-size:9px;color:#bbb;margin:6px 0 0;letter-spacing:.04em">Rendered in parallel · ~10-30s each · drag the slider to re-render at a different scale</div>'+
-        '</div>'+
-      '</div>'
-    );
-    // R0b/T18 — kick off 3 room renders in parallel (one per category).
-    // The first room that lands is linked onto this studio_renders row so the
-    // personal gallery still shows pattern+room together.
-    //
-    // Slider-driven re-render: any change to #studio-repeat-size aborts in-flight
-    // fetches and fires a new batch with the updated patternWidth/Height. Each
-    // payoff frame owns its own AbortController so a fresh Compose can't race
-    // with an old slider drag.
-    var roomLinked = false;
-    var roomAbort = null;
-    function renderStudioRooms() {
-      if (roomAbort) { try { roomAbort.abort(); } catch(_) {} }
-      roomAbort = new AbortController();
-      var signal = roomAbort.signal;
-      var sliderEl = document.getElementById('studio-repeat-size');
-      var inches = sliderEl ? parseInt(sliderEl.value, 10) || 72 : 72;
-      var cells = document.querySelectorAll('#studio-rooms-grid .studio-room-cell');
-      // Reset every cell to the loading state before firing new fetches —
-      // this is what makes the visual refresh feel instant on each slider tick.
-      Array.prototype.forEach.call(cells, function(cell){
-        cell.style.backgroundImage = '';
-        cell.innerHTML = cell.dataset.label + ' · loading…';
-      });
-      Array.prototype.forEach.call(cells, function(cell){
-        var rt = cell.dataset.room;
-        var label = cell.dataset.label;
-        fetch('/api/room', {
-          method:'POST', headers:{'Content-Type':'application/json'},
-          signal: signal,
-          body: JSON.stringify({
-            design_id: j.design_id,
-            roomType: rt,
-            patternWidth: inches,
-            patternHeight: inches
-          })
-        }).then(function(r){ return r.json(); }).then(function(rd){
-          if (signal.aborted) return;
-          if (rd && rd.ok && rd.image_url) {
-            // Cache-bust on the rendered URL so an old image with the same
-            // path (rare, but possible if the server reuses filenames) can't
-            // hang around in the browser cache after a re-render.
-            cell.style.backgroundImage = 'url(\\''+rd.image_url+'?t='+Date.now()+'\\')';
-            cell.innerHTML = '<span style="position:absolute;bottom:6px;left:8px;background:rgba(0,0,0,.6);color:#fff;padding:2px 6px;border-radius:3px;font-size:9px;text-transform:uppercase;letter-spacing:.06em">'+label+' · #'+rd.room_id+'</span>';
-            if (rd.room_id && !roomLinked) {
-              roomLinked = true;
-              fetch('/api/studio-renders/'+j.design_id+'/link-room', {
-                method:'POST', headers:{'Content-Type':'application/json'},
-                body: JSON.stringify({ sid: SID, room_id: rd.room_id })
-              });
-              if (typeof loadMyRenders === 'function') loadMyRenders();
-            }
-          } else {
-            cell.innerHTML = '<span style="color:#c44;font-size:11px">'+label+' failed</span>';
-          }
-        }).catch(function(err){
-          if (err && err.name === 'AbortError') return;
-          cell.innerHTML = '<span style="color:#c44;font-size:11px">'+label+' failed</span>';
-        });
-      });
-    }
-    // Slider wiring — the input event fires continuously during drag (live tile
-    // preview); the change event fires on release (slow /api/room re-render,
-    // debounced 250ms). Value persists to localStorage so the user's preferred
-    // repeat size carries across composes and across browser sessions.
-    //
-    // Order matters: hydrate from localStorage BEFORE the initial renderStudioRooms
-    // call so the first /api/room batch uses the saved size, not the markup default.
-    var sliderEl = document.getElementById('studio-repeat-size');
-    var sliderVal = document.getElementById('studio-repeat-val');
-    var tilePrev = document.getElementById('studio-tile-preview');
-    var sliderDebounce = null;
-    var REPEAT_KEY = 'wallco.studio.repeat-inches';
-    function paintTilePreview(){
-      if (!tilePrev || !sliderEl) return;
-      // 120" reference wall; clamp 5%-100% so extremes stay readable.
-      var pct = Math.max(5, Math.min(100, (parseInt(sliderEl.value, 10) / 120) * 100));
-      tilePrev.style.backgroundSize = pct.toFixed(1) + '% auto';
-    }
-    if (sliderEl && sliderVal) {
-      // Hydrate from localStorage if the user has a preferred repeat size.
-      try {
-        var saved = parseInt(localStorage.getItem(REPEAT_KEY) || '', 10);
-        if (saved >= parseInt(sliderEl.min,10) && saved <= parseInt(sliderEl.max,10)) {
-          sliderEl.value = String(saved);
-          sliderVal.textContent = saved + '" wide';
-        }
-      } catch(_){}
-      sliderEl.addEventListener('input', function(){
-        sliderVal.textContent = sliderEl.value + '" wide';
-        paintTilePreview();
-      });
-      sliderEl.addEventListener('change', function(){
-        try { localStorage.setItem(REPEAT_KEY, sliderEl.value); } catch(_){}
-        if (sliderDebounce) clearTimeout(sliderDebounce);
-        sliderDebounce = setTimeout(renderStudioRooms, 250);
-      });
-      paintTilePreview();
-    }
-    renderStudioRooms();
-    // Delete button — wired by R0d.
-    var delBtn = document.getElementById('studio-delete-btn');
-    if (delBtn) delBtn.addEventListener('click', function(){
-      if (!confirm('Soft-delete render #'+j.design_id+'? (recoverable for 7 days)')) return;
-      this.disabled = true; this.textContent = 'Deleting…';
-      fetch('/api/studio-renders/'+j.design_id+'/delete', {
-        method:'POST',
-        headers:{'Content-Type':'application/json'},
-        body: JSON.stringify({ sid: SID })
-      })
-        .then(function(r){ return r.json(); })
-        .then(function(d){
-          if (d && d.ok) {
-            document.getElementById('studio-payoff').style.opacity = '0.4';
-            delBtn.textContent = '✓ Deleted (recoverable 7d)';
-            if (typeof loadMyRenders === 'function') loadMyRenders();
-          } else {
-            delBtn.textContent = 'Delete failed — retry';
-            delBtn.disabled = false;
-          }
-        })
-        .catch(function(){ delBtn.textContent = 'Delete failed — retry'; delBtn.disabled = false; });
-    });
-  } else {
-    stat.innerHTML = '<div style="padding:14px;background:#f8d7da;color:#721c24;border-radius:6px">Compose failed: '+(j.error||'unknown').replace(/[<>]/g,'')+'</div>';
-  }
-});
-
-async function loadTextures() {
-  var tx = await get('/api/studio/textures');
-  document.getElementById('textures-grid').innerHTML = tx.map(function(t){
-    return '<div draggable="true" ondragstart="dragTexture(event,'+JSON.stringify(t).replace(/"/g,'&quot;')+')" onclick="addTextureToBoard('+JSON.stringify(t).replace(/"/g,'&quot;')+')" style="cursor:grab;text-align:center;border:1px solid #eee;border-radius:4px;padding:3px;background:#fff;font-size:9px" title="'+t.name+' — '+t.category+'">'+
-      '<div style="aspect-ratio:1;background:url(\\''+t.image_url+'\\') center/cover, '+t.dominant_hex+';border-radius:3px"></div>'+
-      '<div style="margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+t.name+'</div>'+
-    '</div>';
-  }).join('');
-}
-
-window.dragTexture = function(e, t) {
-  e.dataTransfer.setData('application/x-texture', JSON.stringify(t));
-};
-window.addTextureToBoard = function(t) {
-  addToBoardNextSlot({
-    source_type:'texture', source_id:String(t.id),
-    image_url: t.image_url, thumb_url: t.thumb_url || t.image_url,
-    title: t.name + ' (texture)', artist: null,
-    style_tags: [t.category],
-    color_hex_seeds: t.dominant_hex ? [t.dominant_hex] : [],
-    selected_elements: [t.prompt_phrase || t.name]
-  });
-};
-
-// Drag-drop the texture / item onto a board slot
-function setupDropZones() {
-  document.querySelectorAll('[data-slot-zone]').forEach(function(z){
-    z.addEventListener('dragover', function(e){ e.preventDefault(); this.style.background='#fffbe6'; });
-    z.addEventListener('dragleave', function(){ this.style.background=''; });
-    z.addEventListener('drop', async function(e){
-      e.preventDefault(); this.style.background='';
-      var slot = parseInt(this.getAttribute('data-slot-zone'),10);
-      var t = e.dataTransfer.getData('application/x-texture');
-      if (t) {
-        var tx = JSON.parse(t);
-        await post('/api/studio/board/slot', {
-          sid: SID, slot_idx: slot,
-          source_type:'texture', source_id:String(tx.id),
-          image_url: tx.image_url, thumb_url: tx.thumb_url || tx.image_url,
-          title: tx.name + ' (texture)',
-          style_tags:[tx.category],
-          color_hex_seeds: tx.dominant_hex ? [tx.dominant_hex] : [],
-          selected_elements: [tx.prompt_phrase || tx.name]
-        });
-        loadBoard();
-      }
-    });
-  });
-}
-
-// Augment renderBoard to add data-slot-zone for drag-drop targets
-var _origRenderBoard = renderBoard;
-renderBoard = function(){
-  _origRenderBoard();
-  // Wrap each rendered slot with a dropzone marker
-  var grid = document.getElementById('board');
-  if (grid) {
-    Array.from(grid.children).forEach(function(child, i){
-      child.setAttribute('data-slot-zone', i);
-    });
-    setupDropZones();
-  }
-};
-
-async function loadSuggestions(){
-  var j = await post('/api/studio/suggest-palettes', { sid: SID });
-  var div = document.getElementById('palette-suggestions');
-  div.innerHTML = (j.palettes||[]).map(function(p,idx){
-    return '<div onclick="adoptPalette('+idx+')" style="cursor:pointer;padding:10px;border:1px solid #eee;border-radius:4px;background:#fafafa" onmouseover="this.style.background=\\'#fff\\'" onmouseout="this.style.background=\\'#fafafa\\'">'+
-      '<div style="font-size:12px;font-weight:500;margin-bottom:6px">'+p.name+'</div>'+
-      '<div style="display:flex;gap:2px;margin-bottom:6px">'+(p.colors||[]).map(function(c){return '<div style="flex:1;height:32px;background:'+c+';border-radius:2px"></div>';}).join('')+'</div>'+
-      '<div style="font-size:10px;color:#666">'+p.rationale+'</div>'+
-      '<div style="font-size:9px;color:#aaa;margin-top:3px;font-family:monospace">'+(p.colors||[]).join(' ')+'</div>'+
-    '</div>';
-  }).join('');
-  window.__palettes = j.palettes;
-}
-window.adoptPalette = async function(idx){
-  if (!window.__palettes || !window.__palettes[idx]) return;
-  var colors = window.__palettes[idx].colors || [];
-  // Add this palette as a new board slot of type 'palette'
-  addToBoardNextSlot({
-    source_type:'palette',
-    source_id:'sug-'+idx+'-'+Date.now(),
-    title: 'Palette: '+window.__palettes[idx].name,
-    artist: null,
-    style_tags: [],
-    color_hex_seeds: colors,
-    selected_elements: []
-  });
-};
-
-async function loadInsights(){
-  var cat = document.getElementById('cat-select').value;
-  var j = await get('/api/studio/category-insights?cat='+encodeURIComponent(cat));
-  var div = document.getElementById('insights-body');
-  var trendsHtml = '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px">' + (j.trends||[]).map(function(t){
-    return '<a href="'+t.link+'" target="_blank" style="padding:4px 10px;background:#eef6ff;color:#1a5298;border-radius:11px;text-decoration:none;font-size:11px" rel="noopener noreferrer">📈 '+t.term+'</a>';
-  }).join('') + '</div>';
-  var bs = j.best_sellers || [];
-  var bsHtml = bs.length ? '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:6px">' + bs.map(function(p){
-    return '<div style="border:1px solid #eee;border-radius:4px;padding:4px;background:#fff;font-size:10px"><img src="'+(p.image_url||'')+'" loading="lazy" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:3px"><div style="margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(p.title||'').slice(0,40)+'</div><div style="color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">Designer Wallcoverings</div></div>';
-  }).join('') + '</div>' : '<p style="color:#999;font-size:12px;margin:0">No best-sellers indexed for this category yet.</p>';
-  div.innerHTML = trendsHtml + bsHtml + '<p style="font-size:10px;color:#aaa;margin-top:8px">'+(j.ad_signals_note||'')+'</p>';
-}
-
-// Refresh-button + select-change hooks (deferred until DOM is wired)
-setTimeout(function(){
-  var btn = document.getElementById('btn-suggest');
-  if (btn) btn.addEventListener('click', loadSuggestions);
-  var sel = document.getElementById('cat-select');
-  if (sel) sel.addEventListener('change', loadInsights);
-  var op = document.getElementById('overlay-opacity');
-  if (op) op.addEventListener('input', function(){ document.getElementById('opacity-val').textContent = this.value + '%'; });
-}, 100);
-
-// Hook into compose: after success, optionally run interior-designer review
-var _origCompose = document.getElementById('btn-compose').onclick;
-document.getElementById('btn-compose').addEventListener('click', async function(){
-  // wait for compose to finish (the prior handler is async and already attached)
-  await new Promise(r => setTimeout(r, 800));
-  // poll for compose-result that includes design link
-  var tries = 0;
-  while (tries++ < 30) {
-    var html = document.getElementById('compose-result').innerHTML;
-    var m = html.match(/\\/design\\/(\\d+)/);
-    if (m && document.getElementById('auto-review').checked) {
-      var did = m[1];
-      var rev = document.getElementById('design-review');
-      rev.innerHTML = '<div style="padding:10px;background:#eef;color:#1a3;border-radius:4px;font-size:13px">Running interior-designer review…</div>';
-      var j = await post('/api/studio/interior-design-review', { design_id: parseInt(did,10) });
-      if (j.ok) {
-        var c = j.critique || {};
-        rev.innerHTML = '<div style="padding:14px;border:1px solid #ddd;border-radius:6px;background:#fafafa"><b>Interior-designer review · design #'+did+'</b>' +
-          '<div style="margin-top:8px;font-size:12px"><b>Strengths:</b> <ul style="margin:4px 0 8px 18px">'+ (c.strengths||[]).map(function(s){return '<li>'+s.replace(/[<>]/g,'')+'</li>';}).join('') +'</ul>' +
-          '<b>Concerns:</b> <ul style="margin:4px 0 8px 18px">'+ (c.concerns||[]).map(function(s){return '<li>'+s.replace(/[<>]/g,'')+'</li>';}).join('') +'</ul>' +
-          '<b>Recommendation:</b> '+ ((c.recommendation||'').replace(/[<>]/g,'')) +
-          '<br><b>Room fit:</b> '+ ((c.room_fit||[]).join(', ').replace(/[<>]/g,'')) + ' · <b>Lighting:</b> ' + ((c.lighting||'').replace(/[<>]/g,'')) + ' · <b>Scale:</b> ' + ((c.scale||'').replace(/[<>]/g,'')) +
-          '</div></div>';
-      } else {
-        rev.innerHTML = '<div style="padding:10px;background:#f8d7da;color:#721c24;border-radius:4px;font-size:13px">Review failed: '+(j.error||'unknown')+'</div>';
-      }
-      break;
-    }
-    await new Promise(r => setTimeout(r, 1500));
-  }
-});
-
-// R0c/R0d — personal gallery of this session's saved renders.
-async function loadMyRenders() {
-  var showDel = document.getElementById('show-deleted-toggle');
-  var includeDel = showDel && showDel.checked ? '&include_deleted=1' : '';
-  var j = await get('/api/studio-renders?sid=' + encodeURIComponent(SID) + includeDel);
-  var rs = (j && j.renders) || [];
-  var sec = document.getElementById('my-renders-section');
-  var grid = document.getElementById('my-renders-grid');
-  var count = document.getElementById('my-renders-count');
-  if (!sec || !grid) return;
-  if (!rs.length) { sec.style.display = 'none'; return; }
-  sec.style.display = 'block';
-  count.textContent = '· ' + rs.length + ' total';
-  grid.innerHTML = rs.map(function(r){
-    var img = r.image_url || '';
-    var dim = r.deleted_at ? 'opacity:.45;filter:grayscale(.6)' : '';
-    var roomBadge = r.room_image_url
-      ? '<span style="position:absolute;top:6px;left:6px;background:rgba(15,14,12,.8);color:#d2b15c;padding:2px 6px;border-radius:9px;font-size:9px;letter-spacing:.06em;text-transform:uppercase">+ room</span>'
-      : '';
-    var delBtn = r.deleted_at
-      ? '<button onclick="restoreRender(' + r.design_id + ')" style="font-size:10px;padding:4px 8px;background:transparent;color:#5a8;border:1px solid #5a8;border-radius:4px;cursor:pointer">Restore</button>'
-      : '<button onclick="deleteRender(' + r.design_id + ')" style="font-size:10px;padding:4px 8px;background:transparent;color:#a44;border:1px solid #d99;border-radius:4px;cursor:pointer">Delete</button>';
-    return '<div style="border:1px solid #eee;border-radius:6px;overflow:hidden;background:#fff;position:relative;' + dim + '">' +
-      '<a href="/design/' + r.design_id + '" target="_blank" style="display:block;aspect-ratio:1;background:url(\\'' + img + '\\') center/cover #f2efe8;text-decoration:none;position:relative" rel="noopener noreferrer">' + roomBadge + '</a>' +
-      '<div style="padding:6px 8px;font-size:10px;color:#666;display:flex;justify-content:space-between;align-items:center;gap:6px">' +
-        '<span>#' + r.design_id + (r.deleted_at ? ' · deleted' : '') + '</span>' + delBtn +
-      '</div>' +
-    '</div>';
-  }).join('');
-}
-window.deleteRender = async function(designId){
-  if (!confirm('Soft-delete render #'+designId+'? (recoverable for 7 days)')) return;
-  var r = await post('/api/studio-renders/'+designId+'/delete', { sid: SID });
-  if (r && r.ok) loadMyRenders(); else alert('Delete failed: '+((r&&r.error)||'unknown'));
-};
-window.restoreRender = async function(designId){
-  var r = await post('/api/studio-renders/'+designId+'/restore', { sid: SID });
-  if (r && r.ok) loadMyRenders(); else alert('Restore failed: '+((r&&r.error)||'unknown'));
-};
-setTimeout(function(){
-  var t = document.getElementById('show-deleted-toggle');
-  if (t) t.addEventListener('change', loadMyRenders);
-}, 80);
-
-loadRefs(); loadBoard(); loadTextures(); loadSuggestions(); loadInsights(); loadMyRenders();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── Pairings: 3 nearest-palette neighbors in CIE Lab space.
-app.get('/api/design/:id/pairings', (req, res) => {
-  try {
-    const id = parseInt(req.params.id, 10);
-    const target = psqlQuery(`SELECT dominant_hex FROM spoon_all_designs WHERE id=${id};`);
-    if (!target || !/^#[0-9a-f]{6}$/i.test(target)) return res.json({ designs: [] });
-    // Compute in JS — straightforward Lab Euclidean
-    const all = JSON.parse(psqlQuery(`SELECT COALESCE(json_agg(t),'[]'::json) FROM (SELECT id, dominant_hex, local_path FROM spoon_all_designs WHERE id <> ${id} AND dominant_hex IS NOT NULL AND generator='replicate') t;`) || '[]');
-    function hexToRgb(h){h=h.replace('#','');return[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)];}
-    function srgbToLinear(c){c/=255;return c<=0.04045?c/12.92:Math.pow((c+0.055)/1.055,2.4);}
-    function rgbToLab(rgb){const[r,g,b]=rgb.map(srgbToLinear);
-      const x=r*0.4124+g*0.3576+b*0.1805, y=r*0.2126+g*0.7152+b*0.0722, z=r*0.0193+g*0.1192+b*0.9505;
-      const xn=0.95047,yn=1,zn=1.08883;
-      const f=t=>t>0.008856?Math.cbrt(t):(7.787*t+16/116);
-      return[116*f(y/yn)-16, 500*(f(x/xn)-f(y/yn)), 200*(f(y/yn)-f(z/zn))];
-    }
-    const tLab = rgbToLab(hexToRgb(target));
-    const scored = all.map(p => {
-      const pLab = rgbToLab(hexToRgb(p.dominant_hex));
-      const d = Math.sqrt((tLab[0]-pLab[0])**2+(tLab[1]-pLab[1])**2+(tLab[2]-pLab[2])**2);
-      return { id: p.id, dominant_hex: p.dominant_hex, distance: d,
-        image_url: '/designs/img/' + (p.local_path||'').split('/').pop() };
-    }).sort((a,b)=>a.distance-b.distance).slice(0,3);
-    res.json({ designs: scored });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// ── GET /api/isolate/:designId — palette + SW codes + coordinating designs
-// Admin-only (404 elsewhere via requireAdmin). Reuses /api/pairings for the
-// design grid via colorIsolate.coordinatingDesigns; adds a SW-annotated palette
-// and 7 paint-coordinating hexes. SW DB is read lazily — missing DB returns
-// `sw: null` per entry and the UI degrades gracefully.
-app.get('/api/isolate/:designId', requireAdmin, (req, res) => {
-  try {
-    const id = parseInt(req.params.designId, 10);
-    const design = DESIGNS.find(d => d.id === id);
-    if (!design) return res.status(404).json({ error: 'design not found' });
-
-    // Resolve image path — prefer local_path, fall back to image_url → IMG_DIR
-    let imgPath = design.local_path || null;
-    if (!imgPath && design.filename) imgPath = path.join(IMG_DIR, design.filename);
-    if (!imgPath && design.image_url) {
-      imgPath = path.join(IMG_DIR, path.basename(design.image_url));
-    }
-
-    // Extract top-6 palette from image; if extraction fails (no PIL, missing file),
-    // fall back to just the dominant_hex.
-    let paletteHexes = [];
-    if (imgPath && fs.existsSync(imgPath)) {
-      paletteHexes = colorIsolate.extractPalette(imgPath, 6);
-    }
-    if (!paletteHexes.length && design.dominant_hex) {
-      paletteHexes = [design.dominant_hex];
-    }
-    const palette = colorIsolate.annotatePalette(paletteHexes);
-
-    // Coordinating hexes (color theory) — anchored on the dominant hex so the
-    // paint view stays stable across re-extracts. Each annotated with SW match.
-    const anchor = design.dominant_hex || paletteHexes[0] || '#888888';
-    const coordHexes = colorIsolate.coordinatingHexes(anchor) || {};
-    const coordinating_hexes = {};
-    for (const [k, hex] of Object.entries(coordHexes)) {
-      coordinating_hexes[k] = { hex, sw: colorIsolate.nearestSW(hex) };
-    }
-
-    // Coordinating designs from the catalog — top-12 by Lab ΔE (legacy single-grid view).
-    const coordinating_designs = colorIsolate
-      .coordinatingDesigns(anchor, DESIGNS.filter(d => d.id !== id), { limit: 12 })
-      .map(d => ({
-        id: d.id,
-        title: d.title,
-        image_url: d.image_url,
-        dominant_hex: d.dominant_hex,
-        category: d.category,
-        deltaE: d.deltaE,
-        closeness: d.closeness,
-      }));
-
-    // Bucketed by canonical style — stripes (10) first, plaids (10) next,
-    // then rotation through remaining styles. Each item carries `style` +
-    // `tags` so the UI can render a tag pill on every thumb. Live-queries
-    // PG for full tag/motif/prompt context (the in-memory DESIGNS snapshot
-    // drops those fields).
-    let coordinates_by_style = [];
-    try {
-      const rawAll = psqlQuery(`
-        SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-          SELECT id, dominant_hex, category, motifs, tags, prompt,
-                 ai_title AS title, ai_pattern AS pattern_name,
-                 local_path
-            FROM spoon_all_designs
-           WHERE id <> ${id}
-             AND (user_removed IS NOT TRUE)
-             AND local_path IS NOT NULL
-           LIMIT 4000
-        ) t;`);
-      const all = JSON.parse(rawAll || '[]').map(r => ({
-        ...r,
-        image_url: r.local_path
-          ? '/designs/img/' + path.basename(r.local_path)
-          : null,
-      })).filter(r => r.image_url);
-      coordinates_by_style = colorIsolate
-        .coordinatesByStyle(anchor, all, { stripeMax: 10, plaidMax: 10, perBucket: 8 })
-        .map(b => ({
-          ...b,
-          items: b.items.map(it => ({
-            id: it.id,
-            title: it.title || ('No. ' + it.id),
-            image_url: it.image_url,
-            dominant_hex: it.dominant_hex,
-            category: it.category,
-            style: it.style,
-            tags: (Array.isArray(it.tags) ? it.tags : []).slice(0, 5),
-            deltaE: it.deltaE,
-            closeness: it.closeness,
-          })),
-        }));
-    } catch (e) {
-      coordinates_by_style = [];
-    }
-
-    // R11 — annotate the design's stored dominant_hex with its nearest SW so
-    // consumers don't have to re-call nearestSW() for the canonical color.
-    const dominant_sw = design.dominant_hex ? colorIsolate.nearestSW(design.dominant_hex) : null;
-
-    res.json({
-      design: {
-        id: design.id,
-        title: design.title,
-        image_url: design.image_url,
-        category: design.category,
-        dominant_hex: design.dominant_hex,
-      },
-      dominant_sw,
-      palette,
-      coordinating_hexes,
-      coordinating_designs,
-      coordinates_by_style,
-    });
-  } catch (e) {
-    res.status(500).json({ error: e.message });
-  }
-});
-
-// ── POST /api/design/:id/bake — save a user-edited recolor as a new design
-// ── /api/design/:id/furnish — generate coordinated furniture + lighting +
-//    decor recommendations for a design, scoped to a use case (residential
-//    /commercial/hospitality/healthcare). Uses Gemini-text against the
-//    design's palette + motif + category. Read-only — no DB writes.
-//    furnish_route marker.
-// Disk-backed cache for /api/design/:id/furnish. The Gemini-text output
-// for a given (design × context) is stable — same palette + motif + use
-// case → same recommendations. Cache forever; manual purge via deleting
-// data/furnish-cache/<id>-<ctx>.json. Steve's 2026-05-13 cost note: each
-// click was hitting Gemini fresh; this drops repeat cost to zero.
-const FURNISH_CACHE_DIR = path.join(__dirname, 'data', 'furnish-cache');
-fs.mkdirSync(FURNISH_CACHE_DIR, { recursive: true });
-function furnishCachePath(id, ctx) {
-  return path.join(FURNISH_CACHE_DIR, `${id}-${ctx}.json`);
-}
-
-app.post('/api/design/:id/furnish', async (req, res) => {
-  try {
-    const id = parseInt(req.params.id, 10);
-    if (!Number.isFinite(id) || id < 1) return res.status(400).json({ ok: false, error: 'bad id' });
-    const ctx = String((req.body && req.body.context) || 'residential').toLowerCase();
-    const validCtx = new Set(['residential','commercial','hospitality','healthcare']);
-    if (!validCtx.has(ctx)) return res.status(400).json({ ok: false, error: 'bad context' });
-
-    // Cache hit — skip Gemini, return saved JSON. ?refresh=1 forces re-gen.
-    const cachePath = furnishCachePath(id, ctx);
-    if (req.query.refresh !== '1' && fs.existsSync(cachePath)) {
-      try {
-        const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
-        return res.json({ ...cached, cached: true });
-      } catch (e) { /* fall through to fresh gen on parse error */ }
-    }
-
-    const d = DESIGNS.find(x => x.id === id);
-    if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
-
-    const KEY = process.env.GEMINI_API_KEY;
-    if (!KEY) return res.status(500).json({ ok: false, error: 'GEMINI_API_KEY missing' });
-
-    const palette = Array.isArray(d.palette) ? d.palette.map(p => p.hex).slice(0, 5).join(', ') : '';
-    const motifs = Array.isArray(d.motifs) ? d.motifs.slice(0, 6).join(', ') : '';
-    const contextLabel = {
-      residential: 'a residential home interior',
-      commercial:  'a commercial office or retail space',
-      hospitality: 'a hospitality space (hotel lobby / restaurant / lounge)',
-      healthcare:  'a healthcare or clinic waiting room',
-    }[ctx];
-
-    const prompt = [
-      'You are an interior designer.',
-      `Wallcovering: "${d.title}". Category: ${d.category}. Motifs: ${motifs || 'n/a'}. Dominant palette hexes: ${palette || 'n/a'}.`,
-      `Use case: ${contextLabel}.`,
-      'Recommend coordinating furniture, lighting, rugs, art, tables, and accessories.',
-      'Return STRICT JSON only (no markdown, no prose) with this exact shape:',
-      '{ "categories": [ { "category": "Seating", "items": [ { "name": "...", "detail": "..." } ] }, ... ], "note": "one sentence styling note" }',
-      'Categories to include: Seating, Lighting, Rugs, Art, Tables, Accessories. 2-3 items each. Detail is 1 short phrase (material + color cue).',
-    ].join(' ');
-
-    const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${KEY}`, {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({
-        contents: [{ parts: [{ text: prompt }] }],
-        generationConfig: { responseMimeType: 'application/json', temperature: 0.7 },
-      }),
-    });
-    if (!r.ok) return res.status(502).json({ ok: false, error: 'gemini ' + r.status, body: (await r.text()).slice(0,200) });
-    const j = await r.json();
-    try {
-      const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js');
-      logGemini(j, { app: 'wallco-ai', note: 'furnish-' + ctx, model: 'gemini-2.5-flash' });
-    } catch {}
-    const text = j.candidates?.[0]?.content?.parts?.find(p => p.text)?.text || '{}';
-    let parsed = {};
-    try { parsed = JSON.parse(text); } catch (e) { return res.status(502).json({ ok: false, error: 'parse failed', text: text.slice(0,200) }); }
-    const payload = { ok: true, design_id: id, context: ctx, categories: parsed.categories || [], note: parsed.note || '' };
-    // Write-through cache (best-effort; ignore disk errors)
-    try {
-      const tmp = cachePath + '.tmp';
-      fs.writeFileSync(tmp, JSON.stringify({ ...payload, generated_at: new Date().toISOString() }, null, 2));
-      fs.renameSync(tmp, cachePath);
-    } catch (e) { console.warn('[furnish-cache] write:', e.message); }
-    return res.json(payload);
-  } catch (e) {
-    return res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-// ── /api/design/:id/recolor — tone-on-tone color-wheel recoloring ──
-// Admin-only. Takes a source design + hue family, calls Gemini image-edit
-// with a strict mono-hue prompt, inserts a new spoon_all_designs row,
-// returns the new id + image_url.
-//
-// Steve's 2026-05-13 ask: "Add buttons to admin to create in color wheel
-// color. for tone on tone items." This endpoint backs those buttons.
-const RECOLOR_HUES = {
-  red:     { label: 'Red',      tail: 'crimson red and burgundy ground; deeper near-black-red motif; rich saturation' },
-  orange:  { label: 'Orange',   tail: 'warm terracotta-orange ground; deeper burnt-orange motif; earthy saturation' },
-  yellow:  { label: 'Yellow',   tail: 'soft butter-yellow ground; deeper gold-ochre motif; vintage feel' },
-  lime:    { label: 'Lime',     tail: 'pale lime-chartreuse ground; deeper olive-lime motif; fresh herbaceous' },
-  green:   { label: 'Green',    tail: 'sage to forest green ground; deeper hunter motif; tonal jewel tones' },
-  teal:    { label: 'Teal',     tail: 'teal-aqua ground; deeper near-black teal motif; tonal lacquer feel' },
-  cyan:    { label: 'Cyan',     tail: 'pale cyan-aqua ground; deeper peacock motif; subtle marine' },
-  blue:    { label: 'Blue',     tail: 'powder to navy blue ground; near-black-navy motif; tonal jewel' },
-  indigo:  { label: 'Indigo',   tail: 'rich indigo ground; near-black indigo motif; deep tonal' },
-  violet:  { label: 'Violet',   tail: 'lavender to deep violet ground; near-black violet motif; tonal' },
-  magenta: { label: 'Magenta',  tail: 'fuchsia-magenta ground; deeper burgundy-magenta motif; saturated tonal' },
-  pink:    { label: 'Pink',     tail: 'blush to dusty rose ground; deeper rose motif; tonal romantic' },
-  neutral: { label: 'Neutral',  tail: 'warm cream/taupe ground; deeper sepia-bronze motif; tonal vintage' },
-  charcoal:{ label: 'Charcoal', tail: 'pale grey ground; deep charcoal motif; tonal monochrome' },
-};
-
-app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
-  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
-  try {
-    const id = parseInt(req.params.id, 10);
-    if (!Number.isFinite(id) || id < 1) return res.status(400).json({ ok: false, error: 'bad id' });
-    const hueKey = String((req.body && req.body.hue) || '').toLowerCase();
-    const hue = RECOLOR_HUES[hueKey];
-    if (!hue) return res.status(400).json({ ok: false, error: 'bad hue', valid: Object.keys(RECOLOR_HUES) });
-
-    const KEY = process.env.GEMINI_API_KEY;
-    if (!KEY) return res.status(500).json({ ok: false, error: 'GEMINI_API_KEY missing' });
-
-    // Load source image bytes from local_path or by reading the file behind image_url
-    const d = DESIGNS.find(x => x.id === id);
-    if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
-    const srcPath = d.local_path || (d.filename ? path.join(IMG_DIR, d.filename) : null)
-      || (d.image_url && d.image_url.startsWith('/designs/img/') ? path.join(IMG_DIR, d.image_url.replace('/designs/img/', '')) : null);
-    if (!srcPath || !fs.existsSync(srcPath)) return res.status(404).json({ ok: false, error: 'source file missing on disk', srcPath });
-    const srcB64 = fs.readFileSync(srcPath).toString('base64');
-
-    const prompt = `Recolor everything into the ${hue.label.toUpperCase()} family ONLY. ${hue.tail}. `
-      + 'Tone-on-tone: strictly one hue family throughout — no contrasting hues anywhere. '
-      + 'Preserve the motif, composition, and pattern repeat exactly. '
-      + 'Seamless tile, no edge artifacts, no signature, no watermark, no text. '
-      + 'High detail, archival quality, fabric-pattern aesthetic.';
-
-    const t0 = Date.now();
-    const gr = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${KEY}`, {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({
-        contents: [{ parts: [{ inline_data: { mime_type: 'image/png', data: srcB64 } }, { text: prompt }] }],
-        generationConfig: { responseModalities: ['IMAGE'] },
-      }),
-    });
-    if (!gr.ok) return res.status(502).json({ ok: false, error: 'gemini ' + gr.status, body: (await gr.text()).slice(0, 200) });
-    const gj = await gr.json();
-    try {
-      const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js');
-      logGemini(gj, { app: 'wallco-ai', note: 'recolor-' + hueKey + '-from-' + id, model: 'gemini-2.5-flash-image' });
-    } catch {}
-    const part = gj.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
-    const data = part?.inline_data?.data || part?.inlineData?.data;
-    if (!data) return res.status(502).json({ ok: false, error: 'no image returned', finish: gj.candidates?.[0]?.finishReason });
-
-    // Save PNG
-    const crypto2 = require('crypto');
-    const seed = crypto2.randomInt(1, 2 ** 31 - 1);
-    const filename = `recolor_${Date.now()}_p${id}_${hueKey}_${seed}.png`;
-    const outPath = path.join(IMG_DIR, filename);
-    fs.writeFileSync(outPath, Buffer.from(data, 'base64'));
-    const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
-
-    // Insert into PG
-    const parentTitle = (d.title || ('Design ' + id)).replace(/\s+No\.\d+$/, '');
-    const newTitle = `${parentTitle} (${hue.label} tonal)`;
-    const baseTags = ['recolor', 'tone-on-tone', hueKey];
-    if (Array.isArray(d.motifs)) baseTags.push(...d.motifs.slice(0, 3));
-    const tagSql = 'ARRAY[' + baseTags.map(t => "'" + String(t).replace(/'/g, "''") + "'").join(',') + ']::text[]';
-    const motifsSql = Array.isArray(d.motifs) && d.motifs.length
-      ? 'ARRAY[' + d.motifs.map(t => "'" + String(t).replace(/'/g, "''") + "'").join(',') + ']::text[]'
-      : "ARRAY[]::text[]";
-    const promptDesc = (newTitle + ' — ' + prompt).slice(0, 1800).replace(/'/g, "''");
-    const insertSql = `INSERT INTO spoon_all_designs
-      (kind, width_in, height_in, generator, prompt, seed, dominant_hex,
-       local_path, image_url, category, motifs, tags, is_published, request_text)
-      VALUES ('seamless_tile', 24, 24, 'gemini-2.5-flash-image-edit',
-        '${promptDesc}', ${seed}, NULL,
-        '${outPath.replace(/'/g, "''")}', '/designs/img/${filename}',
-        ${"'" + (d.category || 'recolor') + "'"},
-        ${motifsSql}, ${tagSql}, TRUE,
-        '${("Tone-on-tone " + hue.label + " recolor of design " + id).replace(/'/g, "''")}')
-      RETURNING id;`;
-    const newIdStr = psqlQuery(insertSql);
-    const newId = parseInt(newIdStr, 10);
-
-    // Push into in-memory DESIGNS so it shows up without a server restart
-    DESIGNS.unshift({
-      id: newId,
-      kind: 'seamless_tile',
-      category: d.category || 'recolor',
-      title: newTitle,
-      handle: 'wallco-' + newId,
-      image_url: '/designs/img/' + filename,
-      filename,
-      local_path: outPath,
-      generator: 'gemini-2.5-flash-image-edit',
-      seed,
-      is_published: true,
-      created_at: new Date().toISOString(),
-      motifs: d.motifs || [],
-      tags: baseTags,
-    });
-
-    res.json({ ok: true, parent_id: id, new_id: newId, new_image_url: '/designs/img/' + filename, hue: hueKey, elapsed_s: elapsed });
-  } catch (e) {
-    res.status(500).json({ ok: false, error: e.message });
-  }
-});
-
-app.post('/api/design/:id/bake',
-  express.json({ limit: '20mb' }),
-  (req, res) => {
-    const parentId = parseInt(req.params.id, 10);
-    const { image_b64 } = req.body || {};
-    if (!image_b64) return res.status(400).json({ ok:false, error:'image_b64 required' });
-    try {
-      const filename = `bake_${Date.now()}_p${parentId}.png`;
-      const outPath = path.join(IMG_DIR, filename);
-      fs.writeFileSync(outPath, Buffer.from(image_b64, 'base64'));
-      // Watermark the baked image (silent no-op if Python/Pillow missing)
-      try {
-        spawnSync('python3', [
-          path.join(__dirname, 'scripts', 'watermark.py'),
-          'embed', outPath, '--out', outPath,
-          '--owner', process.env.WATERMARK_OWNER || 'SAND, LLC',
-          '--year', String(new Date().getFullYear()),
-        ], { timeout: 30000 });
-      } catch {}
-      // Insert into spoon_all_designs (no Replicate call; this is user-edited)
-      const parent = DESIGNS.find(d => d.id === parentId) || {};
-      const id = psqlQuery(`INSERT INTO spoon_all_designs
-        (kind, width_in, height_in, panels, generator, prompt, seed,
-         pd_source_ids, local_path, dominant_hex, category, is_published, notes)
-        VALUES('seamless_tile', 24, 24, 1, 'replicate',
-               'user-baked recolor of design #${parentId}', 0,
-               '{}', '${outPath.replace(/'/g,"''")}',
-               ${parent.dominant_hex ? "'"+parent.dominant_hex+"'" : 'NULL'},
-               ${parent.category ? "'"+parent.category.replace(/'/g,"''")+"'" : 'NULL'},
-               FALSE, 'baked from design #${parentId}')
-        RETURNING id;`);
-      const newId = parseInt(id, 10);
-      // Eagerly synthesize a snapshot-style design + push to in-memory list
-      // so the upcoming GET /design/:newId hits the fast path immediately —
-      // no blocking snapshot regen, no race on redirect.
-      try {
-        const hex = parent.dominant_hex || null;
-        const cat = parent.category || 'mixed';
-        DESIGNS.push({
-          id: newId,
-          kind: 'seamless_tile',
-          category: cat,
-          dominant_hex: hex,
-          saturation: hex ? hexSaturation(hex) : 0,
-          title: titleFor(cat, hex, newId),
-          handle: `wallco-${String(newId).padStart(4,'0')}`,
-          image_url: `/designs/img/${filename}`,
-          filename,
-          generator: 'wallco.ai',
-          seed: 0,
-          is_published: false,
-          created_at: new Date().toISOString(),
-          notes: `baked from design #${parentId}`
-        });
-      } catch {}
-      // Snapshot regen runs in background (non-blocking) — the in-memory
-      // push above means the redirect works immediately regardless.
-      try {
-        const proc = spawn('python3',
-          [path.join(__dirname, 'scripts', 'refresh_designs_snapshot.py')],
-          { detached: true, stdio: 'ignore' });
-        proc.unref();
-      } catch {}
-      res.json({ ok:true, design_id: newId, filename });
-    } catch (e) {
-      res.status(500).json({ ok:false, error: e.message });
-    }
-  }
-);
-
-// ── GET /api/design/:id/rating — AI suggested rating
-app.get('/api/design/:id/rating', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(404).json({});
-  try {
-    const row = psqlQuery(`SELECT row_to_json(t) FROM (SELECT gd_score, gd_critique,
-      id_score, id_critique, combined_score, rating_summary, rated_at
-      FROM spoon_all_designs WHERE id=${id}) t;`);
-    if (!row) return res.json({});
-    res.json(JSON.parse(row));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── POST /api/design/:id/ai-rate — fire the AI rater now
-//    (renamed from /rate to avoid colliding with the human-rating endpoint
-//     at line ~1640 that's gated to admin)
-app.post('/api/design/:id/ai-rate', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id) || id < 1) return res.status(404).json({ ok: false });
-  const proc = spawn('node',
-    [path.join(__dirname, 'scripts', 'rate_design.js'), '--id', String(id)],
-    { detached: true, stdio: 'ignore', env: process.env });
-  proc.unref();
-  res.json({ ok: true, queued: id });
-});
-
-// ─────────────────────────────────────────────────────────────────────────
-// GAMIFY — user votes + head-to-head polls + Elo + learning loop
-// ─────────────────────────────────────────────────────────────────────────
-function voterSession(req, res) {
-  const m = (req.headers.cookie || '').match(/wc-voter=([a-z0-9]+)/i);
-  if (m) return m[1];
-  const id = require('crypto').randomBytes(10).toString('hex');
-  res.setHeader('Set-Cookie', `wc-voter=${id}; Path=/; Max-Age=31536000; SameSite=Lax`);
-  return id;
-}
-
-app.post('/api/design/:id/user-vote', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  const body = req.body || {};
-  const s = parseInt(body.score, 10);
-  const quick = !!body.quick;
-  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
-  if (!Number.isFinite(s) || s < 1 || s > 5) return res.status(400).json({ error: 'score 1..5 required' });
-
-  // Reasons + note are NOT-only metadata — but accept them on any score for forward-compat.
-  // Reasons: array of short strings, cap at 12. Note: free text, cap at 2 KB.
-  const reasons = Array.isArray(body.reasons)
-    ? body.reasons.map(r => String(r || '').trim()).filter(Boolean).slice(0, 12)
-    : [];
-  const note = (typeof body.note === 'string') ? body.note.trim().slice(0, 2048) : '';
-
-  const session = voterSession(req, res);
-  const sqlEsc = (v) => String(v).replace(/'/g, "''");
-  try {
-    // Detect once whether the schema migration has been applied. Cached on first call.
-    if (typeof app.locals._hon_cols_ready === 'undefined') {
-      app.locals._hon_cols_ready = !!psqlQuery(
-        `SELECT 1 FROM information_schema.columns WHERE table_name='wallco_votes' AND column_name='reasons' LIMIT 1;`
-      );
-    }
-    const colsReady = app.locals._hon_cols_ready;
-
-    if (colsReady) {
-      const reasonsSql = reasons.length
-        ? `ARRAY[${reasons.map(r => `'${sqlEsc(r)}'`).join(',')}]::text[]`
-        : `NULL`;
-      const noteSql = note ? `'${sqlEsc(note)}'` : `NULL`;
-      psqlQuery(`INSERT INTO wallco_votes (design_id, voter_session, score, quick, reasons, note)
-        VALUES (${id}, '${sqlEsc(session)}', ${s}, ${quick ? 'TRUE' : 'FALSE'}, ${reasonsSql}, ${noteSql})
-        ON CONFLICT (design_id, voter_session) DO UPDATE SET
-          score=EXCLUDED.score, reasons=EXCLUDED.reasons, note=EXCLUDED.note, created_at=now();`);
-    } else {
-      // Graceful degrade: persist the vote, log reasons/note to disk for later replay.
-      psqlQuery(`INSERT INTO wallco_votes (design_id, voter_session, score, quick)
-        VALUES (${id}, '${sqlEsc(session)}', ${s}, ${quick ? 'TRUE' : 'FALSE'})
-        ON CONFLICT (design_id, voter_session) DO UPDATE SET score=EXCLUDED.score, created_at=now();`);
-      if (reasons.length || note) {
-        try {
-          const queue = path.join(__dirname, 'logs', 'hon-reasons-queue.jsonl');
-          fs.mkdirSync(path.dirname(queue), { recursive: true });
-          fs.appendFileSync(queue, JSON.stringify({
-            ts: new Date().toISOString(),
-            design_id: id, voter_session: session, score: s, reasons, note
-          }) + '\n');
-        } catch (qe) { /* non-fatal */ }
-      }
-    }
-
-    const agg = JSON.parse(psqlQuery(`SELECT row_to_json(t) FROM (SELECT
-      ROUND(AVG(score)::numeric, 2)::text AS avg, COUNT(*)::int AS cnt
-      FROM wallco_votes WHERE design_id=${id}) t;`));
-    psqlQuery(`UPDATE spoon_all_designs SET user_score_avg=${agg.avg || 'NULL'},
-      user_vote_count=${agg.cnt} WHERE id=${id};`);
-    res.json({
-      ok: true,
-      user_score_avg: parseFloat(agg.avg),
-      user_vote_count: agg.cnt,
-      reasons_persisted: colsReady,
-      reasons_queued: !colsReady && (reasons.length > 0 || !!note)
-    });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/design/:id/votes', (req, res) => {
-  const id = parseInt(req.params.id, 10);
-  if (!Number.isFinite(id)) return res.json({});
-  const session = voterSession(req, res);
-  try {
-    const my = psqlQuery(`SELECT score FROM wallco_votes WHERE design_id=${id} AND voter_session='${session}' LIMIT 1;`);
-    const agg = psqlQuery(`SELECT COALESCE(user_score_avg::text,'') || '|' || COALESCE(user_vote_count::text,'0') FROM spoon_all_designs WHERE id=${id};`);
-    const [avg, cnt] = (agg || '|').split('|');
-    res.json({ my_score: my ? parseInt(my,10) : null, avg: avg ? parseFloat(avg) : null, count: parseInt(cnt,10) || 0 });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/poll/pair', (_req, res) => {
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM
-      (SELECT id, dominant_hex, image_url, local_path, category,
-              (poll_wins + poll_losses) AS plays
-       FROM spoon_all_designs WHERE local_path IS NOT NULL
-       ORDER BY (poll_wins + poll_losses) ASC, random() LIMIT 40) t;`);
-    const pool = JSON.parse(raw || '[]');
-    if (pool.length < 2) return res.json({ error: 'not enough designs' });
-    const a = pool[Math.floor(Math.random() * pool.length)];
-    let b = pool[Math.floor(Math.random() * pool.length)];
-    while (b.id === a.id) b = pool[Math.floor(Math.random() * pool.length)];
-    function imgFor(d){ return '/designs/img/' + (d.local_path || '').split('/').pop(); }
-    res.json({
-      a: { id: a.id, title: titleFor(a.category, a.dominant_hex, a.id), image_url: imgFor(a), category: a.category },
-      b: { id: b.id, title: titleFor(b.category, b.dominant_hex, b.id), image_url: imgFor(b), category: b.category }
-    });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.post('/api/poll/vote', (req, res) => {
-  const w = parseInt((req.body || {}).winner, 10), l = parseInt((req.body || {}).loser, 10);
-  if (!Number.isFinite(w) || !Number.isFinite(l) || w === l) return res.status(400).json({ error: 'winner+loser required, must differ' });
-  const session = voterSession(req, res);
-  try {
-    psqlQuery(`INSERT INTO wallco_poll_pairs (design_a, design_b, winner, voter_session)
-      VALUES (${Math.min(w,l)}, ${Math.max(w,l)}, ${w}, '${session}');`);
-    const rows = psqlQuery(`SELECT id::text || '|' || elo::text FROM spoon_all_designs WHERE id IN (${w}, ${l});`).split('\n').filter(Boolean);
-    const elos = {};
-    rows.forEach(r => { const [id, e] = r.split('|'); elos[id] = parseFloat(e); });
-    const Ra = elos[w] || 1200, Rb = elos[l] || 1200;
-    const Ea = 1 / (1 + Math.pow(10, (Rb - Ra) / 400));
-    const K = 32;
-    const newRa = Ra + K * (1 - Ea), newRb = Rb + K * (0 - (1 - Ea));
-    psqlQuery(`UPDATE spoon_all_designs SET elo=${newRa.toFixed(2)}, poll_wins=poll_wins+1 WHERE id=${w};`);
-    psqlQuery(`UPDATE spoon_all_designs SET elo=${newRb.toFixed(2)}, poll_losses=poll_losses+1 WHERE id=${l};`);
-    res.json({ ok: true, new_elo: { [w]: +newRa.toFixed(2), [l]: +newRb.toFixed(2) } });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/leaderboard', (req, res) => {
-  const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
-  try {
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-      SELECT id, dominant_hex, category, local_path, combined_score,
-             user_score_avg, user_vote_count, poll_wins, poll_losses, elo,
-             (COALESCE(combined_score,3) * 0.4
-              + COALESCE(user_score_avg,3) * 0.4
-              + (elo - 1200) / 200.0) AS rank_score
-      FROM spoon_all_designs WHERE local_path IS NOT NULL
-      ORDER BY rank_score DESC NULLS LAST LIMIT ${limit}) t;`);
-    res.json(JSON.parse(raw || '[]'));
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── POST /api/personal/recs — taste-signature-based product picks.
-//    Body: { categories, hexes, ids (excludeIds), limit }
-//    Returns: { picks:[{id, title, image_url, category, dominant_hex,
-//                       combined_score, sample_url}], signature }
-//    The client-side WC engine on /hot-or-not + /swipe + /play surfaces
-//    these once refinement ≥80%.
-app.post('/api/personal/recs', (req, res) => {
-  var { categories = [], hexes = [], ids = [], limit = 8 } = req.body || {};
-  categories = (categories || []).filter(Boolean).slice(0, 5);
-  hexes      = (hexes      || []).filter(h => /^#[0-9a-f]{6}$/i.test(h)).slice(0, 8);
-  ids        = (ids        || []).map(x => parseInt(x,10)).filter(Number.isFinite);
-
-  var catSql = categories.length
-    ? `category IN (${categories.map(c => "'" + String(c).replace(/'/g,"''") + "'").join(',')})`
-    : '1=1';
-  var exclSql = ids.length ? `AND id NOT IN (${ids.join(',')})` : '';
-
-  try {
-    var raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-      SELECT id, prompt, dominant_hex, category, local_path,
-             combined_score, user_score_avg, user_vote_count, elo,
-             (COALESCE(combined_score, 3) * 0.4
-              + COALESCE(user_score_avg, 3) * 0.4
-              + (elo - 1200) / 200.0
-              + CASE WHEN ${catSql} THEN 1.0 ELSE 0 END
-             ) AS rank_score
-      FROM spoon_all_designs
-      WHERE local_path IS NOT NULL
-        ${exclSql}
-      ORDER BY rank_score DESC NULLS LAST
-      LIMIT ${parseInt(limit, 10) || 8}) t;`);
-    var rows = JSON.parse(raw || '[]');
-    var picks = rows.map(r => {
-      var fn = (r.local_path || '').split('/').pop();
-      return {
-        id: r.id,
-        title: titleFor(r.category, r.dominant_hex, r.id),
-        image_url: '/designs/img/' + fn,
-        category: r.category,
-        dominant_hex: r.dominant_hex,
-        combined_score: r.combined_score ? Number(r.combined_score).toFixed(1) : null,
-        sample_url: '/samples?design=' + r.id
-      };
-    });
-    res.json({ picks: picks, signature: { categories: categories, hexes: hexes } });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/api/poll/stats', (_req, res) => {
-  try {
-    const pairs = psqlQuery(`SELECT COUNT(*) FROM wallco_poll_pairs;`);
-    const designs = psqlQuery(`SELECT COUNT(*) FROM spoon_all_designs WHERE (poll_wins+poll_losses)>0;`);
-    res.json({ total_pairs: parseInt(pairs,10) || 0, designs_polled: parseInt(designs,10) || 0 });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-app.get('/poll', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Pick your favorite — wallco.ai',
-    description: 'Vote between two AI-original wallpaper designs. Your taste teaches the system.',
-    canonical: 'https://wallco.ai/poll'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:24px 28px;max-width:1300px;margin:0 auto">
-  <header style="text-align:center;margin-bottom:24px">
-    <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:32px;margin:0 0 6px">Pick your favorite</h1>
-    <p style="color:#666;margin:0 0 12px">Click the one you'd hang on your wall. Your taste teaches the system &mdash; every vote nudges what we generate next.</p>
-    <div id="poll-stats" style="font-size:12px;color:#888"></div>
-  </header>
-  <!-- Gamification banner — fills via WC.refreshBanner() in wallco-game.js -->
-  <div id="wc-game-banner" style="background:#1a1816;border-radius:10px;padding:14px 18px;margin-bottom:22px;border:1px solid rgba(210,177,92,.2)"></div>
-  <div id="poll-arena" style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:32px">
-    <div class="poll-card" id="card-a"><div class="poll-loading">Loading&hellip;</div></div>
-    <div class="poll-card" id="card-b"></div>
-  </div>
-  <div style="text-align:center"><button id="btn-skip" class="btn-outline">Skip &mdash; show me another pair</button></div>
-  <hr style="margin:32px 0;border:0;border-top:1px solid rgba(0,0,0,.1)">
-  <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:0 0 14px;text-align:center">Top 20 &mdash; combined AI + user score + Elo</h2>
-  <div id="leaderboard" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px"></div>
-</main>
-${FOOTER}
-<style>
-  .poll-card { border:1px solid rgba(0,0,0,.08); border-radius:8px; overflow:hidden; cursor:pointer; transition:transform .15s,box-shadow .15s; background:#fff; }
-  .poll-card:hover { transform:scale(1.015); box-shadow:0 12px 36px rgba(0,0,0,.18); border-color:#d2b15c }
-  .poll-card img { display:block; width:100%; aspect-ratio:1; object-fit:cover }
-  .poll-card .meta { padding:12px 16px; background:#1a1816; color:#e8e2d6 }
-  .poll-card .meta .title { font-family:'Cormorant Garamond',serif; font-weight:300; font-size:19px }
-  .poll-card .meta .sub { font-size:11px; color:#888; margin-top:4px; text-transform:uppercase; letter-spacing:.05em }
-  .poll-loading { padding:80px 40px; text-align:center; color:#888 }
-  .lb-card { border:1px solid rgba(0,0,0,.08); border-radius:6px; overflow:hidden; background:#fff; font-size:11px; transition:transform .12s; text-decoration:none; color:inherit; display:block }
-  .lb-card:hover { transform:scale(1.04); box-shadow:0 8px 20px rgba(0,0,0,.12) }
-  .lb-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block }
-  .lb-card .meta { padding:6px 8px }
-  .lb-card .rank { font-weight:600; color:#d2b15c }
-  .lb-card .score { color:#666; font-family:monospace; font-size:10px; margin-top:2px }
-</style>
-<script>
-async function loadPair() {
-  ['a','b'].forEach(function(k){ document.getElementById('card-'+k).innerHTML = '<div class="poll-loading">Loading&hellip;</div>'; });
-  var j = await (await fetch('/api/poll/pair')).json();
-  if (!j.a || !j.b) { document.getElementById('card-a').innerHTML = '<div class="poll-loading">Not enough designs yet.</div>'; return; }
-  ['a','b'].forEach(function(k){
-    var d = j[k];
-    var card = document.getElementById('card-'+k);
-    card.innerHTML = '<img src="'+d.image_url+'" alt="'+d.title+'"><div class="meta"><div class="title">'+d.title+'</div><div class="sub">'+(d.category||'')+'</div></div>';
-    card.onclick = function(evt){ vote(d.id, j[k==='a'?'b':'a'].id, evt, {id: d.id, category: d.category, dominant_hex: d.dominant_hex}); };
-  });
-  try {
-    var s = await (await fetch('/api/poll/stats')).json();
-    if (s && s.total_pairs != null) document.getElementById('poll-stats').textContent = s.total_pairs + ' total votes cast · ' + (s.designs_polled||0) + ' designs in rotation';
-  } catch(e) {}
-}
-async function vote(winner, loser, evt, winnerCtx) {
-  document.getElementById('card-a').style.opacity = 0.4;
-  document.getElementById('card-b').style.opacity = 0.4;
-  if (window.WC) WC.reward('pickA', evt, winnerCtx);
-  await fetch('/api/poll/vote', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({winner: winner, loser: loser})});
-  setTimeout(loadPair, 400);
-  setTimeout(function(){ document.getElementById('card-a').style.opacity = 1; document.getElementById('card-b').style.opacity = 1; loadLB(); }, 500);
-}
-async function loadLB() {
-  var lb = await (await fetch('/api/leaderboard?limit=20')).json();
-  document.getElementById('leaderboard').innerHTML = lb.map(function(d,i){
-    var fn = (d.local_path||'').split('/').pop();
-    var img = '/designs/img/' + fn;
-    var ai = d.combined_score ? '★'+d.combined_score+'AI' : '';
-    var u  = d.user_score_avg ? ' · ★'+d.user_score_avg+' (×'+d.user_vote_count+')' : '';
-    var elo = ' · '+Math.round(d.elo)+'elo';
-    return '<a href="/design/'+d.id+'" class="lb-card"><img src="'+img+'" loading="lazy"><div class="meta"><div><span class="rank">#'+(i+1)+'</span> · ID '+d.id+'</div><div class="score">'+ai+u+elo+'</div></div></a>';
-  }).join('') || '<p style="grid-column:1/-1;text-align:center;color:#999">No designs yet.</p>';
-}
-document.getElementById('btn-skip').addEventListener('click', loadPair);
-loadPair(); loadLB();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /play — gamification hub picker (3 modes)
-app.get('/play', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Play — wallco.ai',
-    description: 'Three ways to teach our AI your taste. Swipe, vote, or pick favorites.',
-    canonical: 'https://wallco.ai/play'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:40px 28px;max-width:1100px;margin:0 auto;text-align:center">
-  <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:40px;margin:0 0 8px">Play</h1>
-  <p style="color:#666;margin:0 0 32px">Three quick ways to teach the system your taste. Every gesture trains what we generate next.</p>
-  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:18px">
-    <a href="/swipe" class="play-card">
-      <div class="play-icon">♥</div>
-      <h2>Swipe</h2>
-      <p>Tinder-style card stack. Drag right to love, left to skip, up to super-love. The most addictive of the three.</p>
-      <span class="play-cta">Start swiping &rarr;</span>
-    </a>
-    <a href="/hot-or-not" class="play-card">
-      <div class="play-icon">🔥</div>
-      <h2>Hot or Not</h2>
-      <p>One design at a time. HOT or NOT. The fastest way to teach the system. Arrow keys for speed.</p>
-      <span class="play-cta">Start rating &rarr;</span>
-    </a>
-    <a href="/poll" class="play-card">
-      <div class="play-icon">⚖</div>
-      <h2>Pick a favorite</h2>
-      <p>Two designs, side-by-side. Click the one you'd actually hang. Builds an Elo ranking across the whole catalog.</p>
-      <span class="play-cta">Start picking &rarr;</span>
-    </a>
-  </div>
-  <div style="margin-top:40px;padding:18px;background:#1a1816;color:#e8e2d6;border-radius:8px">
-    <h3 style="margin:0 0 6px;font-family:'Cormorant Garamond',serif;font-weight:300;font-size:20px;color:#d2b15c">How the learning works</h3>
-    <p style="margin:0;font-size:13px;color:#bba;line-height:1.55">Every vote (♥ / 🔥 / ⚖) lands in <code style="background:rgba(255,255,255,.06);padding:1px 5px;border-radius:3px">wallco_votes</code> or <code style="background:rgba(255,255,255,.06);padding:1px 5px;border-radius:3px">wallco_poll_pairs</code>. Every 30 min, the system aggregates the top-20 ranked designs and writes a high-weight recipe back into the generator. The 3-min cron then picks that recipe more often. Your taste literally bends the next 480 designs we render today.</p>
-  </div>
-</main>
-${FOOTER}
-<style>
-  .play-card { display:flex; flex-direction:column; align-items:center; text-align:center; padding:28px 22px; background:#fff; border:1px solid #e5dec9; border-radius:12px; text-decoration:none; color:inherit; transition:transform .15s, box-shadow .15s, border-color .15s; }
-  .play-card:hover { transform:translateY(-3px); box-shadow:0 16px 40px rgba(0,0,0,.12); border-color:#d2b15c; }
-  .play-icon { font-size:38px; margin-bottom:8px; }
-  .play-card h2 { font-family:'Cormorant Garamond',serif; font-weight:300; font-size:26px; margin:0 0 8px; }
-  .play-card p { font-size:13px; color:#666; line-height:1.5; margin:0 0 14px; }
-  .play-cta { font-size:12px; color:#d2b15c; text-transform:uppercase; letter-spacing:.08em; font-weight:600; }
-</style>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── GET /api/design/random — random rate-able design for hot-or-not + swipe.
-// Filters to designs whose image file actually exists on disk so the UI never
-// renders a broken-image card. Resolves the basename of local_path against
-// IMG_DIR; if it's not there, skip and try again. Bounded retries so the
-// loop can't run away if the entire table is broken.
-app.get('/api/design/random', (req, res) => {
-  const excl = String(req.query.exclude || '').split(',').map(x => parseInt(x,10)).filter(Number.isFinite);
-  const exclSet = new Set(excl);
-  try {
-    // Pull a small candidate pool, then filter by file existence client-side.
-    const exclSql = excl.length ? `AND id NOT IN (${excl.join(',')})` : '';
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (SELECT id, dominant_hex, category, local_path
-      FROM spoon_all_designs WHERE local_path IS NOT NULL ${exclSql}
-      ORDER BY random() LIMIT 30) t;`);
-    const pool = JSON.parse(raw || '[]');
-    for (const d of pool) {
-      if (exclSet.has(d.id)) continue;
-      const fn = (d.local_path || '').split('/').pop();
-      if (!fn) continue;
-      const abs = path.join(IMG_DIR, fn);
-      try { if (!fs.existsSync(abs)) continue; } catch { continue; }
-      return res.json({
-        id: d.id,
-        title: titleFor(d.category, d.dominant_hex, d.id),
-        image_url: `/designs/img/${fn}`,
-        category: d.category,
-        dominant_hex: d.dominant_hex
-      });
-    }
-    return res.status(404).json({ error: 'no renderable design found in pool of 30' });
-  } catch (e) { res.status(500).json({ error: e.message }); }
-});
-
-// ── Hot-or-Not: per-session recent picks + historic rejection-reason chips.
-// Recent picks are derived from wallco_votes (last 6 by voter_session). Skip
-// rows whose image file no longer exists. Always returns up to 6 — the UI
-// pads to 6 slots with skeletons if fewer come back.
-app.get('/api/hot-or-not/recent', (req, res) => {
-  const session = voterSession(req, res);
-  try {
-    const limit = Math.max(1, Math.min(parseInt(req.query.limit || '6', 10), 24));
-    const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-      SELECT v.design_id AS id, v.score, v.created_at,
-             d.local_path, d.dominant_hex, d.category
-      FROM wallco_votes v
-      JOIN spoon_all_designs d ON d.id = v.design_id
-      WHERE v.voter_session = '${session.replace(/'/g, "''")}'
-        AND d.local_path IS NOT NULL
-      ORDER BY v.created_at DESC LIMIT ${limit * 3}
-    ) t;`);
-    const rows = JSON.parse(raw || '[]');
-    const out = [];
-    for (const r of rows) {
-      if (out.length >= limit) break;
-      const fn = (r.local_path || '').split('/').pop();
-      if (!fn) continue;
-      try { if (!fs.existsSync(path.join(IMG_DIR, fn))) continue; } catch { continue; }
-      out.push({
-        id: r.id,
-        score: r.score,
-        action: r.score >= 4 ? 'hot' : r.score <= 2 ? 'not' : 'skip',
-        title: titleFor(r.category, r.dominant_hex, r.id),
-        image_url: `/designs/img/${fn}`,
-        created_at: r.created_at
-      });
-    }
-    res.json({ picks: out });
-  } catch (e) { res.status(500).json({ error: e.message, picks: [] }); }
-});
-
-// Default rejection reasons — used when the historic top-N is too thin OR the
-// reasons column hasn't been migrated yet. Kept short + scannable. The
-// dynamic-from-history layer merges these with whatever's most-clicked.
-const HON_DEFAULT_REASONS = [
-  'Colors clash',
-  'Pattern feels AI-generated / artifacts',
-  'Too busy',
-  'Too plain',
-  'Wrong scale for a wall',
-  "Doesn't tile cleanly",
-  'Cliché / overdone motif',
-  'Off-trend / dated',
-  'Resolution looks low',
-  'Wrong palette for me',
-  'Composition feels off',
-  'Tags / category seem wrong'
-];
-
-app.get('/api/hot-or-not/reasons', (_req, res) => {
-  try {
-    // If the column exists, prefer the live top-N (mixed with defaults to
-    // backfill). If not, fall through to the defaults silently.
-    const colExists = psqlQuery(`SELECT 1 FROM information_schema.columns WHERE table_name='wallco_votes' AND column_name='reasons' LIMIT 1;`);
-    if (colExists) {
-      const raw = psqlQuery(`SELECT COALESCE(json_agg(t), '[]'::json) FROM (
-        SELECT unnest(reasons) AS reason, COUNT(*) AS n
-        FROM wallco_votes WHERE reasons IS NOT NULL AND array_length(reasons,1) > 0
-        GROUP BY 1 ORDER BY n DESC LIMIT 12
-      ) t;`);
-      const live = JSON.parse(raw || '[]').map(r => r.reason).filter(Boolean);
-      const seen = new Set(live.map(s => s.toLowerCase()));
-      const merged = live.concat(HON_DEFAULT_REASONS.filter(d => !seen.has(d.toLowerCase()))).slice(0, 12);
-      return res.json({ reasons: merged, source: live.length ? 'historic+defaults' : 'defaults' });
-    }
-    res.json({ reasons: HON_DEFAULT_REASONS, source: 'defaults' });
-  } catch (e) {
-    res.json({ reasons: HON_DEFAULT_REASONS, source: 'defaults-fallback', error: e.message });
-  }
-});
-
-// ── /hot-or-not — rapid-fire HOT/NOT/SKIP single-card
-app.get('/hot-or-not', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Hot or Not — wallco.ai',
-    description: 'Quick taste game. HOT or NOT for every wallpaper, every vote teaches the system.',
-    canonical: 'https://wallco.ai/hot-or-not'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:24px 28px;max-width:760px;margin:0 auto;text-align:center">
-  <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:34px;margin:0 0 6px">Hot or Not</h1>
-  <p style="color:#666;margin:0 0 22px;font-size:13px">Speed-vote on AI-original wallpaper. Arrow ← NOT · Arrow → HOT · Space SKIP.</p>
-  <div id="streak" style="margin-bottom:8px;font-size:12px;color:#d2b15c;font-weight:500"></div>
-  <div id="wc-game-banner" style="background:#1a1816;border-radius:10px;padding:14px 18px;margin-bottom:18px;border:1px solid rgba(210,177,92,.2)"></div>
-  <div id="hon-card" style="position:relative;border:1px solid rgba(0,0,0,.1);border-radius:14px;overflow:hidden;background:#1a1816;box-shadow:0 20px 60px rgba(0,0,0,.18);transition:transform .26s ease, opacity .26s ease">
-    <img id="hon-img" alt="design" style="display:block;width:100%;aspect-ratio:1;object-fit:cover">
-    <div id="hon-meta" style="padding:14px 20px;color:#e8e2d6;text-align:left;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px">
-      <div>
-        <div id="hon-title" style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:22px"></div>
-        <div id="hon-sub"   style="font-size:11px;color:#888;text-transform:uppercase;letter-spacing:.06em;margin-top:3px"></div>
-      </div>
-      <a id="hon-view" target="_blank" style="font-size:11px;color:#d2b15c;text-decoration:underline" rel="noopener noreferrer">view full &rarr;</a>
-    </div>
-  </div>
-  <div id="hon-buttons" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-top:18px">
-    <button class="hon-btn hon-not" data-action="not">NOT</button>
-    <button class="hon-btn hon-skip" data-action="skip">SKIP</button>
-    <button class="hon-btn hon-hot" data-action="hot">HOT</button>
-  </div>
-  <div id="hon-feed" style="margin-top:30px;text-align:left">
-    <h3 style="font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:#888;margin:0 0 8px">Your last 6 picks</h3>
-    <div id="hon-history" style="display:grid;grid-template-columns:repeat(6,1fr);gap:6px"></div>
-  </div>
-</main>
-
-<!-- NOT modal — reason checkboxes (top-N historic merged w/ defaults) + free-text note -->
-<div id="hon-not-modal" role="dialog" aria-modal="true" aria-labelledby="hon-not-title" style="display:none;position:fixed;inset:0;background:rgba(15,14,12,.78);z-index:9999;align-items:center;justify-content:center;padding:18px;backdrop-filter:blur(4px)">
-  <div style="background:#fbfaf6;color:#1a1a1a;max-width:480px;width:100%;border-radius:14px;box-shadow:0 28px 80px rgba(0,0,0,.5);overflow:hidden">
-    <div style="padding:18px 22px;border-bottom:1px solid #e6e2d8;display:flex;justify-content:space-between;align-items:center">
-      <h2 id="hon-not-title" style="margin:0;font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px">Why not?</h2>
-      <button id="hon-not-cancel" aria-label="Close" style="background:transparent;border:0;font-size:22px;cursor:pointer;color:#888;line-height:1;padding:4px 8px">×</button>
-    </div>
-    <div style="padding:14px 22px 6px;font-size:12px;color:#666">Pick any that apply. Add a note if you want to teach the system.</div>
-    <div id="hon-not-reasons" style="padding:8px 22px 4px;display:grid;grid-template-columns:1fr 1fr;gap:6px 14px;font-size:13px"></div>
-    <div style="padding:8px 22px 4px">
-      <label style="font-size:11px;color:#888;text-transform:uppercase;letter-spacing:.06em">Note for the LLM <span style="text-transform:none;color:#aaa">— optional</span></label>
-      <textarea id="hon-not-note" rows="3" placeholder="e.g., the scale is too tight for a master bedroom; pattern feels stamped, not drawn" style="width:100%;margin-top:6px;padding:10px 12px;border:1px solid #d8d2c5;border-radius:8px;font:13px/1.4 system-ui, sans-serif;background:#fff;resize:vertical;box-sizing:border-box"></textarea>
-    </div>
-    <div style="padding:14px 22px 18px;display:flex;justify-content:flex-end;gap:8px">
-      <button id="hon-not-skip" style="padding:10px 16px;border:1px solid #d8d2c5;background:#fff;border-radius:8px;cursor:pointer;font-size:13px;color:#666">Skip — just NOT</button>
-      <button id="hon-not-submit" style="padding:10px 18px;border:0;background:#c44;color:#fff;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;letter-spacing:.04em">Submit NOT</button>
-    </div>
-  </div>
-</div>
-${FOOTER}
-<style>
-  .hon-btn { padding:18px 12px; font-size:14px; font-weight:600; letter-spacing:.1em; border:0; border-radius:10px; cursor:pointer; transition:transform .12s, background .15s; }
-  .hon-not  { background:#fee; color:#b00; border:1px solid #fbb; }
-  .hon-not:hover { background:#fcc; transform:scale(1.03) }
-  .hon-skip { background:#eee; color:#666; border:1px solid #ddd; }
-  .hon-skip:hover { background:#ddd; transform:scale(1.03) }
-  .hon-hot { background:#d2b15c; color:#1a1a1a; border:1px solid #b89745; }
-  .hon-hot:hover { background:#e0bd64; transform:scale(1.03) }
-  #hon-card.swipe-left { transform:translateX(-120%) rotate(-14deg); opacity:0 }
-  #hon-card.swipe-right { transform:translateX(120%) rotate(14deg); opacity:0 }
-  #hon-card.swipe-up { transform:translateY(-120%); opacity:0 }
-  .hist-card { display:block; aspect-ratio:1; border-radius:5px; overflow:hidden; border:2px solid transparent; background:#f4f1ea }
-  .hist-card.hot  { border-color:#d2b15c }
-  .hist-card.not  { border-color:#c44 }
-  .hist-card.skip { border-color:#888 }
-  .hist-card.empty { border:1px dashed #d8d2c5; background:repeating-linear-gradient(135deg,#f4f1ea 0 8px,#ede9de 8px 16px) }
-  .hist-card img { width:100%; height:100%; object-fit:cover; display:block }
-  .hon-reason-chip { display:flex; align-items:center; gap:8px; padding:7px 10px; border:1px solid #e6e2d8; border-radius:8px; cursor:pointer; background:#fff; transition:background .12s, border-color .12s; user-select:none }
-  .hon-reason-chip:hover { background:#faf6ec }
-  .hon-reason-chip.checked { background:#fde6e2; border-color:#e8aaa5 }
-  .hon-reason-chip input { margin:0 }
-</style>
-<script>
-var seen = [], picks = [];  /* named picks because window.history shadows a global */
-var streak = parseInt(localStorage.getItem('wc-streak') || '0', 10);
-var todayKey = 'wc-day-' + new Date().toISOString().slice(0,10);
-var todayCount = parseInt(localStorage.getItem(todayKey) || '0', 10);
-function renderStreak(){ document.getElementById('streak').textContent = '🔥 ' + streak + ' in a row · ' + todayCount + ' votes today'; }
-renderStreak();
-
-/* Always show 6 thumb slots. Empty slots are dashed placeholders. */
-function renderHistory(){
-  var grid = document.getElementById('hon-history');
-  var html = '';
-  for (var i = 0; i < 6; i++) {
-    var h = picks[i];
-    if (h) {
-      html += '<a class="hist-card '+h.action+'" href="/design/'+h.id+'" title="'+h.title.replace(/"/g,'&quot;')+'">'
-            + '<img src="'+h.image_url+'" alt="'+h.title.replace(/"/g,'&quot;')+'" onerror="this.parentNode.classList.add(\\'empty\\');this.remove()">'
-            + '</a>';
-    } else {
-      html += '<span class="hist-card empty" aria-label="empty slot"></span>';
-    }
-  }
-  grid.innerHTML = html;
-}
-renderHistory();
-
-/* Hydrate from server-side history so the row is always populated. */
-(async function hydrateRecent(){
-  try {
-    var r = await (await fetch('/api/hot-or-not/recent?limit=6')).json();
-    if (r && Array.isArray(r.picks) && r.picks.length) {
-      picks = r.picks.map(function(p){ return { id:p.id, action:p.action, title:p.title || ('Design #'+p.id), image_url:p.image_url }; });
-      r.picks.forEach(function(p){ if (!seen.includes(p.id)) seen.push(p.id); });
-      renderHistory();
-    }
-  } catch(e) { /* non-fatal */ }
-})();
-
-async function load(){
-  var url = '/api/design/random' + (seen.length ? '?exclude=' + seen.slice(-30).join(',') : '');
-  var r = await (await fetch(url)).json();
-  if (!r.id) return;
-  document.getElementById('hon-card').className = '';
-  document.getElementById('hon-img').src = r.image_url;
-  document.getElementById('hon-title').textContent = r.title;
-  document.getElementById('hon-sub').textContent = (r.category||'') + (r.dominant_hex ? ' · ' + r.dominant_hex : '');
-  document.getElementById('hon-view').href = '/design/' + r.id;
-  document.getElementById('hon-card').dataset.id = r.id;
-  document.getElementById('hon-card').dataset.title = r.title;
-  document.getElementById('hon-card').dataset.image = r.image_url;
-  document.getElementById('hon-card').dataset.category = r.category || '';
-  document.getElementById('hon-card').dataset.hex = r.dominant_hex || '';
-  seen.push(r.id);
-}
-
-/* ---------- NOT modal ---------- */
-var honReasonsCache = null;
-async function ensureReasons(){
-  if (honReasonsCache) return honReasonsCache;
-  try {
-    var r = await (await fetch('/api/hot-or-not/reasons')).json();
-    honReasonsCache = (r && Array.isArray(r.reasons)) ? r.reasons : [];
-  } catch(e) { honReasonsCache = []; }
-  return honReasonsCache;
-}
-
-async function openNotModal(){
-  var reasons = await ensureReasons();
-  var box = document.getElementById('hon-not-reasons');
-  box.innerHTML = reasons.map(function(r, i){
-    var v = r.replace(/"/g, '&quot;');
-    return '<label class="hon-reason-chip" data-reason="'+v+'"><input type="checkbox" value="'+v+'">'+r+'</label>';
-  }).join('');
-  /* toggle .checked on chip when its input flips */
-  box.querySelectorAll('.hon-reason-chip').forEach(function(chip){
-    var input = chip.querySelector('input');
-    chip.addEventListener('click', function(e){
-      if (e.target !== input) { input.checked = !input.checked; }
-      chip.classList.toggle('checked', input.checked);
-    });
-  });
-  document.getElementById('hon-not-note').value = '';
-  var m = document.getElementById('hon-not-modal');
-  m.style.display = 'flex';
-  setTimeout(function(){ document.getElementById('hon-not-note').focus(); }, 60);
-}
-function closeNotModal(){ document.getElementById('hon-not-modal').style.display = 'none'; }
-
-function collectModalPayload(){
-  var reasons = [].slice.call(document.querySelectorAll('#hon-not-reasons input:checked')).map(function(i){ return i.value; });
-  var note = (document.getElementById('hon-not-note').value || '').trim();
-  return { reasons: reasons, note: note };
-}
-
-async function act(action, evt){
-  var card = document.getElementById('hon-card');
-  var id = parseInt(card.dataset.id, 10);
-  if (!id) return;
-
-  /* NOT opens the reason modal — actual vote fires from the modal's submit/skip. */
-  if (action === 'not') { window._honPendingId = id; openNotModal(); return; }
-
-  var anim = action==='hot' ? 'swipe-right' : 'swipe-up';
-  card.classList.add(anim);
-  if (action === 'hot') {
-    var score = 5;
-    fetch('/api/design/' + id + '/user-vote', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({score: score, quick: true}) });
-    streak = streak + 1;
-    localStorage.setItem('wc-streak', String(streak));
-  }
-  todayCount++; localStorage.setItem(todayKey, String(todayCount));
-  renderStreak();
-  if (window.WC) WC.reward(action, evt, {id: id, category: card.dataset.category, dominant_hex: card.dataset.hex});
-  picks.unshift({id: id, action: action, title: card.dataset.title, image_url: card.dataset.image});
-  picks = picks.slice(0, 6);
-  renderHistory();
-  setTimeout(load, 280);
-}
-
-/* NOT-with-reasons path: invoked from the modal. Mirrors act('not') flow
-   but carries reasons + note to the server. */
-function submitNot(reasons, note, evt){
-  var id = window._honPendingId;
-  if (!id) { closeNotModal(); return; }
-  window._honPendingId = null;
-  var card = document.getElementById('hon-card');
-  card.classList.add('swipe-left');
-  fetch('/api/design/' + id + '/user-vote', {
-    method:'POST', headers:{'Content-Type':'application/json'},
-    body: JSON.stringify({ score: 1, quick: false, reasons: reasons || [], note: note || '' })
-  });
-  streak = 0; localStorage.setItem('wc-streak', String(streak));
-  todayCount++; localStorage.setItem(todayKey, String(todayCount));
-  renderStreak();
-  if (window.WC) WC.reward('not', evt, {id: id, category: card.dataset.category, dominant_hex: card.dataset.hex});
-  picks.unshift({ id: id, action: 'not', title: card.dataset.title, image_url: card.dataset.image });
-  picks = picks.slice(0, 6);
-  renderHistory();
-  closeNotModal();
-  setTimeout(load, 280);
-}
-
-document.querySelectorAll('.hon-btn').forEach(function(b){ b.addEventListener('click', function(evt){ act(b.dataset.action, evt); }); });
-document.getElementById('hon-not-cancel').addEventListener('click', closeNotModal);
-document.getElementById('hon-not-skip').addEventListener('click', function(evt){ submitNot([], '', evt); });
-document.getElementById('hon-not-submit').addEventListener('click', function(evt){ var p = collectModalPayload(); submitNot(p.reasons, p.note, evt); });
-document.getElementById('hon-not-modal').addEventListener('click', function(e){ if (e.target === this) closeNotModal(); });
-
-document.addEventListener('keydown', function(e){
-  var modalOpen = document.getElementById('hon-not-modal').style.display === 'flex';
-  if (modalOpen) {
-    if (e.key === 'Escape') { e.preventDefault(); closeNotModal(); }
-    else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); var p = collectModalPayload(); submitNot(p.reasons, p.note); }
-    return;
-  }
-  if (e.key === 'ArrowLeft') act('not');
-  else if (e.key === 'ArrowRight') act('hot');
-  else if (e.key === ' ' || e.key === 'ArrowUp') { e.preventDefault(); act('skip'); }
-});
-load();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── /swipe — Tinder-style drag-to-swipe card stack
-app.get('/swipe', (_req, res) => {
-  res.type('html').send(`${htmlHead({
-    title: 'Swipe — wallco.ai',
-    description: 'Swipe right to love, left to skip, up to super-love. Every gesture teaches the system.',
-    canonical: 'https://wallco.ai/swipe'
-  })}
-<body>
-${htmlHeader('')}
-<main style="padding:18px 16px;max-width:520px;margin:0 auto;text-align:center">
-  <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:30px;margin:0 0 4px">Swipe</h1>
-  <p style="color:#666;margin:0 0 14px;font-size:12px">&larr; skip · &rarr; love · &uarr; super-love · drag the card · or use the buttons.</p>
-  <div id="sw-streak" style="margin-bottom:8px;font-size:12px;color:#d2b15c;font-weight:500"></div>
-  <div id="wc-game-banner" style="background:#1a1816;border-radius:10px;padding:14px 18px;margin-bottom:14px;border:1px solid rgba(210,177,92,.2)"></div>
-  <div id="sw-stack" style="position:relative;height:clamp(380px, 70vh, 560px);margin:0 auto;max-width:420px"></div>
-  <div id="sw-buttons" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:14px;max-width:380px;margin-left:auto;margin-right:auto">
-    <button class="sw-btn sw-skip"  data-action="left"  aria-label="Skip">&#10005;</button>
-    <button class="sw-btn sw-super" data-action="up"    aria-label="Super-love">&#9733;</button>
-    <button class="sw-btn sw-love"  data-action="right" aria-label="Love">&#9829;</button>
-  </div>
-  <div id="sw-toast" style="margin-top:18px;min-height:22px;font-size:13px;color:#666"></div>
-</main>
-${FOOTER}
-<style>
-  .sw-card { position:absolute; inset:0; border-radius:18px; overflow:hidden; background:#1a1816; color:#e8e2d6; box-shadow:0 18px 50px rgba(0,0,0,.22); cursor:grab; user-select:none; transition:transform .26s ease, opacity .26s ease; touch-action:none; }
-  .sw-card.dragging { transition:none; cursor:grabbing; }
-  .sw-card img { display:block; width:100%; aspect-ratio:1; object-fit:cover; pointer-events:none; }
-  .sw-card .meta { padding:12px 18px; display:flex; justify-content:space-between; align-items:center; gap:10px; }
-  .sw-card .title { font-family:'Cormorant Garamond',serif; font-weight:300; font-size:20px; }
-  .sw-card .sub { font-size:10px; color:#888; text-transform:uppercase; letter-spacing:.06em; }
-  .sw-card .stamp { position:absolute; top:30px; padding:8px 18px; border-radius:6px; font-weight:700; font-size:18px; letter-spacing:.12em; opacity:0; transition:opacity .15s; border:3px solid currentColor; }
-  .sw-card .stamp-love { right:24px; color:#d2b15c; transform:rotate(12deg); }
-  .sw-card .stamp-skip { left:24px;  color:#c44;    transform:rotate(-12deg); }
-  .sw-card .stamp-super { left:50%; top:50%; transform:translate(-50%,-50%); color:#5aa; font-size:24px; }
-  .sw-card.show-love .stamp-love  { opacity:1 }
-  .sw-card.show-skip .stamp-skip  { opacity:1 }
-  .sw-card.show-super .stamp-super { opacity:1 }
-  .sw-btn { padding:18px; font-size:22px; font-weight:600; border:0; border-radius:50%; aspect-ratio:1; cursor:pointer; transition:transform .12s; }
-  .sw-skip  { background:#fee; color:#c44; border:1px solid #fbb; }
-  .sw-super { background:#e7f5f5; color:#5aa; border:1px solid #aee; }
-  .sw-love  { background:#d2b15c; color:#1a1a1a; border:1px solid #b89745; }
-  .sw-btn:hover { transform:scale(1.08); }
-</style>
-<script>
-var stack = [], swSeen = [];
-var swStreak = parseInt(localStorage.getItem('wc-sw-streak') || '0', 10);
-var swDayKey = 'wc-sw-day-' + new Date().toISOString().slice(0,10);
-var swToday = parseInt(localStorage.getItem(swDayKey) || '0', 10);
-function renderStreak(){ document.getElementById('sw-streak').textContent = '🔥 ' + swStreak + ' loves in a row · ' + swToday + ' swipes today'; }
-renderStreak();
-
-async function fillStack(){
-  while (stack.length < 3) {
-    var r = await (await fetch('/api/design/random?exclude=' + swSeen.slice(-30).join(','))).json();
-    if (!r.id) break;
-    swSeen.push(r.id);
-    stack.push(r);
-  }
-  renderStack();
-}
-function renderStack(){
-  var s = document.getElementById('sw-stack');
-  s.innerHTML = '';
-  for (var i = Math.min(stack.length-1, 2); i >= 0; i--) {
-    var d = stack[i];
-    var card = document.createElement('div');
-    card.className = 'sw-card';
-    card.dataset.id = d.id;
-    card.dataset.category = d.category || '';
-    card.dataset.hex = d.dominant_hex || '';
-    card.style.zIndex = 100 - i;
-    card.style.transform = 'translateY(' + (i * 6) + 'px) scale(' + (1 - i*0.03) + ')';
-    card.innerHTML = '<img src="'+d.image_url+'" alt="'+d.title+'">' +
-      '<div class="meta"><div><div class="title">'+d.title+'</div><div class="sub">'+(d.category||'')+'</div></div>' +
-      '<a href="/design/'+d.id+'" target="_blank" style="font-size:10px;color:#d2b15c;text-decoration:underline" rel="noopener noreferrer">view</a></div>' +
-      '<div class="stamp stamp-love">LOVE</div><div class="stamp stamp-skip">SKIP</div><div class="stamp stamp-super">&#9733; SUPER</div>';
-    s.appendChild(card);
-    if (i === 0) attachDrag(card);
-  }
-  if (stack.length === 0) s.innerHTML = '<div style="padding:40px;color:#888">No more designs in rotation.</div>';
-}
-function attachDrag(card){
-  var startX=0, startY=0, dx=0, dy=0, dragging=false, touchId=null;
-  function pt(e){
-    if (e.touches && e.touches.length) {
-      for (var i=0; i<e.touches.length; i++) if (e.touches[i].identifier === touchId) return e.touches[i];
-      return e.touches[0];
-    }
-    if (e.changedTouches && e.changedTouches.length) return e.changedTouches[0];
-    return e;
-  }
-  function onStart(e){
-    if (dragging) return;
-    if (e.touches && e.touches.length) touchId = e.touches[0].identifier;
-    dragging = true;
-    card.classList.add('dragging');
-    var p = pt(e); startX = p.clientX; startY = p.clientY;
-    if (e.cancelable && e.touches) e.preventDefault();
-  }
-  function onMove(e){
-    if (!dragging) return;
-    if (e.cancelable) e.preventDefault();
-    var p = pt(e);
-    dx = p.clientX - startX; dy = p.clientY - startY;
-    card.style.transform = 'translate(' + dx + 'px,' + dy + 'px) rotate(' + (dx/20) + 'deg)';
-    card.classList.toggle('show-love',  dx >  50);
-    card.classList.toggle('show-skip',  dx < -50);
-    card.classList.toggle('show-super', dy < -70 && Math.abs(dx) < 100);
-  }
-  function onEnd(){
-    if (!dragging) return;
-    dragging = false; touchId = null;
-    card.classList.remove('dragging');
-    if (dy < -110 && Math.abs(dx) < 140) commit('up');
-    else if (dx >  110) commit('right');
-    else if (dx < -110) commit('left');
-    else {
-      card.style.transform = '';
-      card.classList.remove('show-love','show-skip','show-super');
-    }
-  }
-  // Mouse — desktop + Playwright synthetic
-  card.addEventListener('mousedown', onStart);
-  document.addEventListener('mousemove', onMove);
-  document.addEventListener('mouseup',   onEnd);
-  // Touch — attach MOVE + END to document so the gesture survives the
-  // finger leaving the card. This was the bug Steve hit on mobile.
-  card.addEventListener('touchstart',  onStart, {passive:false});
-  document.addEventListener('touchmove', onMove, {passive:false});
-  document.addEventListener('touchend',   onEnd);
-  document.addEventListener('touchcancel',onEnd);
-  // Pointer events — covers Surface, Wacom, future Apple Pencil
-  card.addEventListener('pointerdown',  onStart);
-  document.addEventListener('pointermove', onMove);
-  document.addEventListener('pointerup',    onEnd);
-  document.addEventListener('pointercancel',onEnd);
-}
-function commit(direction, evt){
-  var card = document.querySelector('.sw-card');
-  if (!card) return;
-  var id = parseInt(card.dataset.id, 10);
-  var anim = direction==='right' ? 'translateX(140vw) rotate(20deg)'
-           : direction==='left'  ? 'translateX(-140vw) rotate(-20deg)' : 'translateY(-140vh)';
-  card.style.transform = anim; card.style.opacity = 0;
-  var score = direction==='left' ? 1 : 5;
-  var quick = direction !== 'up';
-  fetch('/api/design/' + id + '/user-vote', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({score: score, quick: quick}) });
-  // Hook into the gamification engine
-  var wcAction = direction==='right' ? 'love' : direction==='up' ? 'super' : 'skip';
-  if (window.WC) WC.reward(wcAction, evt, {id: id, category: card.dataset.category, dominant_hex: card.dataset.hex});
-  if (direction === 'right' || direction === 'up') {
-    swStreak++;
-    document.getElementById('sw-toast').innerHTML = direction==='up' ? '&#9733; Super-love recorded' : '&#9829; Love recorded · streak ' + swStreak;
-  } else { swStreak = 0; document.getElementById('sw-toast').innerHTML = '&#10005; Skipped'; }
-  localStorage.setItem('wc-sw-streak', String(swStreak));
-  swToday++; localStorage.setItem(swDayKey, String(swToday));
-  renderStreak();
-  stack.shift();
-  setTimeout(function(){ fillStack(); }, 260);
-}
-document.querySelectorAll('.sw-btn').forEach(function(b){ b.addEventListener('click', function(evt){ commit(b.dataset.action, evt); }); });
-document.addEventListener('keydown', function(e){
-  if (e.key === 'ArrowLeft')  commit('left');
-  else if (e.key === 'ArrowRight') commit('right');
-  else if (e.key === 'ArrowUp') { e.preventDefault(); commit('up'); }
-});
-fillStack();
-</script>
-${HAMBURGER_JS}
-</body>
-</html>`);
-});
-
-// ── HEALTH
-app.get('/health', (_req, res) => {
-  res.json({ ok: true, site: SITE, count: DESIGNS.length, port: PORT });
-});
-
-// ── /admin/health — admin-gated extended health + by-color cache stats. Useful to
-// confirm the in-process ΔE cache is actually serving HITs and that eviction is bounded.
-// 404 (not 401/403) on non-admin so we don't leak the endpoint's existence.
-app.get('/admin/health', (req, res) => {
-  if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
-  const total = _byColorStats.hits + _byColorStats.misses;
-  res.setHeader('Cache-Control', 'no-store');
-  res.json({
-    ok: true,
-    site: SITE,
-    port: PORT,
-    designs: DESIGNS.length,
-    uptime_s: Math.round(process.uptime()),
-    rss_mb: Math.round(process.memoryUsage().rss / 1048576),
-    by_color_cache: {
-      size: _byColorCache.size,
-      max: _BY_COLOR_MAX,
-      ttl_ms: _BY_COLOR_TTL_MS,
-      ver: _byColorCacheVer,
-      hits: _byColorStats.hits,
-      misses: _byColorStats.misses,
-      evictions: _byColorStats.evictions,
-      hit_rate: total > 0 ? Math.round((_byColorStats.hits / total) * 1000) / 10 : null,
-      since: new Date(_byColorStats.since).toISOString(),
-    },
-    random_etag: (() => {
-      const t = _randomStats.hits + _randomStats.misses;
-      return {
-        hits: _randomStats.hits,
-        misses: _randomStats.misses,
-        hit_rate: t > 0 ? Math.round((_randomStats.hits / t) * 1000) / 10 : null,
-        since: new Date(_randomStats.since).toISOString(),
-      };
-    })(),
-  });
-});
-
-// ── DEV LOGIN — sets dw_auth HS256 cookie for admin layer + inline editor (dev mode only)
-const __crypto = require('crypto');
-function __b64u(s) { return Buffer.from(s).toString('base64').replace(/=+$/,'').replace(/\+/g,'-').replace(/\//g,'_'); }
-function __sign(secret, payload) {
-  const h = __b64u(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
-  const p = __b64u(JSON.stringify(payload));
-  const sig = __b64u(__crypto.createHmac('sha256', secret).update(h + '.' + p).digest());
-  return `${h}.${p}.${sig}`;
-}
-app.get('/_devlogin', (req, res) => {
-  if ((process.env.AUTH_MODE || 'dev') !== 'dev') return res.status(403).send('dev login disabled in prod');
-  const secret = process.env.AUTH_DEV_SECRET || 'wallco-dev-secret-change-for-prod';
-  const exp = Math.floor(Date.now() / 1000) + 8 * 3600;
-  const token = __sign(secret, {
-    email: 'steve@designerwallcoverings.com',
-    role: 'admin',
-    iss: 'https://auth.agentabrams.com',
-    exp
-  });
-  res.cookie('dw_auth', token, { httpOnly: false, maxAge: 8 * 3600 * 1000, sameSite: 'lax' });
-  const next = req.query.next || '/admin';
-  res.type('text/html').send(`<!doctype html><meta charset="utf-8"><title>Admin login</title>
-<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;background:#0f0e0c;color:#e8e2d6;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}.card{background:#1a1816;padding:36px 44px;border-radius:10px;border:1px solid #2a2825;max-width:480px}.card h1{font:300 22px/1.2 'Cormorant Garamond',serif;margin:0 0 6px;color:#d2b15c}.card p{color:#bba;margin:6px 0}a{color:#d2b15c;font-weight:500}</style>
-<div class="card">
-  <h1>Admin login set ✓</h1>
-  <p>Logged in as <code>steve@designerwallcoverings.com</code> · expires in 8 hours.</p>
-  <p><a href="${next}">→ Continue to ${next}</a> · <a href="/_devlogout">Log out</a></p>
-</div>`);
-});
-app.get('/_devlogout', (_req, res) => {
-  res.clearCookie('dw_auth');
-  res.type('text/plain').send('Logged out.\n');
-});
-
-// ── Start
-app.listen(PORT, '0.0.0.0', () => {
-  console.log(`wallco.ai production server → http://0.0.0.0:${PORT}`);
-  console.log(`  Designs loaded: ${DESIGNS.length}`);
-  console.log(`  GA4: ${GA4_ID || '(not set)'}`);
-});

← 3b8b3c1 ghost-detector v2 + Matisse v4 generation prompt  ·  back to Wallco Ai  ·  SEC-1: fix shell injection in POST /api/leads b6ef2b2 →