[object Object]

← back to Wallco Ai

refiner-compare: dual-method new-item composer — drag-a-house + multi-select dropdowns (max 3 each) with toggle, per-idea Coordinate + Create-similar, prompt shown per row

6fa0c16061b1d075f0c19afe71e4ebafeb8f65f3 · 2026-05-19 14:02:21 -0700 · SteveStudio2

Files touched

Diff

commit 6fa0c16061b1d075f0c19afe71e4ebafeb8f65f3
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Tue May 19 14:02:21 2026 -0700

    refiner-compare: dual-method new-item composer — drag-a-house + multi-select dropdowns (max 3 each) with toggle, per-idea Coordinate + Create-similar, prompt shown per row
---
 public/textures/grasscloth/espresso.png            | Bin 0 -> 5889605 bytes
 public/textures/grasscloth/index.json              |  36 ++++++
 public/textures/grasscloth/natural-flax.png        | Bin 0 -> 5911126 bytes
 public/textures/grasscloth/oatmeal.png             | Bin 0 -> 5903723 bytes
 public/textures/grasscloth/pearl.png               | Bin 0 -> 5894969 bytes
 public/textures/grasscloth/sisal-tan.png           | Bin 0 -> 5899486 bytes
 public/textures/grasscloth/smoke-grey.png          | Bin 0 -> 5908025 bytes
 .../library/grasscloth/1838-grasscloth-luxury.jpg  | Bin 0 -> 515655 bytes
 ...igners-guild-brera-grasscloth-graphite-wall.jpg | Bin 0 -> 128222 bytes
 ...igners-guild-paysage-marin-grasscloth-sky-w.jpg | Bin 0 -> 45167 bytes
 ...igners-guild-scene-aquarelle-grasscloth-elm.jpg | Bin 0 -> 39166 bytes
 public/textures/library/index.json                 |  76 +++++++++++++
 public/textures/library/sisal/w3996-135.jpg        | Bin 0 -> 3186073 bytes
 scripts/build_texture_library.js                   | 125 +++++++++++++++++++++
 scripts/gen_grasscloth_textures.js                 |  78 +++++++++++++
 15 files changed, 315 insertions(+)

