โ back to Wallco Ai
cactus-curator: hover any chip to swap flat tile โ room mockup (๐ badge marks the 164/3951 chips with room data; same hover-swap on by-base family cards)
5f263d7f8e284f568176d28a9f461e40c26f1794 ยท 2026-05-28 12:45:30 -0700 ยท Steve
Files touched
M public/admin/cactus-curator.htmlM server.js
Diff
commit 5f263d7f8e284f568176d28a9f461e40c26f1794
Author: Steve <steve@designerwallcoverings.com>
Date: Thu May 28 12:45:30 2026 -0700
cactus-curator: hover any chip to swap flat tile โ room mockup (๐ badge marks the 164/3951 chips with room data; same hover-swap on by-base family cards)
---
public/admin/cactus-curator.html | 30 ++++++++++++++++++++++++++++++
server.js | 4 ++++
2 files changed, 34 insertions(+)
diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index 1fe0848..8cac933 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -29,6 +29,12 @@
.card.sel { outline:2px solid #4aa3ff; outline-offset:-2px; }
.thumb { position:relative; aspect-ratio:1/1; background:#000; }
.thumb img { width:100%; height:100%; object-fit:cover; display:block; -webkit-user-drag:none; user-select:none; }
+ /* On-hover room mockup overlay โ Steve 2026-05-28. Fades in over the flat
+ tile so the curator can see how the pattern reads on a wall in context. */
+ .thumb .card-room { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; opacity:0; transition:opacity .18s ease-out; pointer-events:none; z-index:5; }
+ .card:hover .thumb .card-room { opacity:1; }
+ /* Tiny corner badge so Steve sees which chips have a room mockup at all */
+ .thumb .b.room { background:#0ea5e9; color:#fff; }
.sel-box { position:absolute; top:6px; left:6px; width:20px; height:20px; border-radius:5px; border:2px solid #fff;
background:rgba(0,0,0,.45); cursor:pointer; z-index:3; }
.card.sel .sel-box { background:#4aa3ff; border-color:#4aa3ff; }
@@ -324,6 +330,27 @@ function failReason(d){
const TOP_RANK_N = 10;
const FRESH_MS = 24 * 3600 * 1000;
function isFreshIso(iso){ if(!iso) return false; try{ return (Date.now() - new Date(iso).getTime()) < FRESH_MS; }catch{return false;} }
+function pickRoomKey(rm){
+ if (!rm) return null;
+ // Shape varies: array ["living_room"] OR object {"living_room":"/path"}.
+ const keys = Array.isArray(rm) ? rm : (typeof rm === 'object' ? Object.keys(rm) : []);
+ if (!keys.length) return null;
+ return keys.includes('living_room') ? 'living_room'
+ : keys.includes('bedroom') ? 'bedroom'
+ : keys.includes('office') ? 'office'
+ : keys[0];
+}
+function roomImgHTML(d){
+ const k = pickRoomKey(d.room_mockups);
+ if (!k) return '';
+ return `<img class="card-room" loading="lazy" src="/designs/room/design_${d.id}_${k}.png" alt="" aria-hidden="true">`;
+}
+function roomBadgeHTML(d){
+ const k = pickRoomKey(d.room_mockups);
+ if (!k) return '';
+ return `<span class="b room" title="hover for ${k.replace(/_/g,' ')} mockup">๐</span>`;
+}
+
function card(d, idx){
const el = document.createElement('div');
el.className = 'card';
@@ -360,10 +387,12 @@ function card(d, idx){
${d.tif_path
? `<span class="b tif" title="TIF archive: ${d.tif_w_px||'?'}ร${d.tif_h_px||'?'}px ยท ${d.tif_bytes?(d.tif_bytes/1024/1024).toFixed(2)+' MB':'?'} ยท ${(d.tif_path||'').replace(/^.*\//,'')}">๐ฆ TIF ${d.tif_w_px||''}</span>`
: `<span class="b notif" title="no TIF archive โ generate via /admin/etsy-bucket or scripts/archive-svg-5400.js">๐ฆ NO TIF</span>`}
+ ${roomBadgeHTML(d)}
<span class="b ${verdictClass(d.seam_verdict)}">${d.seam_verdict||'no scan'}</span>
${failReason(d).map(r=>`<span class="b reason" title="failed: ${r}">${r}</span>`).join('')}
</div>
<img loading="lazy" src="/designs/img/by-id/${d.id}" alt="cactus ${d.id}">
+ ${roomImgHTML(d)}
${seamCallout}
<button class="auto-id-btn" title="auto-detect bad areas (you confirm/delete)">๐ Auto-ID</button>
<a class="dbg-btn" href="${q('/admin/seam-debug/'+d.id)}" target="_blank" title="open seam-defect debugger โ see where the mid-line breaks">๐ฌ Debug seams</a>
@@ -887,6 +916,7 @@ load();
</div>
<a class="bb-link" href="/admin/seam-debug/${fam.id}" target="_blank" style="display:block;width:100%;height:100%">
<img loading="lazy" src="/designs/img/by-id/${fam.id}" alt="base ${fam.id}">
+ ${roomImgHTML(fam)}
</a>
<div class="state" style="${stateLabel?'display:block':''}">${stateLabel}</div>
</div>
diff --git a/server.js b/server.js
index f62eb99..62951d3 100644
--- a/server.js
+++ b/server.js
@@ -1747,6 +1747,10 @@ app.get('/api/admin/cactus/list', (req, res) => {
// master ready vs none yet (Steve standing rule: every published
// design must have a 150-DPI TIF archive).
"d.tif_path, d.tif_w_px, d.tif_h_px, d.tif_bytes, " +
+ // room_mockups โ fuels the hover-swap room preview on each chip
+ // (Steve 2026-05-28). Shape varies โ JSONB object {living_room:path}
+ // or JSON array ["living_room"]; the front-end normalizes both.
+ "d.room_mockups, " +
"CASE " +
"WHEN r.vision_score IS NOT NULL AND r.seam_score IS NOT NULL " +
"THEN round(0.6*r.vision_score + 0.4*r.seam_score, 1) " +
โ a2a7d70 /me/saved: add admin view-mode toggle pill (admin/live) โ in
ยท
back to Wallco Ai
ยท
scripts: push-shopify-digital-bundle.js โ Shopify counterpar f55cad1 โ