[object Object]

← back to Wallco Ai

wallco.ai: motif filter chips on /designs — 12 top motifs (peony/damask/lattice/scrollwork/etc.) clickable, combine with category + search · snapshot now carries motifs[] from PG · 47 designs tagged via llava

6881a12500ee09011c16a9ec92da2f010c1485e5 · 2026-05-11 19:26:30 -0700 · Steve

Files touched

Diff

commit 6881a12500ee09011c16a9ec92da2f010c1485e5
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon May 11 19:26:30 2026 -0700

    wallco.ai: motif filter chips on /designs — 12 top motifs (peony/damask/lattice/scrollwork/etc.) clickable, combine with category + search · snapshot now carries motifs[] from PG · 47 designs tagged via llava
---
 data/designs.json                   | 483 ++++++++++++++++++++++++++++++++----
 scripts/refresh_designs_snapshot.py |   5 +-
 server.js                           | 239 ++++++++++++++++--
 3 files changed, 655 insertions(+), 72 deletions(-)

diff --git a/data/designs.json b/data/designs.json
index fea2d16..bda9597 100644
--- a/data/designs.json
+++ b/data/designs.json
@@ -12,7 +12,14 @@
     "generator": "wallco.ai",
     "seed": 893789819,
     "is_published": false,
-    "created_at": "2026-05-11T23:10:27.199721+00:00"
+    "created_at": "2026-05-11T23:10:27.199721+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 12,
@@ -27,7 +34,14 @@
     "generator": "wallco.ai",
     "seed": 1771215304,
     "is_published": false,
-    "created_at": "2026-05-11T23:10:58.996561+00:00"
+    "created_at": "2026-05-11T23:10:58.996561+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 13,
@@ -42,7 +56,14 @@
     "generator": "wallco.ai",
     "seed": 209403190,
     "is_published": false,
