[object Object]

← back to Trending Dw

trending: real our-own images on every covered card (WPB originals + self-hosted in-house webp); restore it.image overlay + /api/matchup lost in mac3 migration; GAP lanes flagged for gen

eeb5aaa03be2a83827599bdeaae846beeee54329 · 2026-07-03 09:04:41 -0700 · Steve

Files touched

Diff

commit eeb5aaa03be2a83827599bdeaae846beeee54329
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jul 3 09:04:41 2026 -0700

    trending: real our-own images on every covered card (WPB originals + self-hosted in-house webp); restore it.image overlay + /api/matchup lost in mac3 migration; GAP lanes flagged for gen
---
 data/bestsellers.json              | 271 ++++++++++++++++++++++++++++++-------
 data/our-catalog.json              |   1 +
 public/assets/pilot/h_black.webp   | Bin 0 -> 235108 bytes
 public/assets/pilot/h_blush.webp   | Bin 0 -> 206106 bytes
 public/assets/pilot/lane1.webp     | Bin 0 -> 215626 bytes
 public/assets/pilot/lane1b.webp    | Bin 0 -> 124294 bytes
 public/assets/pilot/lane1c2.webp   | Bin 0 -> 266822 bytes
 public/assets/pilot/lane2.webp     | Bin 0 -> 200476 bytes
 public/assets/pilot/lane2b.webp    | Bin 0 -> 276178 bytes
 public/assets/pilot/lane2c.webp    | Bin 0 -> 278480 bytes
 public/assets/pilot/lane3b.webp    | Bin 0 -> 255216 bytes
 public/assets/pilot/lane4.webp     | Bin 0 -> 332618 bytes
 public/assets/pilot/lane5.webp     | Bin 0 -> 146446 bytes
 public/assets/pilot/lane6.webp     | Bin 0 -> 323326 bytes
 public/assets/pilot/m_emerald.webp | Bin 0 -> 226568 bytes
 public/assets/pilot/m_plum.webp    | Bin 0 -> 242596 bytes
 public/index.html                  |  29 +++-
 scripts/attach-images.js           | 184 +++++++++++++++++++++++++
 scripts/refresh.sh                 |   1 +
 server.js                          |  24 +++-
 20 files changed, 457 insertions(+), 53 deletions(-)

diff --git a/data/bestsellers.json b/data/bestsellers.json
index 896e65a..2a27180 100644
--- a/data/bestsellers.json
+++ b/data/bestsellers.json
@@ -16,7 +16,11 @@
       "priceBand": "$$$",
       "signal": "Most-installed heritage wallpaper",
       "signalRank": 98,
-      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026"
+      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-002",
@@ -31,7 +35,11 @@
       "priceBand": "$$$",
       "signal": "Ahead of forecast",
       "signalRank": 92,
-      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026"
+      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-003",
@@ -46,7 +54,11 @@
       "priceBand": "$$$",
       "signal": "Ahead of forecast",
       "signalRank": 88,