diff --git a/public/textures/grasscloth/espresso.png b/public/textures/grasscloth/espresso.png
new file mode 100644
index 0000000..0177f9b
Binary files /dev/null and b/public/textures/grasscloth/espresso.png differ
diff --git a/public/textures/grasscloth/index.json b/public/textures/grasscloth/index.json
new file mode 100644
index 0000000..57e9c6f
--- /dev/null
+++ b/public/textures/grasscloth/index.json
@@ -0,0 +1,36 @@
+{
+  "material": "grasscloth",
+  "generated": "2026-05-19T20:42:10.877Z",
+  "textures": [
+    {
+      "slug": "natural-flax",
+      "name": "Natural Flax",
+      "file": "natural-flax.png"
+    },
+    {
+      "slug": "sisal-tan",
+      "name": "Sisal Tan",
+      "file": "sisal-tan.png"
+    },
+    {
+      "slug": "oatmeal",
+      "name": "Oatmeal",
+      "file": "oatmeal.png"
+    },
+    {
+      "slug": "smoke-grey",
+      "name": "Smoke Grey",
+      "file": "smoke-grey.png"
+    },
+    {
+      "slug": "espresso",
+      "name": "Espresso",
+      "file": "espresso.png"
+    },
+    {
+      "slug": "pearl",
+      "name": "Pearl White",
+      "file": "pearl.png"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/public/textures/grasscloth/natural-flax.png b/public/textures/grasscloth/natural-flax.png
new file mode 100644
index 0000000..ee2b499
Binary files /dev/null and b/public/textures/grasscloth/natural-flax.png differ
diff --git a/public/textures/grasscloth/oatmeal.png b/public/textures/grasscloth/oatmeal.png
new file mode 100644
index 0000000..b547c88
Binary files /dev/null and b/public/textures/grasscloth/oatmeal.png differ
diff --git a/public/textures/grasscloth/pearl.png b/public/textures/grasscloth/pearl.png
new file mode 100644
index 0000000..47847e4
Binary files /dev/null and b/public/textures/grasscloth/pearl.png differ
diff --git a/public/textures/grasscloth/sisal-tan.png b/public/textures/grasscloth/sisal-tan.png
new file mode 100644
index 0000000..46e7d71
Binary files /dev/null and b/public/textures/grasscloth/sisal-tan.png differ
diff --git a/public/textures/grasscloth/smoke-grey.png b/public/textures/grasscloth/smoke-grey.png
new file mode 100644
index 0000000..87c381d
Binary files /dev/null and b/public/textures/grasscloth/smoke-grey.png differ
diff --git a/public/textures/library/grasscloth/1838-grasscloth-luxury.jpg b/public/textures/library/grasscloth/1838-grasscloth-luxury.jpg
new file mode 100644
index 0000000..b48c742
Binary files /dev/null and b/public/textures/library/grasscloth/1838-grasscloth-luxury.jpg differ
diff --git a/public/textures/library/grasscloth/designers-guild-brera-grasscloth-graphite-wall.jpg b/public/textures/library/grasscloth/designers-guild-brera-grasscloth-graphite-wall.jpg
new file mode 100644
index 0000000..076b9a1
Binary files /dev/null and b/public/textures/library/grasscloth/designers-guild-brera-grasscloth-graphite-wall.jpg differ
diff --git a/public/textures/library/grasscloth/designers-guild-paysage-marin-grasscloth-sky-w.jpg b/public/textures/library/grasscloth/designers-guild-paysage-marin-grasscloth-sky-w.jpg
new file mode 100644
index 0000000..67b46e1
Binary files /dev/null and b/public/textures/library/grasscloth/designers-guild-paysage-marin-grasscloth-sky-w.jpg differ
diff --git a/public/textures/library/grasscloth/designers-guild-scene-aquarelle-grasscloth-elm.jpg b/public/textures/library/grasscloth/designers-guild-scene-aquarelle-grasscloth-elm.jpg
new file mode 100644
index 0000000..aa5189e
Binary files /dev/null and b/public/textures/library/grasscloth/designers-guild-scene-aquarelle-grasscloth-elm.jpg differ
diff --git a/public/textures/library/index.json b/public/textures/library/index.json
new file mode 100644
index 0000000..01c267b
--- /dev/null
+++ b/public/textures/library/index.json
@@ -0,0 +1,76 @@
+{
+ "source": "dw_unified plain-texture products",
+ "generated": "2026-05-19T20:45:00.379Z",
+ "groups": {
+  "grasscloth": {
+   "name": "Grasscloth",
+   "textures": [
+    {
+     "sku": "1838-grasscloth-luxury",
+     "name": "Black Wallpaper - Grasscloth Luxury",
+     "file": "grasscloth/1838-grasscloth-luxury.jpg"
+    },
+    {
+     "sku": "designers-guild-brera-grasscloth-graphite-wall",
+     "name": "Brera Grasscloth Graphite\n                    \n                                            \n                            Wallpaper",
+     "file": "grasscloth/designers-guild-brera-grasscloth-graphite-wall.jpg"
+    },
+    {
+     "sku": "designers-guild-paysage-marin-grasscloth-sky-w",
+     "name": "Paysage Marin Grasscloth Sky\n                    \n                                            \n                            Wallpaper",
+     "file": "grasscloth/designers-guild-paysage-marin-grasscloth-sky-w.jpg"
+    },
+    {
+     "sku": "designers-guild-scene-aquarelle-grasscloth-elm",
+     "name": "Scene Aquarelle Grasscloth Elm\n                    \n                                            \n                            Wallpaper",
+     "file": "grasscloth/designers-guild-scene-aquarelle-grasscloth-elm.jpg"
+    }
+   ]
+  },
+  "sisal": {
+   "name": "Sisal",
+   "textures": [
+    {
+     "sku": "W3996-135",
+     "name": "Kravet Design",
+     "file": "sisal/w3996-135.jpg"
+    }
+   ]
+  },
+  "generated": {
+   "name": "Generated Grasscloth Weave",
+   "textures": [
+    {
+     "sku": "gen-espresso",
+     "name": "Espresso",
+     "file": "../grasscloth/espresso.png"
+    },
+    {
+     "sku": "gen-natural-flax",
+     "name": "Natural Flax",
+     "file": "../grasscloth/natural-flax.png"
+    },
+    {
+     "sku": "gen-oatmeal",
+     "name": "Oatmeal",
+     "file": "../grasscloth/oatmeal.png"
+    },
+    {
+     "sku": "gen-pearl",
+     "name": "Pearl White",
+     "file": "../grasscloth/pearl.png"
+    },
+    {
+     "sku": "gen-sisal-tan",
+     "name": "Sisal Tan",
+     "file": "../grasscloth/sisal-tan.png"
+    },
+    {
+     "sku": "gen-smoke-grey",
+     "name": "Smoke Grey",
+     "file": "../grasscloth/smoke-grey.png"
+    }
+   ]
+  }
+ }
+}
\ No newline at end of file
diff --git a/public/textures/library/sisal/w3996-135.jpg b/public/textures/library/sisal/w3996-135.jpg
new file mode 100644
index 0000000..37f00f2
Binary files /dev/null and b/public/textures/library/sisal/w3996-135.jpg differ
diff --git a/scripts/build_texture_library.js b/scripts/build_texture_library.js
new file mode 100644
index 0000000..4a63083
--- /dev/null
+++ b/scripts/build_texture_library.js
@@ -0,0 +1,125 @@
+#!/usr/bin/env node
+'use strict';
+/**
+ * build_texture_library — wallco.ai's texture-background library, sourced from
+ * REAL plain natural-fibre wallcovering products in the dw_unified database.
+ *
+ * Steve's rule: plain flat backgrounds look low-end. Compositing a generated
+ * pattern over a genuine grasscloth / sisal / cork / raffia product photo gives
+ * the render an honest, premium textile substrate.
+ *
+ * The intended filter was "36 inch wide" — the standard width for natural-fibre
+ * goods — but dw_unified.products.width_in is unpopulated for ~99% of rows, so
+ * width can't be filtered on. Instead we select by plain-texture MATERIAL and
+ * exclude anything whose name reads as a print/pattern. ~416 products qualify.
+ *
+ * Output: public/textures/library/<bucket>/<slug>.jpg
+ *         public/textures/library/index.json   (manifest, grouped by material)
+ *
+ *   node scripts/build_texture_library.js
+ */
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const OUT = path.join(__dirname, '..', 'public', 'textures', 'library');
+fs.mkdirSync(OUT, { recursive: true });
+
+const TEX_RE   = '(grasscloth|grass cloth|sisal|raffia|abaca|jute|hemp|cork|natural weave|paper weave|arrowroot|sea ?grass)';
+const PRINT_RE = '(floral|damask|stripe|scenic|toile|chinoiserie|print|motif|trellis|paisley|ikat|medallion|leaf|leaves|bird|botanical|animal|geometric)';
+
+// material bucket — first match wins
+function bucket(blob) {
+  if (/grass ?cloth/.test(blob))      return { slug: 'grasscloth', name: 'Grasscloth' };
+  if (/sisal/.test(blob))             return { slug: 'sisal',      name: 'Sisal' };
+  if (/raffia/.test(blob))            return { slug: 'raffia',     name: 'Raffia' };
+  if (/abaca/.test(blob))             return { slug: 'abaca',      name: 'Abaca' };
+  if (/sea ?grass|arrowroot/.test(blob)) return { slug: 'seagrass', name: 'Seagrass & Arrowroot' };
+  if (/\bcork\b/.test(blob))          return { slug: 'cork',       name: 'Cork' };
+  if (/jute|hemp/.test(blob))         return { slug: 'jute',       name: 'Jute & Hemp' };
+  if (/paper weave|natural weave/.test(blob)) return { slug: 'weave', name: 'Paper & Natural Weave' };
+  return { slug: 'natural', name: 'Other Natural Fibre' };
+}
+
+function slugify(s) {
+  return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60) || 'tex';
+}
+
+// ---- pull rows from dw_unified -------------------------------------------
+console.log('querying dw_unified for plain-texture products…');
+const sql = `
+  select coalesce(json_agg(row_to_json(t)), '[]') from (
+    select sku, name, material, pattern_name,
+      (case when array_length(image_urls,1)>0 then image_urls[1] else thumbnail_url end) as img
+    from products
+    where lower(coalesce(material,'')||' '||coalesce(name,'')||' '||coalesce(pattern_name,'')) ~ '${TEX_RE}'
+      and lower(coalesce(material,'')||' '||coalesce(name,'')||' '||coalesce(pattern_name,'')) !~ '${PRINT_RE}'
+      and (array_length(image_urls,1)>0 or thumbnail_url is not null)
+  ) t`;
+const raw = execSync(`psql -d dw_unified -tA -c ${JSON.stringify(sql.replace(/\s+/g, ' '))}`, { encoding: 'utf8', maxBuffer: 64 << 20 });
+let rows = JSON.parse(raw.trim());
+rows = rows.filter(r => r.img && /^https?:\/\//.test(r.img));
+console.log(`  ${rows.length} products with a usable image URL`);
+
+// dedupe by image URL — many SKUs share one flatshot
+const seen = new Map();
+for (const r of rows) if (!seen.has(r.img)) seen.set(r.img, r);
+const uniq = [...seen.values()];
+console.log(`  ${uniq.length} unique images after dedupe`);
+
+// ---- download with concurrency -------------------------------------------
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
+const manifest = {};
+let ok = 0, fail = 0, done = 0;
+
+async function fetchOne(r) {
+  const blob = `${r.material || ''} ${r.name || ''} ${r.pattern_name || ''}`.toLowerCase();
+  const b = bucket(blob);
+  const dir = path.join(OUT, b.slug);
+  fs.mkdirSync(dir, { recursive: true });
+  const ext = (r.img.match(/\.(jpe?g|png|webp)(\?|$)/i) || [, 'jpg'])[1].toLowerCase().replace('jpeg', 'jpg');
+  const file = `${slugify(r.sku)}.${ext}`;
+  const dest = path.join(dir, file);
+  try {
+    if (!fs.existsSync(dest) || fs.statSync(dest).size < 1000) {
+      const ctl = new AbortController();
+      const timer = setTimeout(() => ctl.abort(), 20000);
+      const res = await fetch(r.img, { headers: { 'User-Agent': UA }, signal: ctl.signal });
+      clearTimeout(timer);
+      if (!res.ok) throw new Error('HTTP ' + res.status);
+      const buf = Buffer.from(await res.arrayBuffer());
+      if (buf.length < 1000) throw new Error('tiny (' + buf.length + 'B)');
+      fs.writeFileSync(dest, buf);
+    }
+    (manifest[b.slug] ||= { name: b.name, textures: [] }).textures.push({
+      sku: r.sku, name: (r.name || r.sku).trim(), file: `${b.slug}/${file}`,
+    });
+    ok++;
+  } catch (e) {
+    fail++;
+  }
+  if (++done % 40 === 0) console.log(`  …${done}/${uniq.length}  (ok ${ok} / fail ${fail})`);
+}
+
+(async () => {
+  const CONC = 10;
+  for (let i = 0; i < uniq.length; i += CONC) {
+    await Promise.all(uniq.slice(i, i + CONC).map(fetchOne));
+  }
+  // fold in the procedurally-generated grasscloth weaves as their own group
+  const genDir = path.join(__dirname, '..', 'public', 'textures', 'grasscloth');
+  if (fs.existsSync(path.join(genDir, 'index.json'))) {
+    const gen = JSON.parse(fs.readFileSync(path.join(genDir, 'index.json'), 'utf8'));
+    manifest['generated'] = {
+      name: 'Generated Grasscloth Weave',
+      textures: gen.textures.map(t => ({ sku: 'gen-' + t.slug, name: t.name, file: `../grasscloth/${t.file}` })),
+    };
+  }
+  for (const k of Object.keys(manifest)) manifest[k].textures.sort((a, b) => a.name.localeCompare(b.name));
+  fs.writeFileSync(path.join(OUT, 'index.json'),
+    JSON.stringify({ source: 'dw_unified plain-texture products', generated: new Date().toISOString(), groups: manifest }, null, 1));
+  const total = Object.values(manifest).reduce((n, g) => n + g.textures.length, 0);
+  console.log(`\ndone — ${total} textures across ${Object.keys(manifest).length} material groups`);
+  for (const [k, g] of Object.entries(manifest)) console.log(`  ${g.name}: ${g.textures.length}`);
+  console.log(`(${fail} downloads failed and were skipped)`);
+})();
diff --git a/scripts/gen_grasscloth_textures.js b/scripts/gen_grasscloth_textures.js
new file mode 100644
index 0000000..309618a
--- /dev/null
+++ b/scripts/gen_grasscloth_textures.js
@@ -0,0 +1,78 @@
+#!/usr/bin/env node
+'use strict';
+/**
+ * gen_grasscloth_textures — wallco.ai's own seamless grasscloth texture library.
+ *
+ * Generates 6 seamless, tileable bare-grasscloth weave substrates procedurally
+ * via ImageMagick. No external assets, no licensing — these are ours.
+ *
+ * Grasscloth = woven natural fibre: dominant horizontal weft strands laid
+ * course-by-course, faint vertical warp threads, irregular slubs, neutral
+ * palette. We build a grayscale weave once, then duotone-map it into 6
+ * natural colorways.
+ *
+ * Output: public/textures/grasscloth/<slug>.png  (1024x1024, seamless)
+ *         public/textures/grasscloth/index.json   (manifest)
+ *
+ *   node scripts/gen_grasscloth_textures.js
+ */
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const OUT = path.join(__dirname, '..', 'public', 'textures', 'grasscloth');
+fs.mkdirSync(OUT, { recursive: true });
+const SIZE = 1024;
+
+// 6 natural grasscloth colorways — duotone endpoints [shadow, highlight].
+const COLORWAYS = [
+  { slug: 'natural-flax',  name: 'Natural Flax',  dark: '#473a29', light: '#e7d9bd' },
+  { slug: 'sisal-tan',     name: 'Sisal Tan',     dark: '#54402d', light: '#d8c099' },
+  { slug: 'oatmeal',       name: 'Oatmeal',       dark: '#655c4d', light: '#ede5d4' },
+  { slug: 'smoke-grey',    name: 'Smoke Grey',    dark: '#393733', light: '#ccc7be' },
+  { slug: 'espresso',      name: 'Espresso',      dark: '#221913', light: '#9c7f58' },
+  { slug: 'pearl',         name: 'Pearl White',   dark: '#787161', light: '#f3eee1' },
+];
+
+const GRAY = path.join(OUT, '_weave-gray.png');
+
+function sh(cmd) { execSync(cmd, { stdio: ['ignore', 'ignore', 'inherit'] }); }
+
+// 1. Grayscale weave base.
+//    Grasscloth = long horizontal weft strands. We need HIGH vertical
+//    frequency (many thin stacked strands) + LOW horizontal frequency
+//    (each strand varies slowly along its length = slubs).
+//    - noise generated at low vertical res (256) then stretched -> ~4px strands
+//    - heavy horizontal motion-blur -> long fibrous streaks / slubs
+//    - hard sigmoidal contrast -> the weave actually reads, not a flat field
+//    - 4-way mirror fold -> every edge matches its opposite = seamless tile
+console.log('building grayscale weave base…');
+sh(`magick -size ${SIZE}x256 xc: +noise Random -colorspace Gray `
+ + `-resize ${SIZE}x${SIZE}\\! `
+ + `-virtual-pixel tile -motion-blur 0x44+0 -motion-blur 0x12+0 `
+ + `-auto-level -sigmoidal-contrast 9x46% `
+ + `\\( -size ${SIZE}x40 xc: +noise Random -colorspace Gray -resize ${SIZE}x${SIZE}\\! `
+ + `-virtual-pixel tile -motion-blur 0x70+90 -auto-level \\) `
+ + `-compose softlight -composite `
+ + `-blur 0x0.4 -auto-level `
+ + `\\( +clone -flip \\) -append \\( +clone -flop \\) +append `
+ + `-resize ${SIZE}x${SIZE}\\! `
+ + `"${GRAY}"`);
+
+// 2. Duotone-map the weave into each colorway + a touch of fibre grain.
+const manifest = [];
+for (const c of COLORWAYS) {
+  const out = path.join(OUT, `${c.slug}.png`);
+  sh(`magick "${GRAY}" `
+   + `+level-colors "${c.dark}","${c.light}" `
+   + `-attenuate 0.5 +noise Gaussian `
+   + `-quality 95 "${out}"`);
+  const kb = (fs.statSync(out).size / 1024).toFixed(0);
+  console.log(`  ${c.slug}.png  ${kb} KB`);
+  manifest.push({ slug: c.slug, name: c.name, file: `${c.slug}.png` });
+}
+
+fs.unlinkSync(GRAY);
+fs.writeFileSync(path.join(OUT, 'index.json'),
+  JSON.stringify({ material: 'grasscloth', generated: new Date().toISOString(), textures: manifest }, null, 2));
+console.log(`\ndone — ${manifest.length} grasscloth textures + index.json in public/textures/grasscloth/`);

← 4a89b0e Inspiration pool: Replicate backend + settlement gate + plac  ·  back to Wallco Ai  ·  wallco /designs: hide failed-generation blank designs from a bed0442 →