[object Object]

← back to Model Arena

mood-board-studio: use real DW products + photos instead of synthetic SVG swatches

d3bffed6e76ac9a6e146532256d3ec886b212cdc · 2026-07-30 15:19:04 -0700 · Steve

- Swatch tiles now render real ACTIVE catalog products (dw_unified mirror):
  live cdn.shopify.com photo + real name/vendor/SKU/price, 8 per material,
  rotating so each spawn reveals a different SKU; dbl-click opens the product.
- Procedural SVG textures kept as an onerror fallback (never a broken image).
- PNG export draws the real photos (CORS-clean via crossorigin=anonymous +
  Shopify ACAO:*) so exported boards are untainted; split into
  drawSwatchOnCanvas/drawProceduralSwatch/drawSwatchLabel.
- server.js: widen ONLY the /game route CSP img-src to allow cdn.shopify.com
  (artifact route stays locked to data:/blob:).

Verified in-browser: 6/6 photos load, 0 console errors, canvas export clean.

Files touched

Diff

commit d3bffed6e76ac9a6e146532256d3ec886b212cdc
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 30 15:19:04 2026 -0700

    mood-board-studio: use real DW products + photos instead of synthetic SVG swatches
    
    - Swatch tiles now render real ACTIVE catalog products (dw_unified mirror):
      live cdn.shopify.com photo + real name/vendor/SKU/price, 8 per material,
      rotating so each spawn reveals a different SKU; dbl-click opens the product.
    - Procedural SVG textures kept as an onerror fallback (never a broken image).
    - PNG export draws the real photos (CORS-clean via crossorigin=anonymous +
      Shopify ACAO:*) so exported boards are untainted; split into
      drawSwatchOnCanvas/drawProceduralSwatch/drawSwatchLabel.
    - server.js: widen ONLY the /game route CSP img-src to allow cdn.shopify.com
      (artifact route stays locked to data:/blob:).
    
    Verified in-browser: 6/6 photos load, 0 console errors, canvas export clean.
---
 public/games/dw-mood-board-studio.html | 144 +++++++++++++++++++++++++++++----
 server.js                              |   3 +-
 2 files changed, 132 insertions(+), 15 deletions(-)