-      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026"
+      "url": "https://williammorriswallpaper.co/blogs/guides/wallpaper-trends-2026",
+      "image": "/assets/pilot/lane1b.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Indigo Trellis — Heritage Vine",
+      "isOurOriginal": true
     },
     {
       "id": "TR-004",
@@ -60,7 +72,11 @@
       "color": "Oxblood",
       "priceBand": "$$$$",
       "signal": "Maximalist trend leader",
-      "signalRank": 84
+      "signalRank": 84,
+      "image": "https://wallpapersback.com/designs/img/by-id/57678",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Terracotta Atelier No.57678",
+      "isOurOriginal": true
     },
     {
       "id": "TR-005",
@@ -75,7 +91,11 @@
       "priceBand": "$$",
       "signal": "High review count",
       "signalRank": 80,
-      "url": "https://www.etsy.com/shop/WallHalla"
+      "url": "https://www.etsy.com/shop/WallHalla",
+      "image": "https://wallpapersback.com/designs/img/by-id/40178",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Folio No.40178",
+      "isOurOriginal": true
     },
     {
       "id": "TR-006",
@@ -90,7 +110,11 @@
       "priceBand": "$$$",
       "signal": "2026-trends collection",
       "signalRank": 78,
-      "url": "https://www.spoonflower.com/en/shop/2026-trends"
+      "url": "https://www.spoonflower.com/en/shop/2026-trends",
+      "image": "https://wallpapersback.com/designs/img/by-id/57669",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Chamois Reverie No.57669",
+      "isOurOriginal": true
     },
     {
       "id": "TR-007",
@@ -104,7 +128,9 @@
       "color": "Blush",
       "priceBand": "$$$$",
       "signal": "Designer trend pick 2026",
-      "signalRank": 90
+      "signalRank": 90,
+      "needsGeneration": true,
+      "genBrief": "Original Animal-Skin Print in Blush — generated in our own hand from live trend research, settlement-gated. Trend signal: Designer trend pick 2026 (rank 90)."
     },
     {
       "id": "TR-008",
@@ -118,7 +144,9 @@
       "color": "Forest Green",
       "priceBand": "$$$$",
       "signal": "Designer trend pick 2026",
-      "signalRank": 89
+      "signalRank": 89,
+      "needsGeneration": true,
+      "genBrief": "Original Animal-Skin Print in Forest Green — generated in our own hand from live trend research, settlement-gated. Trend signal: Designer trend pick 2026 (rank 89)."
     },
     {
       "id": "TR-009",
@@ -133,7 +161,9 @@
       "priceBand": "$$",
       "signal": "Shop bestseller",
       "signalRank": 76,
-      "url": "https://www.etsy.com/shop/WallHalla"
+      "url": "https://www.etsy.com/shop/WallHalla",
+      "needsGeneration": true,
+      "genBrief": "Original Animal-Skin Print in Claret — generated in our own hand from live trend research, settlement-gated. Trend signal: Shop bestseller (rank 76)."
     },
     {
       "id": "TR-010",
@@ -148,7 +178,9 @@
       "priceBand": "$$",
       "signal": "High review count",
       "signalRank": 74,
-      "url": "https://www.etsy.com/shop/WallHalla"
+      "url": "https://www.etsy.com/shop/WallHalla",
+      "needsGeneration": true,
+      "genBrief": "Original Animal-Skin Print in Mustard — generated in our own hand from live trend research, settlement-gated. Trend signal: High review count (rank 74)."
     },
     {
       "id": "TR-011",
@@ -163,7 +195,9 @@
       "priceBand": "$$$",
       "signal": "Chartreuse color-of-year adjacent",
       "signalRank": 82,
-      "url": "https://patternbank.com/trends"
+      "url": "https://patternbank.com/trends",
+      "needsGeneration": true,
+      "genBrief": "Original Art-Deco Geometric in Chartreuse — generated in our own hand from live trend research, settlement-gated. Trend signal: Chartreuse color-of-year adjacent (rank 82)."
     },
     {
       "id": "TR-012",
@@ -178,7 +212,11 @@
       "priceBand": "$$",
       "signal": "Top-5 P&S design 2026",
       "signalRank": 79,
-      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/"
+      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/",
+      "image": "https://wallpapersback.com/designs/img/by-id/57705",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Lattice No.57705",
+      "isOurOriginal": true
     },
     {
       "id": "TR-013",
@@ -193,7 +231,9 @@
       "priceBand": "$$",
       "signal": "Key emerging trend",
       "signalRank": 81,
-      "url": "https://blog.wallpaperdirect.com/wallpaper-trends-forecast-2025-2/"
+      "url": "https://blog.wallpaperdirect.com/wallpaper-trends-forecast-2025-2/",
+      "needsGeneration": true,
+      "genBrief": "Original Texture-Effect in Greige — generated in our own hand from live trend research, settlement-gated. Trend signal: Key emerging trend (rank 81)."
     },
     {
       "id": "TR-014",
@@ -207,7 +247,9 @@
       "color": "Cloud White",
       "priceBand": "$$$",
       "signal": "Tactility trend",
-      "signalRank": 77
+      "signalRank": 77,
+      "needsGeneration": true,
+      "genBrief": "Original Texture-Effect in Cloud White — generated in our own hand from live trend research, settlement-gated. Trend signal: Tactility trend (rank 77)."
     },
     {
       "id": "TR-015",
@@ -222,7 +264,11 @@
       "priceBand": "$",
       "signal": "Small-room favorite",
       "signalRank": 70,
-      "url": "https://www.spoonflower.com/en/shop/top-sellers"
+      "url": "https://www.spoonflower.com/en/shop/top-sellers",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-016",
@@ -236,7 +282,11 @@
       "color": "Indigo",
       "priceBand": "$",
       "signal": "Micro-print rising",
-      "signalRank": 68
+      "signalRank": 68,
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-017",
@@ -251,7 +301,11 @@
       "priceBand": "$$$",
       "signal": "Luxury P&S bestseller",
       "signalRank": 83,
-      "url": "https://www.etsy.com/uk/shop/musewallstudio"
+      "url": "https://www.etsy.com/uk/shop/musewallstudio",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-018",
@@ -266,7 +320,11 @@
       "priceBand": "$$",
       "signal": "Shop bestseller",
       "signalRank": 73,
-      "url": "https://www.etsy.com/uk/shop/musewallstudio"
+      "url": "https://www.etsy.com/uk/shop/musewallstudio",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-019",
@@ -281,7 +339,11 @@
       "priceBand": "$$",
       "signal": "Featured bestseller",
       "signalRank": 69,
-      "url": "https://www.etsy.com/uk/shop/musewallstudio"
+      "url": "https://www.etsy.com/uk/shop/musewallstudio",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-020",
@@ -296,7 +358,11 @@
       "priceBand": "$$$$",
       "signal": "Premium timeless seller",
       "signalRank": 80,
-      "url": "https://blog.wallpaperdirect.com/wallpaper-trends-forecast-2025-2/"
+      "url": "https://blog.wallpaperdirect.com/wallpaper-trends-forecast-2025-2/",
+      "image": "https://wallpapersback.com/designs/img/by-id/57669",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Chamois Reverie No.57669",
+      "isOurOriginal": true
     },
     {
       "id": "TR-021",
@@ -311,7 +377,9 @@
       "priceBand": "$$",
       "signal": "High review count",
       "signalRank": 66,
-      "url": "https://www.etsy.com/shop/WallHalla"
+      "url": "https://www.etsy.com/shop/WallHalla",
+      "needsGeneration": true,
+      "genBrief": "Original Tropical in Sage — generated in our own hand from live trend research, settlement-gated. Trend signal: High review count (rank 66)."
     },
     {
       "id": "TR-022",
@@ -326,7 +394,9 @@
       "priceBand": "$$",
       "signal": "New 2026 release",
       "signalRank": 72,
-      "url": "https://wallblush.com/products/selina"
+      "url": "https://wallblush.com/products/selina",
+      "needsGeneration": true,
+      "genBrief": "Original Boho in Terracotta — generated in our own hand from live trend research, settlement-gated. Trend signal: New 2026 release (rank 72)."
     },
     {
       "id": "TR-023",
@@ -341,7 +411,11 @@
       "priceBand": "$$",
       "signal": "Scandi bestseller",
       "signalRank": 67,
-      "url": "https://www.etsy.com/uk/shop/musewallstudio"
+      "url": "https://www.etsy.com/uk/shop/musewallstudio",
+      "image": "https://wallpapersback.com/designs/img/by-id/57705",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Lattice No.57705",
+      "isOurOriginal": true
     },
     {
       "id": "TR-024",
@@ -356,7 +430,11 @@
       "priceBand": "$$",
       "signal": "Neutral P&S seller",
       "signalRank": 64,
-      "url": "https://www.etsy.com/uk/shop/musewallstudio"
+      "url": "https://www.etsy.com/uk/shop/musewallstudio",
+      "image": "https://wallpapersback.com/designs/img/by-id/57705",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Lattice No.57705",
+      "isOurOriginal": true
     },
     {
       "id": "TR-025",
@@ -371,7 +449,11 @@
       "priceBand": "$$",
       "signal": "Top-5 P&S design 2026",
       "signalRank": 75,
-      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/"
+      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-026",
@@ -386,7 +468,11 @@
       "priceBand": "$$",
       "signal": "#1 P&S design 2026",
       "signalRank": 85,
-      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/"
+      "url": "https://fancywalls.eu/blog/top-wallpaper-designs/",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-027",
@@ -401,7 +487,11 @@
       "priceBand": "$$",
       "signal": "Curated 2026 color trends",
       "signalRank": 71,
-      "url": "https://www.spoonflower.com/en/shop/2026-color-trends"
+      "url": "https://www.spoonflower.com/en/shop/2026-color-trends",
+      "image": "/assets/pilot/lane3b.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Cottage Wash — Painterly Floral",
+      "isOurOriginal": true
     },
     {
       "id": "TR-028",
@@ -416,7 +506,11 @@
       "priceBand": "$",
       "signal": "Pantone 2026 aligned",
       "signalRank": 65,
-      "url": "https://www.spoonflower.com/en/shop/2026-trends"
+      "url": "https://www.spoonflower.com/en/shop/2026-trends",
+      "image": "https://wallpapersback.com/designs/img/by-id/10024",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Reverie No.10024",
+      "isOurOriginal": true
     },
     {
       "id": "TR-029",
@@ -431,7 +525,9 @@
       "priceBand": "$$",
       "signal": "Trending wall tapestry",
       "signalRank": 79,
-      "url": "https://society6.com/products/art-deco-gatsby-geometric-pattern-ivory-and-gold_wall-tapestry"
+      "url": "https://society6.com/products/art-deco-gatsby-geometric-pattern-ivory-and-gold_wall-tapestry",
+      "needsGeneration": true,
+      "genBrief": "Original Art-Deco Geometric in Cream Gold — generated in our own hand from live trend research, settlement-gated. Trend signal: Trending wall tapestry (rank 79)."
     },
     {
       "id": "TR-030",
@@ -446,7 +542,9 @@
       "priceBand": "$$",
       "signal": "Trending wall tapestry",
       "signalRank": 74,
-      "url": "https://society6.com/products/psychedelic-70s-floral8796623_wall-tapestry"
+      "url": "https://society6.com/products/psychedelic-70s-floral8796623_wall-tapestry",
+      "needsGeneration": true,
+      "genBrief": "Original Retro Psychedelic in Mustard — generated in our own hand from live trend research, settlement-gated. Trend signal: Trending wall tapestry (rank 74)."
     },
     {
       "id": "TR-031",
@@ -461,7 +559,11 @@
       "priceBand": "$$",
       "signal": "Trending wall tapestry",
       "signalRank": 70,
-      "url": "https://society6.com/collections/tapestries"
+      "url": "https://society6.com/collections/tapestries",
+      "image": "https://wallpapersback.com/designs/img/by-id/57717",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Prussian Origin No.57717",
+      "isOurOriginal": true
     },
     {
       "id": "TR-032",
@@ -476,7 +578,9 @@
       "priceBand": "$",
       "signal": "Wall-decor bestseller",
       "signalRank": 66,
-      "url": "https://society6.com/collections/wall-art-decor"
+      "url": "https://society6.com/collections/wall-art-decor",
+      "needsGeneration": true,
+      "genBrief": "Original Boho in Terracotta — generated in our own hand from live trend research, settlement-gated. Trend signal: Wall-decor bestseller (rank 66)."
     },
     {
       "id": "TR-033",
@@ -491,7 +595,11 @@
       "priceBand": "$$",
       "signal": "Strong on wall art + phone",
       "signalRank": 76,
-      "url": "https://www.redbubble.com/shop/botanical+wall-art"
+      "url": "https://www.redbubble.com/shop/botanical+wall-art",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-034",
@@ -506,7 +614,9 @@
       "priceBand": "$",
       "signal": "Community-niche seller",
       "signalRank": 72,
-      "url": "https://www.redbubble.com/shop/mushroom+pattern"
+      "url": "https://www.redbubble.com/shop/mushroom+pattern",
+      "needsGeneration": true,
+      "genBrief": "Original Mushroom / Goblincore in Mushroom — generated in our own hand from live trend research, settlement-gated. Trend signal: Community-niche seller (rank 72)."
     },
     {
       "id": "TR-035",
@@ -521,7 +631,11 @@
       "priceBand": "$",
       "signal": "Cottagecore demand",
       "signalRank": 71,
-      "url": "https://www.redbubble.com/shop/cottagecore+wall-art"
+      "url": "https://www.redbubble.com/shop/cottagecore+wall-art",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-036",
@@ -536,7 +650,9 @@
       "priceBand": "$",
       "signal": "Retro geometric cycle",
       "signalRank": 68,
-      "url": "https://www.redbubble.com/shop/memphis+pattern"
+      "url": "https://www.redbubble.com/shop/memphis+pattern",
+      "needsGeneration": true,
+      "genBrief": "Original Retro Geometric in Pop Pink — generated in our own hand from live trend research, settlement-gated. Trend signal: Retro geometric cycle (rank 68)."
     },
     {
       "id": "TR-037",
@@ -551,7 +667,11 @@
       "priceBand": "$$",
       "signal": "Blue-ocean wall art",
       "signalRank": 67,
-      "url": "https://www.redbubble.com/shop/dark+academia+wall-art"
+      "url": "https://www.redbubble.com/shop/dark+academia+wall-art",
+      "image": "https://wallpapersback.com/designs/img/by-id/10006",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Studio No.10006",
+      "isOurOriginal": true
     },
     {
       "id": "TR-038",
@@ -566,7 +686,11 @@
       "priceBand": "$$$",
       "signal": "Top bundle (toile/chinoiserie/vintage)",
       "signalRank": 82,
-      "url": "https://creativemarket.com/Neti_Neti/292231195-700-Seamless-Patterns-Bundle"
+      "url": "https://creativemarket.com/Neti_Neti/292231195-700-Seamless-Patterns-Bundle",
+      "image": "https://wallpapersback.com/designs/img/by-id/40138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Chamois Folio No.40138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-039",
@@ -581,7 +705,11 @@
       "priceBand": "$$",
       "signal": "Toile bestseller",
       "signalRank": 75,
-      "url": "https://creativemarket.com/Neti_Neti/260616430-Toile-de-Jouy-Patterns-Bundle"
+      "url": "https://creativemarket.com/Neti_Neti/260616430-Toile-de-Jouy-Patterns-Bundle",
+      "image": "https://wallpapersback.com/designs/img/by-id/40138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Chamois Folio No.40138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-040",
@@ -596,7 +724,11 @@
       "priceBand": "$$",
       "signal": "Bestselling floral bundle",
       "signalRank": 73,
-      "url": "https://creativemarket.com/Veyla/292246107-Soft-Season-60-Seamless-Patterns"
+      "url": "https://creativemarket.com/Veyla/292246107-Soft-Season-60-Seamless-Patterns",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-041",
@@ -611,7 +743,11 @@
       "priceBand": "$$",
       "signal": "Popular floral set",
       "signalRank": 70,
-      "url": "https://creativemarket.com/Veyla/292231194-Bloom-Patterns-Illustrations"
+      "url": "https://creativemarket.com/Veyla/292231194-Bloom-Patterns-Illustrations",
+      "image": "/assets/pilot/lane3b.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Cottage Wash — Painterly Floral",
+      "isOurOriginal": true
     },
     {
       "id": "TR-042",
@@ -626,7 +762,11 @@
       "priceBand": "$$",
       "signal": "Organic-shapes bundle",
       "signalRank": 68,
-      "url": "https://creativemarket.com/Curly_Pat/292152878-Big-Bundle-90-Seamless-Patterns"
+      "url": "https://creativemarket.com/Curly_Pat/292152878-Big-Bundle-90-Seamless-Patterns",
+      "image": "https://wallpapersback.com/designs/img/by-id/10006",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Studio No.10006",
+      "isOurOriginal": true
     },
     {
       "id": "TR-043",
@@ -641,7 +781,11 @@
       "priceBand": "$$",
       "signal": "Morris digital bestseller",
       "signalRank": 72,
-      "url": "https://creativemarket.com/search?q=william%20morris%20seamless"
+      "url": "https://creativemarket.com/search?q=william%20morris%20seamless",
+      "image": "https://wallpapersback.com/designs/img/by-id/138",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Apricot Garden No.138",
+      "isOurOriginal": true
     },
     {
       "id": "TR-044",
@@ -656,7 +800,11 @@
       "priceBand": "$",
       "signal": "Ditsy demand",
       "signalRank": 66,
-      "url": "https://creativemarket.com/search?q=ditsy%20floral%20seamless"
+      "url": "https://creativemarket.com/search?q=ditsy%20floral%20seamless",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-045",
@@ -671,7 +819,11 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 88,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "image": "/assets/pilot/m_plum.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Amethyst Nocturne — Moody Bloom",
+      "isOurOriginal": true
     },
     {
       "id": "TR-046",
@@ -686,7 +838,11 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 84,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "image": "/assets/pilot/lane3b.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Cottage Wash — Painterly Floral",
+      "isOurOriginal": true
     },
     {
       "id": "TR-047",
@@ -701,7 +857,11 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 82,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "image": "/assets/pilot/lane1c2.webp",
+      "imageSource": "DW in-house original",
+      "imageDesign": "Calico Vine — Heritage Ditsy",
+      "isOurOriginal": true
     },
     {
       "id": "TR-048",
@@ -716,7 +876,9 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 80,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "needsGeneration": true,
+      "genBrief": "Original Paisley / Nomadic in Claret — generated in our own hand from live trend research, settlement-gated. Trend signal: Premiere Vision AW26-27 direction (rank 80)."
     },
     {
       "id": "TR-049",
@@ -731,7 +893,11 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 78,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "image": "https://wallpapersback.com/designs/img/by-id/57705",
+      "imageSource": "Wallpaper's Back (our original)",
+      "imageDesign": "Greige Lattice No.57705",
+      "isOurOriginal": true
     },
     {
       "id": "TR-050",
@@ -746,7 +912,10 @@
       "priceBand": "$$$",
       "signal": "Premiere Vision AW26-27 direction",
       "signalRank": 76,
-      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends"
+      "url": "https://patternbank.com/trends/premiere-vision-AW-26-27-print-trends",
+      "needsGeneration": true,
+      "genBrief": "Original Abstract Blur in Ombre Blue — generated in our own hand from live trend research, settlement-gated. Trend signal: Premiere Vision AW26-27 direction (rank 76)."
     }