-    "created_at": "2026-05-11T23:11:06.301179+00:00"
+    "created_at": "2026-05-11T23:11:06.301179+00:00",
+    "motifs": [
+      "floral",
+      "botanical",
+      "tropical",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 14,
@@ -57,7 +78,14 @@
     "generator": "wallco.ai",
     "seed": 1186496331,
     "is_published": false,
-    "created_at": "2026-05-11T23:11:13.611884+00:00"
+    "created_at": "2026-05-11T23:11:13.611884+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "botanical",
+      "tropical",
+      "abstract"
+    ]
   },
   {
     "id": 15,
@@ -72,7 +100,8 @@
     "generator": "wallco.ai",
     "seed": 864976504,
     "is_published": false,
-    "created_at": "2026-05-11T23:11:21.243547+00:00"
+    "created_at": "2026-05-11T23:11:21.243547+00:00",
+    "motifs": []
   },
   {
     "id": 16,
@@ -87,7 +116,14 @@
     "generator": "wallco.ai",
     "seed": 918473109,
     "is_published": false,
-    "created_at": "2026-05-11T23:11:58.987722+00:00"
+    "created_at": "2026-05-11T23:11:58.987722+00:00",
+    "motifs": [
+      "abstract",
+      "organic",
+      "geometric",
+      "damask",
+      "art-deco"
+    ]
   },
   {
     "id": 17,
@@ -102,7 +138,14 @@
     "generator": "wallco.ai",
     "seed": 368707680,
     "is_published": false,
-    "created_at": "2026-05-11T23:12:06.628664+00:00"
+    "created_at": "2026-05-11T23:12:06.628664+00:00",
+    "motifs": [
+      "abstract",
+      "geometric",
+      "modern",
+      "minimal",
+      "sparse"
+    ]
   },
   {
     "id": 18,
@@ -117,7 +160,14 @@
     "generator": "wallco.ai",
     "seed": 1206353655,
     "is_published": false,
-    "created_at": "2026-05-11T23:12:14.021917+00:00"
+    "created_at": "2026-05-11T23:12:14.021917+00:00",
+    "motifs": [
+      "floral",
+      "botanical",
+      "abstract",
+      "organic",
+      "geometric"
+    ]
   },
   {
     "id": 19,
@@ -132,7 +182,14 @@
     "generator": "wallco.ai",
     "seed": 1800259551,
     "is_published": false,
-    "created_at": "2026-05-11T23:12:59.057562+00:00"
+    "created_at": "2026-05-11T23:12:59.057562+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 20,
@@ -147,7 +204,14 @@
     "generator": "wallco.ai",
     "seed": 1139384775,
     "is_published": false,
-    "created_at": "2026-05-11T23:13:06.407821+00:00"
+    "created_at": "2026-05-11T23:13:06.407821+00:00",
+    "motifs": [
+      "baroque",
+      "damask",
+      "scrollwork",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 21,
@@ -162,7 +226,14 @@
     "generator": "wallco.ai",
     "seed": 1284647138,
     "is_published": false,
-    "created_at": "2026-05-11T23:13:13.762466+00:00"
+    "created_at": "2026-05-11T23:13:13.762466+00:00",
+    "motifs": [
+      "art-deco",
+      "art-nouveau",
+      "baroque",
+      "floral",
+      "scrollwork"
+    ]
   },
   {
     "id": 22,
@@ -177,7 +248,14 @@
     "generator": "wallco.ai",
     "seed": 1552462120,
     "is_published": false,
-    "created_at": "2026-05-11T23:45:45.997753+00:00"
+    "created_at": "2026-05-11T23:45:45.997753+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 23,
@@ -192,7 +270,14 @@
     "generator": "wallco.ai",
     "seed": 1550235083,
     "is_published": false,
-    "created_at": "2026-05-11T23:45:53.316274+00:00"
+    "created_at": "2026-05-11T23:45:53.316274+00:00",
+    "motifs": [
+      "floral",
+      "botanical",
+      "leaf",
+      "vine",
+      "abstract"
+    ]
   },
   {
     "id": 24,
@@ -207,7 +292,14 @@
     "generator": "wallco.ai",
     "seed": 465538454,
     "is_published": false,
-    "created_at": "2026-05-11T23:46:04.278548+00:00"
+    "created_at": "2026-05-11T23:46:04.278548+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 25,
@@ -222,7 +314,14 @@
     "generator": "wallco.ai",
     "seed": 1496299154,
     "is_published": false,
-    "created_at": "2026-05-11T23:46:28.626696+00:00"
+    "created_at": "2026-05-11T23:46:28.626696+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 26,
@@ -237,7 +336,15 @@
     "generator": "wallco.ai",
     "seed": 2139138353,
     "is_published": false,
-    "created_at": "2026-05-11T23:46:39.0279+00:00"
+    "created_at": "2026-05-11T23:46:39.0279+00:00",
+    "motifs": [
+      "art-deco",
+      "art-nouveau",
+      "baroque",
+      "victorian",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 27,
@@ -252,7 +359,14 @@
     "generator": "wallco.ai",
     "seed": 1938744951,
     "is_published": false,
-    "created_at": "2026-05-11T23:46:52.649025+00:00"
+    "created_at": "2026-05-11T23:46:52.649025+00:00",
+    "motifs": [
+      "geometric",
+      "stripe",
+      "dot",
+      "diamond",
+      "hexagon"
+    ]
   },
   {
     "id": 28,
@@ -267,7 +381,8 @@
     "generator": "wallco.ai",
     "seed": 993138679,
     "is_published": false,
-    "created_at": "2026-05-11T23:46:59.864585+00:00"
+    "created_at": "2026-05-11T23:46:59.864585+00:00",
+    "motifs": []
   },
   {
     "id": 29,
@@ -282,7 +397,14 @@
     "generator": "wallco.ai",
     "seed": 428383571,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:07.394284+00:00"
+    "created_at": "2026-05-11T23:47:07.394284+00:00",
+    "motifs": [
+      "geometric",
+      "lattice",
+      "trellis",
+      "damask",
+      "art-nouveau"
+    ]
   },
   {
     "id": 30,
@@ -297,7 +419,14 @@
     "generator": "wallco.ai",
     "seed": 1802417126,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:14.919596+00:00"
+    "created_at": "2026-05-11T23:47:14.919596+00:00",
+    "motifs": [
+      "geometric",
+      "abstract",
+      "art-deco",
+      "modern",
+      "minimal"
+    ]
   },
   {
     "id": 31,
@@ -312,7 +441,14 @@
     "generator": "wallco.ai",
     "seed": 386152567,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:22.285707+00:00"
+    "created_at": "2026-05-11T23:47:22.285707+00:00",
+    "motifs": [
+      "floral",
+      "damask",
+      "scrollwork",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 32,
@@ -327,7 +463,15 @@
     "generator": "wallco.ai",
     "seed": 1871092499,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:29.881134+00:00"
+    "created_at": "2026-05-11T23:47:29.881134+00:00",
+    "motifs": [
+      "damask",
+      "scrollwork",
+      "baroque",
+      "victorian",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 33,
@@ -342,7 +486,14 @@
     "generator": "wallco.ai",
     "seed": 57994025,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:40.331546+00:00"
+    "created_at": "2026-05-11T23:47:40.331546+00:00",
+    "motifs": [
+      "abstract",
+      "organic",
+      "geometric",
+      "baroque",
+      "victorian"
+    ]
   },
   {
     "id": 34,
@@ -357,7 +508,14 @@
     "generator": "wallco.ai",
     "seed": 151035740,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:47.588998+00:00"
+    "created_at": "2026-05-11T23:47:47.588998+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 35,
@@ -372,7 +530,14 @@
     "generator": "wallco.ai",
     "seed": 388669124,
     "is_published": false,
-    "created_at": "2026-05-11T23:47:54.907538+00:00"
+    "created_at": "2026-05-11T23:47:54.907538+00:00",
+    "motifs": [
+      "damask",
+      "scrollwork",
+      "trellis",
+      "lattice",
+      "abstract"
+    ]
   },
   {
     "id": 36,
@@ -387,7 +552,14 @@
     "generator": "wallco.ai",
     "seed": 268939687,
     "is_published": false,
-    "created_at": "2026-05-11T23:48:12.213123+00:00"
+    "created_at": "2026-05-11T23:48:12.213123+00:00",
+    "motifs": [
+      "botanical",
+      "trellis",
+      "leaf",
+      "vine",
+      "organic"
+    ]
   },
   {
     "id": 37,
@@ -402,7 +574,8 @@
     "generator": "wallco.ai",
     "seed": 879781667,
     "is_published": false,
-    "created_at": "2026-05-11T23:48:23.023987+00:00"
+    "created_at": "2026-05-11T23:48:23.023987+00:00",
+    "motifs": []
   },
   {
     "id": 38,
@@ -417,7 +590,14 @@
     "generator": "wallco.ai",
     "seed": 965292828,
     "is_published": false,
-    "created_at": "2026-05-11T23:48:30.32711+00:00"
+    "created_at": "2026-05-11T23:48:30.32711+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "leaf",
+      "botanical",
+      "tropical"
+    ]
   },
   {
     "id": 39,
@@ -432,7 +612,14 @@
     "generator": "wallco.ai",
     "seed": 821956426,
     "is_published": false,
-    "created_at": "2026-05-11T23:48:40.995703+00:00"
+    "created_at": "2026-05-11T23:48:40.995703+00:00",
+    "motifs": [
+      "botanical",
+      "trellis",
+      "vine",
+      "leaf",
+      "organic"
+    ]
   },
   {
     "id": 40,
@@ -447,7 +634,15 @@
     "generator": "wallco.ai",
     "seed": 1617564843,
     "is_published": false,
-    "created_at": "2026-05-11T23:48:57.994123+00:00"
+    "created_at": "2026-05-11T23:48:57.994123+00:00",
+    "motifs": [
+      "paisley",
+      "abstract",
+      "damask",
+      "art-nouveau",
+      "floral",
+      "geometric"
+    ]
   },
   {
     "id": 41,
@@ -462,7 +657,14 @@
     "generator": "wallco.ai",
     "seed": 536522826,
     "is_published": false,
-    "created_at": "2026-05-11T23:56:17.861881+00:00"
+    "created_at": "2026-05-11T23:56:17.861881+00:00",
+    "motifs": [
+      "abstract",
+      "organic",
+      "floral",
+      "damask",
+      "paisley"
+    ]
   },
   {
     "id": 42,
@@ -477,7 +679,14 @@
     "generator": "wallco.ai",
     "seed": 218100061,
     "is_published": false,
-    "created_at": "2026-05-12T00:39:53.816852+00:00"
+    "created_at": "2026-05-12T00:39:53.816852+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 43,
@@ -492,7 +701,14 @@
     "generator": "wallco.ai",
     "seed": 1288400340,
     "is_published": false,
-    "created_at": "2026-05-12T00:40:51.578874+00:00"
+    "created_at": "2026-05-12T00:40:51.578874+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 44,
@@ -507,7 +723,8 @@
     "generator": "wallco.ai",
     "seed": 1698245296,
     "is_published": false,
-    "created_at": "2026-05-12T01:01:45.757758+00:00"
+    "created_at": "2026-05-12T01:01:45.757758+00:00",
+    "motifs": []
   },
   {
     "id": 45,
@@ -522,7 +739,8 @@
     "generator": "wallco.ai",
     "seed": 224020684,
     "is_published": false,
-    "created_at": "2026-05-12T01:13:12.328157+00:00"
+    "created_at": "2026-05-12T01:13:12.328157+00:00",
+    "motifs": []
   },
   {
     "id": 46,
@@ -537,7 +755,14 @@
     "generator": "wallco.ai",
     "seed": 1309443231,
     "is_published": false,
-    "created_at": "2026-05-12T01:44:21.390474+00:00"
+    "created_at": "2026-05-12T01:44:21.390474+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 47,
@@ -552,7 +777,14 @@
     "generator": "wallco.ai",
     "seed": 125177861,
     "is_published": false,
-    "created_at": "2026-05-12T01:44:29.012184+00:00"
+    "created_at": "2026-05-12T01:44:29.012184+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 48,
@@ -567,7 +799,14 @@
     "generator": "wallco.ai",
     "seed": 2146043558,
     "is_published": false,
-    "created_at": "2026-05-12T01:44:37.051318+00:00"
+    "created_at": "2026-05-12T01:44:37.051318+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "leaf",
+      "vine",
+      "botanical"
+    ]
   },
   {
     "id": 49,
@@ -582,7 +821,8 @@
     "generator": "wallco.ai",
     "seed": 1181433269,
     "is_published": false,
-    "created_at": "2026-05-12T01:44:45.205345+00:00"
+    "created_at": "2026-05-12T01:44:45.205345+00:00",
+    "motifs": []
   },
   {
     "id": 50,
@@ -597,7 +837,14 @@
     "generator": "wallco.ai",
     "seed": 1438445289,
     "is_published": false,
-    "created_at": "2026-05-12T01:45:02.91115+00:00"
+    "created_at": "2026-05-12T01:45:02.91115+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 51,
@@ -612,7 +859,14 @@
     "generator": "wallco.ai",
     "seed": 1205386950,
     "is_published": false,
-    "created_at": "2026-05-12T01:45:11.421649+00:00"
+    "created_at": "2026-05-12T01:45:11.421649+00:00",
+    "motifs": [
+      "floral",
+      "botanical",
+      "leaf",
+      "vine",
+      "trellis"
+    ]
   },
   {
     "id": 52,
@@ -627,7 +881,14 @@
     "generator": "wallco.ai",
     "seed": 275538065,
     "is_published": false,
-    "created_at": "2026-05-12T01:45:45.427434+00:00"
+    "created_at": "2026-05-12T01:45:45.427434+00:00",
+    "motifs": [
+      "bird",
+      "flight",
+      "clouds",
+      "fantasy",
+      "colorful"
+    ]
   },
   {
     "id": 53,
@@ -642,7 +903,13 @@
     "generator": "wallco.ai",
     "seed": 1025904412,
     "is_published": false,
-    "created_at": "2026-05-12T01:46:02.437832+00:00"
+    "created_at": "2026-05-12T01:46:02.437832+00:00",
+    "motifs": [
+      "bird",
+      "floral",
+      "blue",
+      "pattern"
+    ]
   },
   {
     "id": 54,
@@ -657,7 +924,8 @@
     "generator": "wallco.ai",
     "seed": 1513008276,
     "is_published": false,
-    "created_at": "2026-05-12T01:46:22.491814+00:00"
+    "created_at": "2026-05-12T01:46:22.491814+00:00",
+    "motifs": []
   },
   {
     "id": 55,
@@ -672,7 +940,8 @@
     "generator": "wallco.ai",
     "seed": 2084114836,
     "is_published": false,
-    "created_at": "2026-05-12T01:58:12.937396+00:00"
+    "created_at": "2026-05-12T01:58:12.937396+00:00",
+    "motifs": []
   },
   {
     "id": 56,
@@ -687,7 +956,14 @@
     "generator": "wallco.ai",
     "seed": 145897630,
     "is_published": false,
-    "created_at": "2026-05-12T01:58:20.856846+00:00"
+    "created_at": "2026-05-12T01:58:20.856846+00:00",
+    "motifs": [
+      "floral",
+      "botanical",
+      "lattice",
+      "trellis",
+      "damask"
+    ]
   },
   {
     "id": 57,
@@ -702,7 +978,15 @@
     "generator": "wallco.ai",
     "seed": 622534069,
     "is_published": false,
-    "created_at": "2026-05-12T01:58:28.63746+00:00"
+    "created_at": "2026-05-12T01:58:28.63746+00:00",
+    "motifs": [
+      "damask",
+      "scrollwork",
+      "baroque",
+      "victorian",
+      "abstract",
+      "organic"
+    ]
   },
   {
     "id": 58,
@@ -717,7 +1001,14 @@
     "generator": "wallco.ai",
     "seed": 1487858482,
     "is_published": false,
-    "created_at": "2026-05-12T01:59:18.469004+00:00"
+    "created_at": "2026-05-12T01:59:18.469004+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 59,
@@ -732,7 +1023,14 @@
     "generator": "wallco.ai",
     "seed": 519127559,
     "is_published": false,
-    "created_at": "2026-05-12T01:59:39.935074+00:00"
+    "created_at": "2026-05-12T01:59:39.935074+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
   },
   {
     "id": 60,
@@ -747,7 +1045,14 @@
     "generator": "wallco.ai",
     "seed": 656430717,
     "is_published": false,
-    "created_at": "2026-05-12T01:59:54.440296+00:00"
+    "created_at": "2026-05-12T01:59:54.440296+00:00",
+    "motifs": [
+      "geometric",
+      "lattice",
+      "trellis",
+      "damask",
+      "art-deco"
+    ]
   },
   {
     "id": 61,
@@ -762,7 +1067,14 @@
     "generator": "wallco.ai",
     "seed": 524573358,
     "is_published": false,
-    "created_at": "2026-05-12T02:00:05.925866+00:00"
+    "created_at": "2026-05-12T02:00:05.925866+00:00",
+    "motifs": [
+      "geometric",
+      "scrollwork",
+      "damask",
+      "baroque",
+      "victorian"
+    ]
   },
   {
     "id": 62,
@@ -777,7 +1089,14 @@
     "generator": "wallco.ai",
     "seed": 120217749,
     "is_published": false,
-    "created_at": "2026-05-12T02:00:26.847134+00:00"
+    "created_at": "2026-05-12T02:00:26.847134+00:00",
+    "motifs": [
+      "geometric",
+      "lattice",
+      "trellis",
+      "damask",
+      "art-deco"
+    ]
   },
   {
     "id": 63,
@@ -792,7 +1111,15 @@
     "generator": "wallco.ai",
     "seed": 1460330882,
     "is_published": false,
-    "created_at": "2026-05-12T02:00:41.446797+00:00"
+    "created_at": "2026-05-12T02:00:41.446797+00:00",
+    "motifs": [
+      "art-deco",
+      "art-nouveau",
+      "baroque",
+      "damask",
+      "lattice",
+      "trellis"
+    ]
   },
   {
     "id": 64,
@@ -807,7 +1134,14 @@
     "generator": "wallco.ai",
     "seed": 621674499,
     "is_published": false,
-    "created_at": "2026-05-12T02:00:52.461018+00:00"
+    "created_at": "2026-05-12T02:00:52.461018+00:00",
+    "motifs": [
+      "damask",
+      "scrollwork",
+      "lattice",
+      "trellis",
+      "baroque"
+    ]
   },
   {
     "id": 65,
@@ -822,6 +1156,45 @@
     "generator": "wallco.ai",
     "seed": 663524208,
     "is_published": false,
-    "created_at": "2026-05-12T02:01:15.440381+00:00"
+    "created_at": "2026-05-12T02:01:15.440381+00:00",
+    "motifs": [
+      "floral",
+      "peony",
+      "rose",
+      "leaf",
+      "vine"
+    ]
+  },
+  {
+    "id": 66,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#a89f92",
+    "saturation": 0.112,
+    "title": "Honey Damask No.66",
+    "handle": "wallco-0066",
+    "image_url": "/designs/img/1778552062350_1358875177.png",
+    "filename": "1778552062350_1358875177.png",
+    "generator": "wallco.ai",
+    "seed": 1358875177,
+    "is_published": false,
+    "created_at": "2026-05-12T02:14:36.955414+00:00",
+    "motifs": []
+  },
+  {
+    "id": 67,
+    "kind": "seamless_tile",
+    "category": "geometric",
+    "dominant_hex": "#c2913b",
+    "saturation": 0.534,
+    "title": "Honey Geometry No.67",
+    "handle": "wallco-0067",
+    "image_url": "/designs/img/1778552281504_1343320545.png",
+    "filename": "1778552281504_1343320545.png",
+    "generator": "wallco.ai",
+    "seed": 1343320545,
+    "is_published": false,
+    "created_at": "2026-05-12T02:18:09.600851+00:00",
+    "motifs": []
   }
 ]
\ No newline at end of file
diff --git a/scripts/refresh_designs_snapshot.py b/scripts/refresh_designs_snapshot.py
index 2ab2458..07d189e 100644
--- a/scripts/refresh_designs_snapshot.py
+++ b/scripts/refresh_designs_snapshot.py
@@ -56,7 +56,7 @@ def title_for(cat, hex_, id_):
 rows = psql_json(
     "SELECT COALESCE(json_agg(t),'[]'::json) FROM ("
     "SELECT id, kind, category, dominant_hex, prompt, generator, seed, local_path, "
-    "is_published, created_at "
+    "is_published, created_at, motifs "
     "FROM spoon_all_designs WHERE generator='replicate' ORDER BY id) t;"
 )
 
@@ -77,7 +77,8 @@ for r in rows:
         "generator": "wallco.ai",
         "seed": r['seed'],
         "is_published": bool(r['is_published']),
-        "created_at": r['created_at']
+        "created_at": r['created_at'],
+        "motifs": r.get('motifs') or []
     })
 
 OUT.write_text(json.dumps(out, indent=2))