diff --git a/public/games/dw-mood-board-studio.html b/public/games/dw-mood-board-studio.html
index 538ccf8..9e92c42 100644
--- a/public/games/dw-mood-board-studio.html
+++ b/public/games/dw-mood-board-studio.html
@@ -408,15 +408,40 @@ header {
 }
 
 .swatch-tile .tile-label {
-  background: rgba(0,0,0,0.45);
-  color: rgba(255,255,255,0.90);
+  background: rgba(0,0,0,0.52);
+  color: rgba(255,255,255,0.92);
   backdrop-filter: blur(4px);
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+  line-height: 1.18;
+}
+.swatch-tile .tl-name {
+  font-weight: 600;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.swatch-tile .tl-vendor {
+  font-size: 8px;
+  letter-spacing: .02em;
+  opacity: 0.72;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 .swatch-svg {
   width: 100%;
   height: 100%;
 }
+.swatch-img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  display: block;
+  background: #1a1712;
+}
 
 /* ============================================================
    COLOR CHIP TILES
@@ -877,6 +902,31 @@ header {
 <div class="toast" id="toast" role="status" aria-live="polite"></div>
 
 <script>
+/* ============================================================
+   REAL DW PRODUCTS  (live catalog — dw_unified mirror, status=ACTIVE,
+   real cdn.shopify.com photos). Keyed by material type; each product:
+   { name, vendor, sku, handle, img, price }
+   ============================================================ */
+const PRODUCTS = {"chinoiserie":[{"name":"Jardin Fleuri - Ivory Multi By Brunschwig & Fils | Summer Palace Wallcoverings |Chinoiserie Botanical & Floral Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102285","handle":"dwkk-102285","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019118_192_950782b7-bc9c-4b7a-a0a9-49fae23ee1a0.jpg?v=1725922617","price":1567},{"name":"La Pagode - Teal By Brunschwig & Fils | Les Ensembliers Wallcoverings | Chinoiserie Wallcovering Embroidery","vendor":"Brunschwig & Fils","sku":"DWKK-102331","handle":"dwkk-102331","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8020108_35_f3afd4ba-dbe6-4ac9-a195-b930f188a780.jpg?v=1725922727","price":393},{"name":"Tongli - Fern Multi By Brunschwig & Fils | Summer Palace Wallcoverings |Chinoiserie Botanical & Floral Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102274","handle":"dwkk-102274","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019115_33_a7361bcf-88e8-40df-b0fa-23c228b55301.jpg?v=1725922594","price":1394},{"name":"Ming Dragon Print - Claret Multi By Brunschwig & Fils | Summer Palace |Chinoiserie Novelty Multipurpose Print","vendor":"Brunschwig & Fils","sku":"DWKK-100756","handle":"dwkk-gc1f32da2","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8019140_195_1fb0f24d-239b-41e5-a453-692799b39101.jpg?v=1725919361","price":298},{"name":"Jardin Fleuri - Delft Multi By Brunschwig & Fils | Summer Palace Wallcoverings |Chinoiserie Botanical & Floral Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102282","handle":"dwkk-102282","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019118_153_b7b10748-93ce-4c18-9649-24aa6370d0cc.jpg?v=1725922610","price":1567},{"name":"Ming Dragon - Indigo Multi By Brunschwig & Fils | Summer Palace Wallcoverings | Chinoiserie Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102269","handle":"dwkk-102269","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019113_50_c18abe3c-8f2d-4eda-9618-afb98f493602.jpg?v=1725922585","price":605},{"name":"Tongli - Lagoon Turquoise By Brunschwig & Fils | Summer Palace Wallcoverings |Chinoiserie Botanical & Floral Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102272","handle":"dwkk-102272","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019115_133_9415dc52-c6cd-4caf-8098-030e271046f5.jpg?v=1725922590","price":1394},{"name":"Jardin Fleuri - Opal Multi By Brunschwig & Fils | Summer Palace Wallcoverings |Chinoiserie Botanical & Floral Wallcovering Print","vendor":"Brunschwig & Fils","sku":"DWKK-102283","handle":"dwkk-102283","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8019118_157_3d36ee49-50bf-4eb9-9487-f3ad353c0648.jpg?v=1725922612","price":1567}],"damask":[{"name":"Seville Weave - Navy/Marine Blue By Lee Jofa | Triana Weaves |Damask Texture Upholstery","vendor":"Lee Jofa","sku":"DWKK-135512","handle":"dwkk-gb1fadc09","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2021108_155_edbb8cca-1e09-466e-9d25-7bbb048f1766.jpg?v=1726530135","price":312},{"name":"Acanthus Damask - Blue Blue By Lee Jofa | Oscar De La Renta Iv | Damask Multipurpose","vendor":"Lee Jofa","sku":"DWKK-135434","handle":"dwkk-g7c9cb323","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2020212_1516_93e2821c-87c7-4b44-93e5-cb517b7c85bf.jpg?v=1726529925","price":298},{"name":"Granada Print - Ruby Red By Lee Jofa | Oscar De La Renta Iv | Damask Multipurpose Print","vendor":"Lee Jofa","sku":"DWKK-135458","handle":"dwkk-g989f77c9","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2020220_924_1bb4d925-b513-4eba-9892-2c06fa1e3047.jpg?v=1726529992","price":376},{"name":"Damask Pierre - Spice Orange By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100206","handle":"dwkk-gbf4f8dda","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_440_ea0b5f00-53dd-4f6b-b737-e39f0b24f6f2.jpg?v=1725918203","price":627},{"name":"Hayes Embroidery - Ivory Ivory By Lee Jofa | Bunny Williams Arcadia | Damask Drapery Embroidery","vendor":"Lee Jofa","sku":"DWKK-135611","handle":"dwkk-gbdde20cf","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2022110_1_1f27128b-18c2-40b9-b7a0-db1b5cf4decc.jpg?v=1726530384","price":355},{"name":"Damask Pierre - Eggplant Plum By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100214","handle":"dwkk-g2fda5c11","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_909_94ed9012-9887-4107-9e65-82d2027ee3cc.jpg?v=1725918220","price":627},{"name":"Arnaud Damask - Aqua Light Blue By Brunschwig & Fils | Vienne Silks | Damask Multipurpose Silk","vendor":"Brunschwig & Fils","sku":"DWKK-101383","handle":"dwkk-gff434372","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8023150_13_071e61ae-d6c8-4125-b482-8612dfcae110.jpg?v=1725920760","price":327},{"name":"Gambetta Weave - Rose Coral By Brunschwig & Fils | Alsace Weaves | Damask Upholstery","vendor":"Brunschwig & Fils","sku":"DWKK-100702","handle":"dwkk-g2d18508a","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8019120_197_676b1bc3-4715-4799-848c-48b978414aff.jpg?v=1725919242","price":208}],"grasscloth":[{"name":"Grasscloth Slubs Natura | Phillipe Romano","vendor":"Phillipe Romano","sku":"GRS-1811","handle":"grasscloth-slubs-natura-grs-1811","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/a731e8e333ad16ef3b05543b830e00de_7ec623bc-ba2d-44dd-8884-176af8ed4f39.jpg?v=1745460826","price":23},{"name":"Hula - Black Black By Lee Jofa Modern | Hunt Slonem Ii | Modern Wallcovering Grasscloth","vendor":"Groundworks","sku":"DWKK-139617","handle":"dwkk-139617","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/GWP-3713_84_e688a483-e43f-4d03-b8a3-d9a6741c8a75.jpg?v=1753291483","price":444},{"name":"Diamond Grasscloth Tribal | Phillipe Romano","vendor":"Phillipe Romano","sku":"GRS-1804","handle":"diamond-grasscloth-tribal-grs-1804","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/9014b0c5b6e28bcffc717479cca2f5e0.jpg?v=1745460833","price":23},{"name":"Plantation Grasscloth | Phillipe Romano","vendor":"Phillipe Romano","sku":"GRS-1806","handle":"plantation-grasscloth-grs-1806","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/plantation-grasscloth-grs-1806-cropped.jpg?v=1774341392","price":23},{"name":"Fine Raffia - Natural Beige By Lee Jofa Modern | Elements Ii Naturals |Solid Texture Wallcovering Grasscloth","vendor":"Groundworks","sku":"DWKK-139575","handle":"dwkk-g328a6d2a","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/GWP-3415_16_ae628f14-0e94-479b-bfd8-a11a13484a57.jpg?v=1753291589","price":138},{"name":"Pali Basketweave Paperweave Grasscloth | Phillipe Romano","vendor":"Phillipe Romano","sku":"GRA-39052","handle":"pali-basketweave-paperweave-grasscloth-gra-39052","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/da6ddb3316744cc200ef1ea31be33850.jpg?v=1745459005","price":5},{"name":"Hank's Horizontal Grasscloth | Phillipe Romano","vendor":"Phillipe Romano","sku":"GRS-38023","handle":"hank-s-horizontal-grasscloth-wall-paper-grs-38023","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/grs-1495926177904-cropped_5f436f24-2e50-4830-ac9a-898a00c493f6.jpg?v=1774344984","price":4},{"name":"Exhale Woven Faux Grasscloth - Moss Residential Wallcovering | Jeffrey Stevens","vendor":"Jeffrey Stevens","sku":"DWJS-91046","handle":"exhale-woven-faux-grasscloth-moss-residential-wallpaper-jeffrey-stevens","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/0093554_exhale-moss-woven-faux-grasscloth-wallpaper.jpg?v=1775163868","price":null}],"linen":[{"name":"Watermill Linen - Onyx Black By Lee Jofa | Colour Complements Ii |Solid Texture Multipurpose","vendor":"Lee Jofa","sku":"DWKK-134386","handle":"dwkk-gdd16bc69","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2012176_8_e25de644-ce6f-4603-a697-1e5f36bb9eae.jpg?v=1726547195","price":165},{"name":"Dublin Linen - Slate Grey By Lee Jofa | Colour Complements Ii |Solid Texture Multipurpose","vendor":"Lee Jofa","sku":"DWKK-134356","handle":"dwkk-g17f20967","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2012175_52_80c9d662-5da8-4ff1-9447-3bac642a912d.jpg?v=1726547112","price":63},{"name":"Watermill Linen - Cream White By Lee Jofa | Colour Complements Ii |Solid Texture Multipurpose","vendor":"Lee Jofa","sku":"DWKK-134362","handle":"dwkk-ge8a4d5f8","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2012176_1_bc47c44b-f4c6-4839-b5db-44147e1a00d7.jpg?v=1726547127","price":165},{"name":"Bankers Linen - Red Red By Brunschwig & Fils | Gis |Solid Texture Multipurpose Linen","vendor":"Brunschwig & Fils","sku":"DWKK-100605","handle":"dwkk-ge6b86835","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8017144_19_41d7b025-6adc-461b-939d-df24fcd879fc.jpg?v=1725919035","price":150},{"name":"Bankers Linen - Cream White By Brunschwig & Fils | Le Jardin Chinois |Solid Texture Multipurpose Linen","vendor":"Brunschwig & Fils","sku":"DWKK-100024","handle":"dwkk-g86428c9f","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8012140_111_d5520c5c-231a-47b0-b595-e5b7f49dd89c.jpg?v=1725917826","price":150},{"name":"Bankers Linen - Ink Blue By Brunschwig & Fils | Le Jardin Chinois |Solid Texture Multipurpose Linen","vendor":"Brunschwig & Fils","sku":"DWKK-100038","handle":"dwkk-gd3d7f11a","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8012140_50_463fe0c2-c2f6-46f9-8e36-b67c267b4b07.jpg?v=1725917855","price":150},{"name":"Dublin Linen -  Prism Grey By Lee Jofa | Colour Library Vii |Solid Texture Multipurpose","vendor":"Lee Jofa","sku":"DWKK-134328","handle":"dwkk-g06da8410","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2012175_1101_28895aed-245e-44cd-9ef9-f5c5e388791e.jpg?v=1726547029","price":63},{"name":"Lille Linen - Midgnight Dark Blue By Lee Jofa | Gis | Solid Multipurpose","vendor":"Lee Jofa","sku":"DWKK-134752","handle":"dwkk-gf234f2cd","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/2017119_50_a4368812-7556-47ba-a7b1-0e10ba5a1b92.jpg?v=1726548252","price":150}],"raffia":[{"name":"Fine Raffia - Natural Beige By Lee Jofa Modern | Elements Ii Naturals |Solid Texture Wallcovering Grasscloth","vendor":"Groundworks","sku":"DWKK-139575","handle":"dwkk-g328a6d2a","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/GWP-3415_16_ae628f14-0e94-479b-bfd8-a11a13484a57.jpg?v=1753291589","price":138},{"name":"Cabo Raffia Wallcovering | Thibaut","vendor":"Thibaut","sku":"DWTT-80391","handle":"dwtt-80391-designer-wallcoverings-los-angeles","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T41160_d90c5f1b-3086-4237-851f-332ec133f1c8.jpg?v=1781202567","price":196},{"name":"Bethany Raffia Off White - Off White Wallcovering | Thibaut","vendor":"Thibaut","sku":"DWTT-80363","handle":"dwtt-80363-designer-wallcoverings-los-angeles","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T19602_05477fe1-50d1-4fa8-b5ec-f23c50e31727.jpg?v=1781202597","price":379},{"name":"Sea Bright Plain Paper Weave Raffia Wallcovering | Phillipe Romano","vendor":"Phillipe Romano","sku":"PWV-500950","handle":"sea-bright-plain-paper-weave-raffia-paper-weave-wallcovering-phillipe-romano","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/baf2884af6422e26083a1b2f6e07e3cd.jpg?v=1783880263","price":null},{"name":"Raffia Decor - Smoke | Scalamandre","vendor":"Scalamandre Wallpaper","sku":"DWA-88550","handle":"wtt661468","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/WTT661468.jpg?v=1745346442","price":null},{"name":"Bankun Raffia Antique - Antique Natural Wallcovering | Thibaut","vendor":"Thibaut","sku":"DWTT-72328","handle":"dwtt-72328-designer-wallcoverings-los-angeles","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T6817_89424133-37e8-4acd-9f01-2b49bbf46f15.jpg?v=1781202941","price":4},{"name":"Madagascar Cloth 3 Raffia","vendor":"Designer Wallcoverings","sku":"DWC-278566","handle":"dwc-278566-designerwallcoverings-com","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DWC-278566-DesignerWallcoverings-Los-Angeles-1_a3bede8c-b94d-477d-b0c2-97ad23dba752.png?v=1741881338","price":null},{"name":"Taiki Paper Weave Raffia | Phillipe Romano","vendor":"Phillipe Romano","sku":"XQA-33723","handle":"taiki-paper-weave-raffia-xqa-33723","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/products/69007e51570bb9c98f02694ac9a82280_13600e9f-b3b4-4f31-9801-14c9e2a4965a.jpg?v=1745457371","price":4}],"silk":[{"name":"Damask Pierre - Chestnut Brown By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100209","handle":"dwkk-g761bb5a0","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_606_2cb44e0a-37d7-4b04-8e4b-96307660072f.jpg?v=1725918209","price":627},{"name":"Damask Pierre - Red Red By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100213","handle":"dwkk-gd8647d7a","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_9_61cc5b9b-d2be-4a47-8c79-577e47820436.jpg?v=1725918217","price":627},{"name":"Arnaud Damask - Aqua Light Blue By Brunschwig & Fils | Vienne Silks | Damask Multipurpose Silk","vendor":"Brunschwig & Fils","sku":"DWKK-101383","handle":"dwkk-gff434372","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8023150_13_071e61ae-d6c8-4125-b482-8612dfcae110.jpg?v=1725920760","price":327},{"name":"Damask Pierre - Sand Beige By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100198","handle":"dwkk-g55f0dcc6","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_1116_af6caecd-9ea2-4855-a05d-e55f7ad2465d.jpg?v=1725918185","price":627},{"name":"Damask Pierre - Grey Grey By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100195","handle":"dwkk-g28f6f736","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_11_07b058dc-7c5d-4008-bb9b-fce99728b8c9.jpg?v=1725918179","price":627},{"name":"Damask Pierre - Delft Blue By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100207","handle":"dwkk-ga7c82fa0","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_50_8afa77cd-9bed-4841-aa5f-a05040e880ff.jpg?v=1725918205","price":627},{"name":"Damask Pierre - Olive Green By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100202","handle":"dwkk-ged4f176b","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_30_75ebeb88-3494-4242-a679-efffd52a9959.jpg?v=1725918194","price":627},{"name":"Damask Pierre - Snow White By Brunschwig & Fils | B&F Showroom Exclusive 2019 | Damask Upholstery Silk","vendor":"Brunschwig & Fils","sku":"DWKK-100193","handle":"dwkk-g48d398e0","img":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/8013188_1_00da697d-d623-4bc3-90d4-02a0d59aaff8.jpg?v=1725918174","price":627}]};
+
+/* Rotating picker — each spawn surfaces the next real product for its material,
+   so switching / reshuffling presets keeps revealing different live SKUs. */
+const _prodIdx = {};
+function pickProduct(type) {
+  const pool = PRODUCTS[type] || [];
+  if (!pool.length) return null;
+  const i = (_prodIdx[type] || 0) % pool.length;
+  _prodIdx[type] = i + 1;
+  return pool[i];
+}
+/* "Jardin Fleuri - Ivory Multi By Brunschwig ... | Collection | Type" -> "Jardin Fleuri - Ivory Multi" */
+function productShortName(name) {
+  return (name || '').split(/\s+By\s+|\s*\|\s*/)[0].replace(/\s+/g, ' ').trim();
+}
+function productHref(handle) {
+  return 'https://www.designerwallcoverings.com/products/' + handle;
+}
+
 /* ============================================================
    STYLE PRESET DATA
    ============================================================ */
@@ -1312,8 +1362,38 @@ function createSwatchTile(type, palette, x, y, z, id) {
   el.style.top  = y + 'px';
   el.style.zIndex = z;
 
-  const svgStr = buildSwatchSVG(type, palette);
-  el.innerHTML = `${svgStr}<div class="tile-label">${SWATCH_LABELS[type] || type}</div>`;
+  const prod   = pickProduct(type);
+  const svgStr = buildSwatchSVG(type, palette); // procedural fallback texture
+
+  if (!prod) {
+    el.innerHTML = `${svgStr}<div class="tile-label">${SWATCH_LABELS[type] || type}</div>`;
+    return el;
+  }
+
+  // Real DW product photo
+  el.dataset.material = type;
+  el.dataset.product  = JSON.stringify(prod);
+  const short    = productShortName(prod.name);
+  const priceStr = (typeof prod.price === 'number' && prod.price > 0) ? ' · $' + prod.price : '';
+  const esc = s => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+  el.title = `${prod.name}  —  ${prod.vendor}${prod.sku ? '  ·  ' + prod.sku : ''}${priceStr}\n(double-click to open on designerwallcoverings.com)`;
+
+  el.innerHTML =
+    `<img class="swatch-img" crossorigin="anonymous" loading="lazy" alt="${esc(short)}" src="${esc(prod.img)}">` +
+    `<div class="tile-label">` +
+      `<span class="tl-name">${esc(short)}</span>` +
+      `<span class="tl-vendor">${esc(prod.vendor)}${esc(priceStr)}</span>` +
+    `</div>`;
+
+  // Degrade gracefully to the procedural texture if a photo ever fails to load
+  const img = el.querySelector('.swatch-img');
+  img.addEventListener('error', () => {
+    el.classList.add('img-failed');
+    el.innerHTML = `${svgStr}<div class="tile-label">${SWATCH_LABELS[type] || type}</div>`;
+  }, { once: true });
+  // Double-click opens the live product (single click stays reserved for drag/select)
+  el.addEventListener('dblclick', () => window.open(productHref(prod.handle), '_blank', 'noopener'));
+
   return el;
 }
 
@@ -1875,7 +1955,52 @@ async function openExport() {
 
 /* --- Sub-renderers for canvas export --- */
 function drawSwatchOnCanvas(ctx, t, x, y, w, h, palette) {
-  const swatchType = t.el.querySelector('.tile-label')?.textContent?.toLowerCase().replace(/\s+/g,'') || '';
+  // Prefer the REAL product photo. Images load with crossorigin="anonymous" and
+  // Shopify CDN sends Access-Control-Allow-Origin:* — so drawing them does NOT
+  // taint the export canvas and toBlob() still works.
+  const imgEl = t.el.querySelector('.swatch-img');
+  if (imgEl && imgEl.complete && imgEl.naturalWidth > 0) {
+    const ir = imgEl.naturalWidth / imgEl.naturalHeight;
+    const dr = w / h;
+    let sx, sy, sw, sh; // object-fit: cover source rect
+    if (ir > dr) { sh = imgEl.naturalHeight; sw = sh * dr; sx = (imgEl.naturalWidth - sw) / 2; sy = 0; }
+    else         { sw = imgEl.naturalWidth;  sh = sw / dr; sx = 0; sy = (imgEl.naturalHeight - sh) / 2; }
+    try {
+      ctx.drawImage(imgEl, sx, sy, sw, sh, x, y, w, h);
+    } catch (e) {
+      drawProceduralSwatch(ctx, t, x, y, w, h, palette);
+    }
+    drawSwatchLabel(ctx, t, x, y, w, h);
+    return;
+  }
+  drawProceduralSwatch(ctx, t, x, y, w, h, palette);
+  drawSwatchLabel(ctx, t, x, y, w, h);
+}
+
+function drawSwatchLabel(ctx, t, x, y, w, h) {
+  let label = '';
+  const pj = t.el.dataset.product;
+  if (pj) { try { label = productShortName(JSON.parse(pj).name); } catch (e) {} }
+  if (!label) label = t.el.querySelector('.tl-name')?.textContent
+                    || t.el.querySelector('.tile-label')?.textContent || '';
+  ctx.fillStyle = 'rgba(0,0,0,0.52)';
+  ctx.fillRect(x, y + h - h * 0.18, w, h * 0.18);
+  ctx.font = `700 ${Math.round(w * 0.062)}px -apple-system,Helvetica,Arial,sans-serif`;
+  ctx.fillStyle = 'rgba(255,255,255,0.92)';
+  ctx.textAlign = 'left';
+  const maxW = w * 0.88;
+  let txt = label.toUpperCase();
+  if (ctx.measureText(txt).width > maxW) {
+    while (txt.length > 4 && ctx.measureText(txt + '…').width > maxW) txt = txt.slice(0, -1);
+    txt = txt.replace(/[\s\-]+$/, '') + '…';
+  }
+  ctx.fillText(txt, x + w * 0.06, y + h - h * 0.06);
+}
+
+function drawProceduralSwatch(ctx, t, x, y, w, h, palette) {
+  const swatchType = (t.el.dataset.material
+                     || t.el.querySelector('.tile-label')?.textContent
+                     || '').toLowerCase().replace(/\s+/g,'');
 
   if (swatchType.includes('grasscloth')) {
     const bg = palette[4]?.hex || '#d4c4a4';
@@ -1971,15 +2096,6 @@ function drawSwatchOnCanvas(ctx, t, x, y, w, h, palette) {
     ctx.fill();
     ctx.globalAlpha = 1;
   }
-
-  // Label strip
-  ctx.fillStyle = 'rgba(0,0,0,0.40)';
-  ctx.fillRect(x, y + h - h*0.16, w, h*0.16);
-  ctx.font = `700 ${Math.round(w * 0.07)}px -apple-system,Helvetica,Arial,sans-serif`;
-  ctx.fillStyle = 'rgba(255,255,255,0.85)';
-  ctx.textAlign = 'left';
-  const labelText = t.el.querySelector('.tile-label')?.textContent || '';
-  ctx.fillText(labelText.toUpperCase(), x + w*0.06, y + h - h*0.04);
 }
 
 function drawChipOnCanvas(ctx, el, x, y, w, h) {
diff --git a/server.js b/server.js
index eacfb60..9495984 100644
--- a/server.js
+++ b/server.js
@@ -936,7 +936,8 @@ iframe{width:100%;height:460px;border:0;background:#000}.c.win iframe{height:560
     if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no game'); }
     res.writeHead(200, {
       'Content-Type': 'text/html; charset=utf-8',
-      'Content-Security-Policy': "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; media-src data: blob:; font-src data:",
+      // games may pull real DW product photos from the Shopify CDN (img-src)
+      'Content-Security-Policy': "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob: https://cdn.shopify.com; media-src data: blob:; font-src data:",
       'X-Frame-Options': 'SAMEORIGIN',
     });
     return res.end(fs.readFileSync(fp));

← 2aa4931 auto-save: 2026-07-30T07:45:45 (6 files) — data/challenges.j  ·  back to Model Arena  ·  mood-board-studio: real products only — drop color chips, ty a236fbf →