-  ]
+  ],
+  "imagesAttachedAt": "2026-07-02"
 }
\ No newline at end of file
diff --git a/data/our-catalog.json b/data/our-catalog.json
new file mode 100644
index 0000000..fa82916
--- /dev/null
+++ b/data/our-catalog.json
@@ -0,0 +1 @@
+[{"title":"Arden Botanical — Heritage Trellis","cat":"in-house-original","image":"/assets/pilot/lane1.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original heritage botanical — original, generated in our own hand from live trend research (forest green + cream on soft sage). settlement-passed. arden botanical — heritage trellis"},{"title":"Nocturne Bloom — Dark Moody Floral","cat":"in-house-original","image":"/assets/pilot/lane2.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original dark moody florals — original, generated in our own hand from live trend research (blush peony on ink-black ground). settlement-passed. nocturne bloom — dark moody floral"},{"title":"Gilded Bough — Heritage Noir","cat":"in-house-original","image":"/assets/pilot/h_black.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original heritage botanical — original, in our own hand from live trend research (antique gold on charcoal). settlement post-gen pass. gilded bough — heritage noir"},{"title":"Rosewater Trellis — Heritage Soft","cat":"in-house-original","image":"/assets/pilot/h_blush.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original heritage botanical — original, in our own hand from live trend research (sage on blush). settlement post-gen pass. rosewater trellis — heritage soft"},{"title":"Amethyst Nocturne — Moody Bloom","cat":"in-house-original","image":"/assets/pilot/m_plum.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original dark moody floral — original, in our own hand from live trend research (plum on aubergine-black). settlement post-gen pass. amethyst nocturne — moody bloom"},{"title":"Emerald Nocturne — Camellia","cat":"in-house-original","image":"/assets/pilot/m_emerald.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original dark moody floral — original, in our own hand from live trend research (blush on emerald-black). settlement post-gen pass. emerald nocturne — camellia"},{"title":"Atomic Meadow — Mid-Century Retro","cat":"in-house-original","image":"/assets/pilot/lane4.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original retro mid-century — original, in our own hand from live trend research (mustard, teal & burnt-orange). settlement post-gen pass. atomic meadow — mid-century retro"},{"title":"Tide Lines — Coastal Stripe","cat":"in-house-original","image":"/assets/pilot/lane5.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original coastal stripe — original, in our own hand from live trend research (faded navy, sage & sand). settlement post-gen pass. tide lines — coastal stripe"},{"title":"Folk Tile — Block Print Geometry","cat":"in-house-original","image":"/assets/pilot/lane6.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original block-print geometry — original, in our own hand from live trend research (indigo & terracotta on cream). settlement post-gen pass. folk tile — block print geometry"},{"title":"Calico Vine — Heritage Ditsy","cat":"in-house-original","image":"/assets/pilot/lane1c2.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original heritage botanical ditsy — original, in our own hand from live trend research (terracotta & olive on bone). settlement post-gen pass. calico vine — heritage ditsy"},{"title":"Cottage Wash — Painterly Floral","cat":"in-house-original","image":"/assets/pilot/lane3b.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original painterly watercolor floral — original, in our own hand from live trend research (blush + sage on warm cream). settlement post-gen pass. cottage wash — painterly floral"},{"title":"Indigo Trellis — Heritage Vine","cat":"in-house-original","image":"/assets/pilot/lane1b.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original heritage botanical trellis — original, in our own hand from live trend research (deep indigo on oatmeal). settlement post-gen pass. indigo trellis — heritage vine"},{"title":"Crimson Nocturne — Moody Rose","cat":"in-house-original","image":"/assets/pilot/lane2b.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original dark moody floral — original, in our own hand from live trend research (burgundy rose on charcoal). settlement post-gen pass. crimson nocturne — moody rose"},{"title":"Teal Nocturne — Magnolia Bloom","cat":"in-house-original","image":"/assets/pilot/lane2c.webp","url":null,"vendor":"DW in-house original","keywords":"in-house-original dark moody floral — original, in our own hand from live trend research (ivory magnolia on deep teal). settlement post-gen pass. teal nocturne — magnolia bloom"},{"title":"Greige Carouse No.57710","cat":"drunk-animals","image":"https://wallpapersback.com/designs/img/by-id/57710","url":"https://wallpapersback.com/design/57710","vendor":"Wallpaper's Back (our original)","keywords":"drunk-animals revelrous animal characters mid-carouse on a soft greige ground — conversation-piece whimsy with a neutral palette that still behaves in a real room. greige carouse no.57710"},{"title":"Chamois Atelier No.56250","cat":"muybridge-plate · saddle-mocha","image":"https://wallpapersback.com/designs/img/by-id/56250","url":"https://wallpapersback.com/design/56250","vendor":"Wallpaper's Back (our original)","keywords":"muybridge-plate · saddle-mocha muybridge-style sequential motion plates in saddle-mocha on chamois — a scholarly, gallery-wall rhythm for studies, bars and menswear-leaning spaces. chamois atelier no.56250"},{"title":"Apricot Folio No.40178","cat":"chinoiserie · champagne-linen","image":"https://wallpapersback.com/designs/img/by-id/40178","url":"https://wallpapersback.com/design/40178","vendor":"Wallpaper's Back (our original)","keywords":"chinoiserie · champagne-linen hand-painted-look chinoiserie branches drifting across a champagne-linen ground — classic east-meets-west elegance in warm apricot neutrals. apricot folio no.40178"},{"title":"Chamois Reverie No.57669","cat":"monterey-mural","image":"https://wallpapersback.com/designs/img/by-id/57669","url":"https://wallpapersback.com/design/57669","vendor":"Wallpaper's Back (our original)","keywords":"monterey-mural full-wall monterey coastal reverie in sun-washed chamois — a panel mural that reads as fine-art scenery rather than repeat wallpaper. chamois reverie no.57669"},{"title":"Oxblood Origin No.40172","cat":"stripe · oxblood","image":"https://wallpapersback.com/designs/img/by-id/40172","url":"https://wallpapersback.com/design/40172","vendor":"Wallpaper's Back (our original)","keywords":"stripe · oxblood a disciplined heritage stripe in deep oxblood — tailored, masculine, and instantly at home in libraries, halls and hospitality corridors. oxblood origin no.40172"},{"title":"Chamois Studio No.54111","cat":"designer-zoo-calm","image":"https://wallpapersback.com/designs/img/by-id/54111","url":"https://wallpapersback.com/design/54111","vendor":"Wallpaper's Back (our original)","keywords":"designer-zoo-calm a serene designer-zoo menagerie in muted chamois — calm, illustrative animals with the restraint of a high-end nursery-to-study crossover. chamois studio no.54111"},{"title":"Foxes Slumber in Bottle Green","cat":"stoned-animals","image":"https://wallpapersback.com/designs/img/by-id/7448","url":"https://wallpapersback.com/design/7448","vendor":"Wallpaper's Back (our original)","keywords":"stoned-animals heavy-lidded foxes slumbering in bottle green — moody storybook charm with saturated, jewel-box color. foxes slumber in bottle green"},{"title":"Greige Reverie No.10024","cat":"inspired","image":"https://wallpapersback.com/designs/img/by-id/10024","url":"https://wallpapersback.com/design/10024","vendor":"Wallpaper's Back (our original)","keywords":"inspired a heritage-inspired abstract in layered greige — quiet, textural pattern that flatters furniture instead of fighting it. greige reverie no.10024"},{"title":"Terracotta Atelier No.57678","cat":"damask","image":"https://wallpapersback.com/designs/img/by-id/57678","url":"https://wallpapersback.com/design/57678","vendor":"Wallpaper's Back (our original)","keywords":"damask traditional damask reworked in terracotta and umber — old-world formality with an earthy, contemporary temperature. terracotta atelier no.57678"},{"title":"Chamois Folio No.57718","cat":"cactus","image":"https://wallpapersback.com/designs/img/by-id/57718","url":"https://wallpapersback.com/design/57718","vendor":"Wallpaper's Back (our original)","keywords":"cactus desert cactus silhouettes on warm chamois — southwestern character kept polite enough for coastal and transitional rooms. chamois folio no.57718"},{"title":"Greige Folio No.38848","cat":"cactus-11ft-mural","image":"https://wallpapersback.com/designs/img/by-id/38848","url":"https://wallpapersback.com/design/38848","vendor":"Wallpaper's Back (our original)","keywords":"cactus-11ft-mural an 11-foot floor-to-ceiling cactus mural in soft greige — sculptural desert scale for feature walls and lobbies. greige folio no.38848"},{"title":"Apricot Garden No.138","cat":"floral","image":"https://wallpapersback.com/designs/img/by-id/138","url":"https://wallpapersback.com/design/138","vendor":"Wallpaper's Back (our original)","keywords":"floral a soft apricot garden floral — romantic, painterly blooms with vintage-print warmth. apricot garden no.138"},{"title":"Chamois Folio No.40138","cat":"designer-scenic · chamois","image":"https://wallpapersback.com/designs/img/by-id/40138","url":"https://wallpapersback.com/design/40138","vendor":"Wallpaper's Back (our original)","keywords":"designer-scenic · chamois a scenic panoramic toile in chamois — pastoral storytelling in a single sweeping colorway. chamois folio no.40138"},{"title":"Prussian Origin No.57717","cat":"geometric-block-leaves","image":"https://wallpapersback.com/designs/img/by-id/57717","url":"https://wallpapersback.com/design/57717","vendor":"Wallpaper's Back (our original)","keywords":"geometric-block-leaves block-printed leaf geometry on deep prussian blue — graphic, wood-block texture with serious nighttime drama. prussian origin no.57717"},{"title":"Greige Lattice No.57705","cat":"geometric","image":"https://wallpapersback.com/designs/img/by-id/57705","url":"https://wallpapersback.com/design/57705","vendor":"Wallpaper's Back (our original)","keywords":"geometric a greige lattice geometric — architectural trellis order that layers under art and millwork effortlessly. greige lattice no.57705"},{"title":"Celadon Reverie No.54049","cat":"drunk-monkeys-v2","image":"https://wallpapersback.com/designs/img/by-id/54049","url":"https://wallpapersback.com/design/54049","vendor":"Wallpaper's Back (our original)","keywords":"drunk-monkeys-v2 playful monkeys tumbling across a celadon ground — chinoiserie mischief in a fresh botanical green. celadon reverie no.54049"},{"title":"Sloths Saunter in Greige","cat":"drunk-animals","image":"https://wallpapersback.com/designs/img/by-id/57688","url":"https://wallpapersback.com/design/57688","vendor":"Wallpaper's Back (our original)","keywords":"drunk-animals sloths sauntering through a greige canopy — slow-living humor rendered in grown-up neutrals. sloths saunter in greige"},{"title":"Chamois Reverie No.56249","cat":"muybridge-plate · saddle-mocha","image":"https://wallpapersback.com/designs/img/by-id/56249","url":"https://wallpapersback.com/design/56249","vendor":"Wallpaper's Back (our original)","keywords":"muybridge-plate · saddle-mocha a second muybridge motion-study plate in chamois reverie — sepia sequences with archival, printed-page character. chamois reverie no.56249"},{"title":"Chamois Origin No.40177","cat":"chinoiserie · smoke-ash","image":"https://wallpapersback.com/designs/img/by-id/40177","url":"https://wallpapersback.com/design/40177","vendor":"Wallpaper's Back (our original)","keywords":"chinoiserie · smoke-ash chinoiserie branchwork in smoke-ash over warm chamois — softened contrast for bedrooms and quiet dining rooms. chamois origin no.40177"},{"title":"Chamois Folio No.57668","cat":"monterey-mural","image":"https://wallpapersback.com/designs/img/by-id/57668","url":"https://wallpapersback.com/design/57668","vendor":"Wallpaper's Back (our original)","keywords":"monterey-mural a companion monterey mural panel in pale chamois — airy coastal scenery for multi-panel installations. chamois folio no.57668"},{"title":"Honey Studio No.40171","cat":"stripe · antique-brass","image":"https://wallpapersback.com/designs/img/by-id/40171","url":"https://wallpapersback.com/design/40171","vendor":"Wallpaper's Back (our original)","keywords":"stripe · antique-brass a honeyed stripe in antique brass — warm metallic-leaning bands with old-hotel polish. honey studio no.40171"},{"title":"Chamois Reverie No.54109","cat":"designer-zoo-calm","image":"https://wallpapersback.com/designs/img/by-id/54109","url":"https://wallpapersback.com/design/54109","vendor":"Wallpaper's Back (our original)","keywords":"designer-zoo-calm designer-zoo calm in deeper chamois — the menagerie line in a moodier, tea-stained neutral. chamois reverie no.54109"},{"title":"Raccoons Drift in Claret","cat":"stoned-animals","image":"https://wallpapersback.com/designs/img/by-id/7445","url":"https://wallpapersback.com/design/7445","vendor":"Wallpaper's Back (our original)","keywords":"stoned-animals raccoons drifting in claret — rich wine-dark ground with sly, characterful wildlife. raccoons drift in claret"},{"title":"Greige Studio No.10006","cat":"inspired","image":"https://wallpapersback.com/designs/img/by-id/10006","url":"https://wallpapersback.com/design/10006","vendor":"Wallpaper's Back (our original)","keywords":"inspired an inspired abstract study in cool greige — smoky, mineral tones for modern minimal interiors. greige studio no.10006"}]
\ No newline at end of file
diff --git a/public/assets/pilot/h_black.webp b/public/assets/pilot/h_black.webp
new file mode 100644
index 0000000..3e81061
Binary files /dev/null and b/public/assets/pilot/h_black.webp differ
diff --git a/public/assets/pilot/h_blush.webp b/public/assets/pilot/h_blush.webp
new file mode 100644
index 0000000..79a0933
Binary files /dev/null and b/public/assets/pilot/h_blush.webp differ
diff --git a/public/assets/pilot/lane1.webp b/public/assets/pilot/lane1.webp
new file mode 100644
index 0000000..d986bb3
Binary files /dev/null and b/public/assets/pilot/lane1.webp differ
diff --git a/public/assets/pilot/lane1b.webp b/public/assets/pilot/lane1b.webp
new file mode 100644
index 0000000..6906a86
Binary files /dev/null and b/public/assets/pilot/lane1b.webp differ
diff --git a/public/assets/pilot/lane1c2.webp b/public/assets/pilot/lane1c2.webp
new file mode 100644
index 0000000..2cc00d4
Binary files /dev/null and b/public/assets/pilot/lane1c2.webp differ
diff --git a/public/assets/pilot/lane2.webp b/public/assets/pilot/lane2.webp
new file mode 100644
index 0000000..2c712e7
Binary files /dev/null and b/public/assets/pilot/lane2.webp differ
diff --git a/public/assets/pilot/lane2b.webp b/public/assets/pilot/lane2b.webp
new file mode 100644
index 0000000..7c25a3e
Binary files /dev/null and b/public/assets/pilot/lane2b.webp differ
diff --git a/public/assets/pilot/lane2c.webp b/public/assets/pilot/lane2c.webp
new file mode 100644
index 0000000..85fdc25
Binary files /dev/null and b/public/assets/pilot/lane2c.webp differ
diff --git a/public/assets/pilot/lane3b.webp b/public/assets/pilot/lane3b.webp
new file mode 100644
index 0000000..4495fd9
Binary files /dev/null and b/public/assets/pilot/lane3b.webp differ
diff --git a/public/assets/pilot/lane4.webp b/public/assets/pilot/lane4.webp
new file mode 100644
index 0000000..784090b
Binary files /dev/null and b/public/assets/pilot/lane4.webp differ
diff --git a/public/assets/pilot/lane5.webp b/public/assets/pilot/lane5.webp
new file mode 100644
index 0000000..7281246
Binary files /dev/null and b/public/assets/pilot/lane5.webp differ
diff --git a/public/assets/pilot/lane6.webp b/public/assets/pilot/lane6.webp
new file mode 100644
index 0000000..ab59c9d
Binary files /dev/null and b/public/assets/pilot/lane6.webp differ
diff --git a/public/assets/pilot/m_emerald.webp b/public/assets/pilot/m_emerald.webp
new file mode 100644
index 0000000..174ac88
Binary files /dev/null and b/public/assets/pilot/m_emerald.webp differ
diff --git a/public/assets/pilot/m_plum.webp b/public/assets/pilot/m_plum.webp
new file mode 100644
index 0000000..86ea579
Binary files /dev/null and b/public/assets/pilot/m_plum.webp differ
diff --git a/public/index.html b/public/index.html
index 910f276..8577e26 100644
--- a/public/index.html
+++ b/public/index.html
@@ -144,7 +144,7 @@ function shade(h,p){ const n=parseInt(h.slice(1),16); let r=(n>>16)+p,g=((n>>8)&
 function card(it){
   const c=document.createElement('div'); c.className='card';
   const grad='linear-gradient(135deg,'+it.dominantHex+' 0%,'+shade(it.dominantHex,-18)+' 100%)';
-  c.innerHTML='<div class="swatch" style="background:'+grad+'">'
+  c.innerHTML='<div class="swatch" style="position:relative;overflow:hidden;background:'+grad+'">'+(it.image?'<img src="'+esc(it.image)+'" alt="" loading="lazy" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.remove()">':'')
     +(it.isNew?'<div class="ribbon">NEW</div>':'')
     +'<div class="cov '+it.ourCoverage+'">'+(it.ourCoverage==='gap'?'🎯 GAP':'✓ HAVE')+'</div>'
     +(it.signalRank?'<div class="rank">▲ '+it.signalRank+'</div>':'')+'</div>'
@@ -154,6 +154,7 @@ function card(it){
     +'<div class="signal"><span class="dot">●</span> '+esc(it.signal)+'</div>'
     +'<div class="tier">DW tier → '+esc(it.dwTier)+(it.url?' · <a class="src" href="'+it.url+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div>'
     +'</div>';
+  c.style.cursor="pointer"; c.title="Match against our catalog"; c.addEventListener("click",function(e){ if(!e.target.closest("a")) openMatchup(it); });
   return c;
 }
 async function fetchPage(){
@@ -199,6 +200,32 @@ new IntersectionObserver(es=>{ if(es[0].isIntersecting) fetchPage(); },{rootMarg
 
 const _v=new URLSearchParams(location.search).get('view'); if(_v==='lanes'||_v==='grid') state.view=_v;
 loadFacets(); chips(); setView(state.view);
+
+/* ---- trending → catalog matchup modal (internal "look at the item" + yesterday's find-similar) ---- */
+function openMatchup(it){
+  let m=document.getElementById('tmodal');
+  if(!m){ m=document.createElement('div'); m.id='tmodal';
+    m.style.cssText='position:fixed;inset:0;background:rgba(20,18,15,.6);display:none;align-items:center;justify-content:center;z-index:9999;padding:20px';
+    m.innerHTML='<div id="tmbox" style="background:#fff;max-width:940px;width:100%;max-height:88vh;overflow:auto;border-radius:14px"></div>';
+    m.addEventListener('click',e=>{ if(e.target===m) m.style.display='none'; });
+    document.body.appendChild(m);
+  }
+  var q=(it.style+' '+(it.color==='Multi'?'':it.color)).trim();
+  document.getElementById('tmbox').innerHTML='<div style="padding:20px 24px;border-bottom:1px solid #eee;display:flex;gap:16px;align-items:center">'
+    +(it.image?'<img src="'+it.image+'" style="width:84px;height:84px;object-fit:cover;border-radius:10px">':'')
+    +'<div><div style="font-size:18px;font-weight:600">'+esc(it.title)+'</div>'
+    +'<div style="color:#777;font-size:13px;margin-top:4px">'+esc(it.marketplace)+' · '+esc(it.style)+' · '+esc(it.color)+(it.url?' · <a href="'+it.url+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div></div>'
+    +'<button onclick="document.getElementById(\'tmodal\').style.display=\'none\'" style="margin-left:auto;border:0;background:#f2efe9;border-radius:8px;padding:8px 12px;cursor:pointer;font-size:15px">✕</button></div>'
+    +'<div style="padding:16px 24px"><div style="font-weight:600;margin-bottom:10px">🎯 Matched in our catalog <span id="tmc" style="color:#888;font-weight:400;font-size:13px"></span></div><div id="tmgrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px">Loading…</div></div>';
+  document.getElementById('tmodal').style.display='flex';
+  fetch('/api/matchup?q='+encodeURIComponent(q)).then(function(r){return r.json();}).then(function(d){
+    document.getElementById('tmc').textContent='('+d.matches.length+' of '+(d.total||0)+' shown)';
+    document.getElementById('tmgrid').innerHTML=(d.matches||[]).map(function(x){return '<a href="'+(x.url||'#')+'" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit;border:1px solid #eee;border-radius:10px;overflow:hidden;display:block">'
+      +(x.image?'<img src="'+x.image+'" style="width:100%;height:120px;object-fit:cover">':'<div style="height:120px;background:#f2efe9"></div>')
+      +'<div style="padding:8px"><div style="font-size:12px;font-weight:600;line-height:1.3">'+esc((x.title||"").slice(0,52))+'</div><div style="font-size:11px;color:#888;margin-top:3px">'+esc(x.vendor||"")+'</div><div style="font-size:11px;color:#b8860b;margin-top:4px">View on DW ↗</div></div></a>';}).join('')||'<div style="color:#999">No catalog matches — a GAP worth generating.</div>';
+  }).catch(function(){document.getElementById('tmgrid').innerHTML='<div style="color:#c00">Matchup unavailable.</div>';});
+}
+
 </script>
 </body>
 </html>
diff --git a/scripts/attach-images.js b/scripts/attach-images.js
new file mode 100644
index 0000000..f8bc75a
--- /dev/null
+++ b/scripts/attach-images.js
@@ -0,0 +1,184 @@
+#!/usr/bin/env node
+// attach-images.js — give every trending item a COPYRIGHT-SAFE, OUR-OWN image.
+//
+// Steve's rule (2026-07-03): trending.designerwallcoverings.com must show REAL images,
+// and they must be OUR OWN designs (never a scraped/hotlinked marketplace photo).
+//   • covered lanes  -> match to an EXISTING our-own design (pattern-vault: WPB originals
+//                       + self-hosted in-house-original webp).
+//   • GAP lanes      -> NO image; flag needsGeneration + a settlement-gated brief. Those
+//                       are drafted for Steve; generation is metered and never auto-run here.
+//
+// Sources of "our own designs":
+//   1. WPB by-id images (absolute https, public)  -> used directly.
+//   2. in-house-original *.webp (local-only paths) -> COPIED into public/assets/pilot/
+//      so the public site serves them same-origin (no hotlink fragility).
+//
+// Verifies every image resolves (abs URL -> 200 + image/*, local -> file exists) before writing.
+
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const ROOT = path.join(__dirname, '..');
+const PVCAT = path.join(ROOT, '..', 'pattern-vault', 'data', 'catalog.json');
+const PVASSETS = path.join(ROOT, '..', 'pattern-vault', 'public');   // in-house webp live under here (/assets/pilot/*)
+const DEST_ASSETS = path.join(ROOT, 'public');                       // self-host into our own public/
+const BEST = path.join(ROOT, 'data', 'bestsellers.json');
+const OURCAT = path.join(ROOT, 'data', 'our-catalog.json');
+
+// Must mirror server.js GAP_STYLES — gap lanes get generation, not an existing image.
+const GAP_STYLES = new Set([
+  'Animal-Skin Print', 'Art-Deco Geometric', 'Texture-Effect', 'Boho', 'Tropical',
+  'Retro Psychedelic', 'Mushroom / Goblincore', 'Paisley / Nomadic', 'Abstract Blur', 'Retro Geometric'
+]);
+
+// cute-creature lanes: great in the matchup modal, WRONG as a representative card image for
+// a floral/geometric trend — exclude from the PRIMARY card pick (still in our-catalog for matchup).
+const ANIMAL_CATS = ['drunk-animals','stoned-animals','designer-zoo-calm','drunk-monkeys','muybridge-plate'];
+
+// trending style -> extra search terms (lane synonyms)
+const STYLE_SYNONYMS = {
+  'Heritage Floral':   'heritage botanical floral bloom garden',
+  'Botanical':         'botanical heritage leaf garden vine',
+  'Painterly Floral':  'painterly watercolor floral bloom',
+  'Cottagecore Floral':'cottage floral heritage bloom garden',
+  'Midnight Flora':    'moody dark nocturne floral bloom',
+  'Heritage Trellis':  'heritage trellis lattice vine',
+  'Ditsy Micro-Print': 'ditsy heritage small floral',
+  'Chinoiserie':       'chinoiserie branch',
+  'Chinoiserie Mural': 'chinoiserie mural monterey scenic panoramic',
+  'Scenic Mural':      'scenic mural monterey panoramic panel',
+  'Toile de Jouy':     'toile scenic pastoral chinoiserie',
+  'Damask':            'damask traditional',
+  'Geometric':         'geometric lattice block trellis',
+  'Pop-Art Geometric': 'geometric block bold',
+  'Abstract':          'abstract inspired study smoky',
+  'Color Trend':       'inspired study',
+  'Calm Neutral':      'neutral greige calm quiet inspired'
+};
+
+// item color -> terms that appear in our design titles/styleLines
+const COLOR_SYNONYMS = {
+  'Forest Green':'green bottle emerald forest', 'Sage':'green sage emerald celadon',
+  'Blush':'blush apricot rosewater', 'Black':'black noir', 'Greige':'greige chamois',
+  'Terracotta':'terracotta apricot umber', 'Indigo':'indigo prussian blue', 'Navy':'navy prussian blue indigo',
+  'Claret':'claret crimson wine', 'Oxblood':'oxblood crimson wine', 'Mustard':'honey brass gold mustard',
+  'Cream Gold':'gold brass chamois honey', 'Teal':'teal celadon', 'Plum':'plum amethyst',
+  'Cloud White':'white greige neutral', 'Indigo ':'indigo prussian'
+};
+
+function norm(pv){
+  return pv.map(r => {
+    const isAbs = /^https?:\/\//.test(r.img || '');
+    const local = !isAbs && (r.img || '').startsWith('/assets/');
+    const cat = (r.category || '').toLowerCase();
+    return {
+      title: r.title,
+      cat,
+      image: isAbs ? r.img : (local ? r.img : null),        // local paths become same-origin once copied
+      srcWebp: local ? path.join(PVASSETS, r.img) : null,
+      url: r.wpbUrl || null,
+      vendor: isAbs ? "Wallpaper's Back (our original)" : 'DW in-house original',
+      keywords: [cat, r.styleLine || '', r.title || ''].join(' ').toLowerCase(),
+      isLocal: local,
+      animalOnly: ANIMAL_CATS.some(a => cat.includes(a))
+    };
+  }).filter(r => r.image);   // must have a usable image path
+}
+
+// Lane-dominant scoring: a design must hit the LANE first; color only breaks ties WITHIN the
+// right lane. This stops prose color-words in a styleLine ("...forest green + cream...") from
+// hijacking the wrong lane (e.g. a heritage trellis landing in a chinoiserie-mural slot).
+function score(item, row){
+  const styleTerms = [...new Set(((item.style + ' ' + (STYLE_SYNONYMS[item.style] || '')).toLowerCase().match(/[a-z]+/g) || []).filter(t => t.length > 2))];
+  const colorTerms = [...new Set(((COLOR_SYNONYMS[item.color] || item.color || '').toLowerCase().match(/[a-z]+/g) || []).filter(t => t.length > 2))];
+  const styleHits = styleTerms.filter(t => row.keywords.includes(t)).length;
+  if (!styleHits) return 0;                                   // no lane match -> ineligible
+  const colorHits = colorTerms.filter(t => row.keywords.includes(t)).length;
+  return styleHits * 10 + colorHits;                          // lane dominates; color refines
+}
+
+// verify an absolute image URL returns 200 + image/*
+function verifyUrl(url){
+  return new Promise(resolve => {
+    const req = https.get(url, { timeout: 12000 }, res => {
+      const ok = res.statusCode === 200 && /^image\//.test(res.headers['content-type'] || '');
+      res.destroy(); resolve(ok);
+    });
+    req.on('error', () => resolve(false));
+    req.on('timeout', () => { req.destroy(); resolve(false); });
+  });
+}
+
+async function main(){
+  const pv = JSON.parse(fs.readFileSync(PVCAT, 'utf8'));
+  const rows = norm(pv);
+
+  // 1. self-host the local in-house webp assets into our own public/
+  let copied = 0;
+  for (const r of rows.filter(x => x.isLocal)){
+    const dest = path.join(DEST_ASSETS, r.image.replace(/^\//, ''));
+    fs.mkdirSync(path.dirname(dest), { recursive: true });
+    if (r.srcWebp && fs.existsSync(r.srcWebp)) { fs.copyFileSync(r.srcWebp, dest); copied++; }
+  }
+
+  // 2. write normalized our-catalog.json for /api/matchup (strip srcWebp)
+  const ourCat = rows.map(({ srcWebp, isLocal, animalOnly, ...keep }) => keep);
+  fs.writeFileSync(OURCAT, JSON.stringify(ourCat, null, 0));
+
+  // 3. attach an our-own image to every COVERED item; flag GAP items for generation
+  const doc = JSON.parse(fs.readFileSync(BEST, 'utf8'));
+  const primary = rows.filter(r => !r.animalOnly);
+  const report = { covered: 0, gap: 0, unmatched: [], verifyFail: [] };
+  const verifyCache = new Map();
+
+  for (const it of doc.items){
+    // reset any prior image state so re-runs are idempotent
+    delete it.image; delete it.imageSource; delete it.imageDesign; delete it.isOurOriginal;
+    delete it.needsGeneration; delete it.genBrief;
+
+    if (GAP_STYLES.has(it.style)){
+      it.needsGeneration = true;
+      it.genBrief = `Original ${it.style} in ${it.color} — generated in our own hand from live trend research, settlement-gated. Trend signal: ${it.signal} (rank ${it.signalRank}).`;
+      report.gap++;
+      continue;
+    }
+
+    const ranked = primary.map(r => ({ r, s: score(it, r) })).filter(x => x.s > 0).sort((a, b) => b.s - a.s);
+    let chosen = null;
+    for (const cand of ranked){
+      const url = cand.r.image;
+      let ok;
+      if (/^https?:\/\//.test(url)){
+        if (verifyCache.has(url)) ok = verifyCache.get(url);
+        else { ok = await verifyUrl(url); verifyCache.set(url, ok); }
+      } else {
+        ok = fs.existsSync(path.join(DEST_ASSETS, url.replace(/^\//, '')));
+      }
+      if (ok){ chosen = cand.r; break; }
+      report.verifyFail.push({ item: it.title, url });
+    }
+
+    if (chosen){
+      it.image = chosen.image;
+      it.imageSource = chosen.vendor;
+      it.imageDesign = chosen.title;
+      it.isOurOriginal = true;
+      report.covered++;
+    } else {
+      report.unmatched.push(`${it.title} (${it.style} / ${it.color})`);
+    }
+  }
+
+  doc.imagesAttachedAt = doc.generatedAt;   // stamped from seed; refresh re-stamps
+  fs.writeFileSync(BEST, JSON.stringify(doc, null, 2));
+
+  console.log(`self-hosted webp copied: ${copied}`);
+  console.log(`our-catalog.json rows:   ${ourCat.length}`);
+  console.log(`covered w/ our-own image: ${report.covered}`);
+  console.log(`gap -> needsGeneration:   ${report.gap}`);
+  if (report.unmatched.length) console.log(`UNMATCHED covered (${report.unmatched.length}):\n  - ` + report.unmatched.join('\n  - '));
+  if (report.verifyFail.length) console.log(`verify-fail (skipped, tried next): ${report.verifyFail.length}`);
+}
+
+main().catch(e => { console.error(e); process.exit(1); });
diff --git a/scripts/refresh.sh b/scripts/refresh.sh
index 0409a32..130cd60 100755
--- a/scripts/refresh.sh
+++ b/scripts/refresh.sh
@@ -6,5 +6,6 @@
 set -e
 cd "$(dirname "$0")/.."
 node scripts/seed.js
+node scripts/attach-images.js   # re-attach our-own images (seed.js rewrites the file, wiping them) + flag GAP lanes
 if command -v pm2 >/dev/null 2>&1 && pm2 id trending-dw >/dev/null 2>&1; then pm2 restart trending-dw; fi
 echo "refreshed $(date)"
diff --git a/server.js b/server.js
index 0454b5d..f95d5eb 100644
--- a/server.js
+++ b/server.js
@@ -9,9 +9,10 @@ const PORT = process.env.PORT || 9951;
 const PUBLIC = path.join(__dirname, 'public');
 const DATA = path.join(__dirname, 'data', 'bestsellers.json');
 const CATALOG = path.join(__dirname, '..', 'pattern-vault', 'data', 'catalog.json');   // our own catalog (for gap scoring)
+const OURCAT = path.join(__dirname, 'data', 'our-catalog.json');   // normalized our-own designs (WPB originals + DW storefront) — built by scripts/attach-images.js
 const AUTH = process.env.BASIC_AUTH === undefined ? 'admin:DW2024!' : process.env.BASIC_AUTH; // BASIC_AUTH='' disables (public go-live is gated)
 
-const MIME = { '.html':'text/html', '.js':'application/javascript', '.css':'text/css', '.json':'application/json', '.svg':'image/svg+xml', '.ico':'image/x-icon' };
+const MIME = { '.html':'text/html', '.js':'application/javascript', '.css':'text/css', '.json':'application/json', '.svg':'image/svg+xml', '.ico':'image/x-icon', '.webp':'image/webp', '.png':'image/png', '.jpg':'image/jpeg', '.jpeg':'image/jpeg', '.gif':'image/gif', '.avif':'image/avif' };
 
 // Styles that are demonstrably selling but ABSENT/thin in our own catalog = the whitespace.
 // Derived from the 2026-07-02 gap analysis against pattern-vault/data/catalog.json.
@@ -32,6 +33,18 @@ function loadItems(){ try { return (JSON.parse(fs.readFileSync(DATA,'utf8')).ite
 // our own catalog coverage summary (best-effort; for the Lanes view "we have N")
 function ourCatalogCount(){ try { return (JSON.parse(fs.readFileSync(CATALOG,'utf8'))||[]).length; } catch(e){ return 0; } }
 
+// normalized our-own designs used by /api/matchup ([{title,image,url,vendor,style,color,keywords}])
+function loadOurCatalog(){ try { return JSON.parse(fs.readFileSync(OURCAT,'utf8')) || []; } catch(e){ return []; } }
+
+// keyword-score a query ("<style> <color>") against an our-own design row; higher = closer lane match
+function matchScore(q, row){
+  const terms = q.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 2);
+  if (!terms.length) return 0;
+  const hay = ((row.keywords||'') + ' ' + (row.style||'') + ' ' + (row.color||'') + ' ' + (row.title||'')).toLowerCase();
+  let s = 0; for (const t of terms){ if (hay.includes(t)) s += 1; }
+  return s;
+}
+
 function facets(items){
   const dim = (key) => {
     const m = {};
@@ -76,6 +89,15 @@ const server = http.createServer((req,res)=>{
     return send(res, 200, JSON.stringify({ lanes, ourCatalog: ourCatalogCount() }));
   }
 
+  if (u.pathname === '/api/matchup'){                       // trending item -> closest designs in OUR OWN catalog
+    const q = u.searchParams.get('q') || '';
+    const cat = loadOurCatalog();
+    const scored = cat.map(row => ({ row, s: matchScore(q, row) })).filter(x => x.s > 0)
+      .sort((a,b) => b.s - a.s).slice(0, 12)
+      .map(x => ({ title: x.row.title, image: x.row.image, url: x.row.url, vendor: x.row.vendor }));
+    return send(res, 200, JSON.stringify({ matches: scored, total: cat.length }));
+  }
+
   if (u.pathname === '/api/items'){
     let items = applyFilters(loadItems(), u.searchParams);
     const sort = u.searchParams.get('sort') || 'signal';

← 307d7e6 chore: macstudio3 migration — reconcile from mac2 + repoint  ·  back to Trending Dw  ·  trending: 'our lane' caption on our-own images (honesty labe 3c73597 →