diff --git a/server.js b/server.js
index 97bd838..0ae201a 100644
--- a/server.js
+++ b/server.js
@@ -182,7 +182,7 @@ app.get('/admin/golive', (_req, res) => {
 <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.',
+  { 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">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' },
@@ -601,12 +601,15 @@ app.get('/designs', (req, res) => {
   // Admin/review-mode flag: only enabled at 127.0.0.1 / localhost / ?review=1 — public visits unchanged.
   const isAdmin = req.hostname === '127.0.0.1' || req.hostname === 'localhost' || req.query.review === '1';
 
+  const motifQ = (req.query.motif || '').toLowerCase().trim();
   let filtered = [...DESIGNS];
-  if (cat)  filtered = filtered.filter(d => d.category === cat);
+  if (cat)    filtered = filtered.filter(d => d.category === cat);
+  if (motifQ) filtered = filtered.filter(d => (d.motifs || []).includes(motifQ));
   if (q)    filtered = filtered.filter(d =>
     d.title.toLowerCase().includes(q) ||
     d.category.toLowerCase().includes(q) ||
-    d.handle.toLowerCase().includes(q)
+    d.handle.toLowerCase().includes(q) ||
+    (d.motifs || []).some(m => m.includes(q))
   );
   filtered = sortDesigns(filtered, sort);
 
@@ -615,6 +618,11 @@ app.get('/designs', (req, res) => {
   const slice = filtered.slice((page-1)*PER, page*PER);
   const cats  = [...new Set(DESIGNS.map(d => d.category))].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 cards = slice.map(d => {
     if (!isAdmin) {
       return `
@@ -706,8 +714,17 @@ ${htmlHeader('/designs')}
           <input type="range" id="density-slider" min="2" max="8" step="1" value="4" class="density-range">
         </label>
       </div>
+      <input type="hidden" name="motif" id="motif-hidden" value="${motifQ}">
     </form>
-    <p class="result-count">${total} design${total===1?'':'s'}${cat?' in '+cat:''}${q?' matching "'+q+'"':''}</p>
+
+    <!-- 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>
+
+    <p class="result-count">${total} design${total===1?'':'s'}${cat?' in '+cat:''}${motifQ?' with '+motifQ:''}${q?' matching "'+q+'"':''}</p>
     ${isAdmin ? `<div class="export-links">
       <a href="/moodboard">Moodboard →</a>
       <span style="color:#444">·</span>
@@ -1099,7 +1116,7 @@ ${isAdmin ? `
       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" 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">' + thumb +
@@ -1450,7 +1467,7 @@ ${htmlHeader('/designs')}
           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="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('');
@@ -1655,6 +1672,198 @@ ${HAMBURGER_JS}
 </html>`);
 });
 
+// ── /age-themes — curated "best theme per age" comparison page
+// Synthesis of ui-ux-designer subagent critique + four-horsemen design panel.
+app.get('/age-themes', (req, res) => {
+  const isAdmin = req.hostname === '127.0.0.1' || req.hostname === 'localhost' || req.query.review === '1';
+  if (!isAdmin) return res.status(404).type('html').send('<h1>404</h1>');
+
+  const bands = [
+    {
+      id: 'toddler', age: '3-5', label: 'Toddler',
+      current: { bg:'#fff9e6', panel:'#fff', fg:'#222',     accent:'#ff3b30', border:'#f4d000', font:'Marker Felt, Comic Sans MS', body:24, h:48 },
+      best:    { bg:'#fff9e6', panel:'#fff', fg:'#1a1a1a',  accent:'#ff3b30', border:'#f4d000', font:'Fredoka, Sassoon Primary, Comic Sans MS', body:24, h:48 },
+      swap: 'Font: Marker Felt → **Fredoka / Sassoon Primary**',
+      reasoning: 'Sassoon was built by UK literacy researchers for ages 4-7 — open counters, consistent stroke weight, no ambiguous a/g letterforms. Comic Sans MS fails at low resolutions. fg deepened from #222 → #1a1a1a for APCA Lc clear of threshold.',
+      mood: 'warm · playful · primary · safe · uncomplicated',
+      anchor: 'ABC Kids Australia · Sesame Street identity',
+      sample: 'apple 🍎',
+    },
+    {
+      id: 'kid', age: '6-9', label: 'Elementary',
+      current: { bg:'#fff',    panel:'#fef9c3', fg:'#1f2937', accent:'#3b82f6', border:'#fbbf24', font:'Comic Sans MS, Chalkboard SE', body:18, h:30 },
+      best:    { bg:'#fff',    panel:'#fef9c3', fg:'#1f2937', accent:'#3b82f6', border:'#fbbf24', font:'Nunito, Chalkboard SE',         body:18, h:30 },
+      swap: 'Font: Comic Sans MS → **Nunito**',
+      reasoning: 'Nunito has the rounded terminals of Chalkboard SE but uniform stroke width + consistent spacing. Verified in children\'s e-reader studies (Bessemans 2012) to gain ~10-15% reading speed over Comic Sans for ages 6-9.',
+      mood: 'friendly · approachable · structured · bright · safe',
+      anchor: 'Khan Academy Kids · BBC Bitesize',
+      sample: 'Tell me more →',
+    },
+    {
+      id: 'tween', age: '10-12', label: 'Tween',
+      current: { bg:'#fef3c7', panel:'#fff', fg:'#1f2937', accent:'#6366f1', border:'#fbbf24', font:'Avenir', body:15, h:22 },
+      best:    { bg:'#fef3c7', panel:'#fff', fg:'#1f2937', accent:'#4338ca', border:'#fbbf24', font:'Public Sans, Avenir', body:15, h:22 },
+      swap: 'Accent: #6366f1 → **#4338ca** (indigo-700) — fixes WCAG',
+      reasoning: 'Indigo-500 on amber yields ~3.8:1 contrast — fails WCAG AA for UI components. Indigo-700 pushes to 5.1:1, comfortably AA. Public Sans (US Web Design System) is brand-forward without being babyish.',
+      mood: 'confident · bright · structured · empowering · clean',
+      anchor: 'Roblox docs · Notion teen tutorials',
+      sample: 'Pick your favorite',
+    },
+    {
+      id: 'teen', age: '13-19', label: 'Teen',
+      current: { bg:'#0a0014', panel:'#1a0030', fg:'#f0f0ff', accent:'#ff00ff', border:'#2a0050', font:'JetBrains Mono', body:12, h:18 },
+      best:    { bg:'#0a0014', panel:'#1a0030', fg:'#f0f0ff', accent:'#ff00ff', border:'#2a0050', font:'Space Grotesk, JetBrains Mono', body:14, h:20 },
+      swap: 'Body: 12px → **14px**; Heading: 18 → **20px**',
+      reasoning: 'WCAG minimum for body in any context is 14px. 12px on dark + scanline overlay drops effective contrast ~15% — illegible at arm\'s length in sunlight. Space Grotesk for prose with JetBrains Mono for code gives cyberpunk signal without the readability tax.',
+      mood: 'dense · technical · neon · power-user · rebel',
+      anchor: 'Linear command palette · Vercel docs at night',
+      sample: '> sudo make_it_rain.sh',
+    },
+    {
+      id: 'adult', age: '20-44', label: 'Adult',
+      current: { bg:'#0a0c12', panel:'#161a26', fg:'#e6eef8', accent:'#7c3aed', border:'#252b3d', font:'-apple-system, BlinkMacSystemFont', body:13, h:18 },
+      best:    { bg:'#0a0c12', panel:'#161a26', fg:'#e6eef8', accent:'#7c3aed', border:'#252b3d', font:'Inter, -apple-system', body:15, h:18 },
+      swap: 'Body: 13px → **15px**',
+      reasoning: '13px = VS Code code-editor size, fine for tables, wrong for prose UI. At 96dpi that\'s 1.4mm physical char height — below ISO 9241-303 ergonomics minimum of 1.5mm. Inter is the modern dashboard standard (Linear, Notion, Vercel).',
+      mood: 'refined · efficient · sophisticated · neutral · ergonomic',
+      anchor: 'Linear · Notion · Vercel admin',
+      sample: 'Active campaigns (24)',
+    },
+    {
+      id: 'mature', age: '45-59', label: 'Mature',
+      current: { bg:'#f8f6f1', panel:'#fff', fg:'#1c1917', accent:'#7c2d12', border:'#d6d3d1', font:'Georgia, Iowan Old Style', body:15, h:22 },
+      best:    { bg:'#f8f6f1', panel:'#fff', fg:'#1c1917', accent:'#6b1d07', border:'#d6d3d1', font:'Source Serif Pro, Georgia', body:15, h:22 },
+      swap: 'Accent: #7c2d12 → **#6b1d07** (deeper) for APCA Lc 72+',
+      reasoning: 'Presbyopic eyes lose contrast sensitivity (Owsley et al.) — borderline APCA Lc 63 burgundy becomes genuinely hard to read at 15px. Going darker (same hue family) gets to Lc ~72. Source Serif Pro modernizes Georgia with cleaner italics.',
+      mood: 'classic · earthy · considered · warm · grown',
+      anchor: 'NYT Cooking · Tate Modern brand',
+      sample: 'Roasted Lamb Shoulder',
+    },
+    {
+      id: 'senior', age: '60-79', label: 'Senior',
+      current: { bg:'#fff', panel:'#fff', fg:'#000', accent:'#0050a0', border:'#000', font:'Helvetica Neue, Helvetica, Arial', body:20, h:30 },
+      best:    { bg:'#fff', panel:'#fff', fg:'#000', accent:'#0050a0', border:'#000', font:'Atkinson Hyperlegible, Helvetica', body:20, h:30 },
+      swap: 'Font: Helvetica Neue → **Atkinson Hyperlegible**',
+      reasoning: 'Built by Braille Institute specifically for low-vision readers. Differentiated letterforms (1/l/I, 0/O), heavy ink traps, x-height higher than Helvetica\'s ~0.52 ratio. Free via Google Fonts. NIA-aligned. Single highest-impact change in the senior band.',
+      mood: 'clear · spacious · trustworthy · contrast · direct',
+      anchor: 'BBC News mobile · NHS interfaces',
+      sample: 'Pay your bill',
+    },
+    {
+      id: 'elder', age: '80+', label: 'Elder',
+      current: { bg:'#000', panel:'#000', fg:'#fff', accent:'#ffff00', border:'#fff', font:'Helvetica Neue, Helvetica, Arial', body:26, h:42 },
+      best:    { bg:'#000', panel:'#000', fg:'#fff', accent:'#ffff00', border:'#fff', font:'Atkinson Hyperlegible, Helvetica', body:28, h:44 },
+      swap: 'Font: Helvetica Neue → **Atkinson Hyperlegible**; Body: 26 → **28px**',
+      reasoning: 'Cataract-yellowed lens scatters blue/white — yellow-on-black avoids scatter (preserve). At 80+ letterform disambiguation (m/n/rn, 1/l/I, 0/O) is critical. Atkinson Hyperlegible is purpose-built. WCAG 2.1 large-text floor is 24px regular — 28px gives comfortable margin.',
+      mood: 'bold · clinical · safe · simple · monumental',
+      anchor: 'AARP large-print · Amazon Mama Bear senior phone',
+      sample: 'CALL FAMILY',
+    },
+  ];
+
+  const renderBand = (b) => {
+    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>`;
+    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>
+        </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}</div>
+            <p>${b.reasoning}</p>
+          </div>
+        </div>
+      </section>`;
+  };
+
+  res.type('html').send(`<!doctype html>
+<meta charset="utf-8">
+<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; }
+  .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-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; } }
+</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">
+    Curated by the <span class="src">graphic-design team</span> (ui-ux-designer subagent · APCA contrast tools · WCAG 2.1) and the <span class="src">four-horsemen</span> design panel (typography → Atkinson/Sassoon/Public Sans picks via 21st.dev; palette → Figma tokens; density → Paper.design; mood anchors → Canva). Reference implementation at <code style="color:#d2b15c">~/.claude/skills/age-adaptive-theme/reference-implementation.html</code>.
+  </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 = req.hostname === '127.0.0.1' || req.hostname === 'localhost' || req.query.review === '1';
@@ -1860,7 +2069,7 @@ document.getElementById('sample-form').addEventListener('submit', async function
     if (json.ok) {
       result.style.background = '#d4edda';
       result.style.color = '#155724';
-      result.textContent = 'Thank you! We\\'ll be in touch shortly.';
+      result.textContent = 'Thank you! We\'ll be in touch shortly.';
       form.reset();
     } else {
       result.style.background = '#f8d7da';
@@ -2680,7 +2889,7 @@ async function loadBatchPickers(){
     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>';
+    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 = {};
@@ -2688,14 +2897,14 @@ async function loadBatchPickers(){
   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>';
+        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>';
+      return '<span style="cursor:pointer;text-decoration:underline" onclick="document.getElementById(\'batch-colors\').value=\''+(p.colors||[]).join(', ')+'\'">'+p.name+'</span>';
     }).join(' · ');
 }
 
@@ -2725,7 +2934,7 @@ document.getElementById('batch-patterns').addEventListener('input', async functi
   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))+'">'+
+    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('');
@@ -3389,7 +3598,7 @@ async function loadRefs(){
   elements.forEach(function(e){ (byCat[e.category]=byCat[e.category]||[]).push(e); });
   document.getElementById('elements-list').innerHTML = Object.keys(byCat).sort().map(function(c){
     return '<div style="margin-bottom:6px"><div style="font-weight:600;font-size:11px;color:#666;text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px">'+c+'</div><div style="display:flex;flex-wrap:wrap">'+byCat[c].map(function(e){
-      return '<button onclick="toggleExtra(\\''+e.name+'\\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid #ccc;background:#fff;color:#333;cursor:pointer;margin:1px">'+e.name+'</button>';
+      return '<button onclick="toggleExtra(\''+e.name+'\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid #ccc;background:#fff;color:#333;cursor:pointer;margin:1px">'+e.name+'</button>';
     }).join('')+'</div></div>';
   }).join('');
   renderExtra();
@@ -3464,7 +3673,7 @@ async function doSearch(){
     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=\\'\\'">'+
+    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>'+
@@ -3534,7 +3743,7 @@ 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="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('');
@@ -3598,7 +3807,7 @@ 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\\'">'+
+    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>'+

← b8132ca wallco.ai: width + material picker on /design/:id — 24" resi  ·  back to Wallco Ai  ·  design/:id: Photoshop-style edit panel — sliders, palette do c9365f5 →