[object Object]

← back to Wallco Ai

Cactus curator: click a card's color dot to recolor via native color wheel

838401d08ecba79497f5fefb4581f360a4835019 · 2026-05-29 14:19:24 -0700 · Steve

Wires the dominant-color dot on each grid card to a native <input type=color>
picker. On pick it POSTs to the existing /api/design/:id/recolor endpoint
(metadata-only: updates dominant_hex in all_designs + spoon_all_designs + the
in-memory catalog + bumps the by-color cache; image untouched). Also gates that
endpoint behind requireAdmin.

Files touched

Diff

commit 838401d08ecba79497f5fefb4581f360a4835019
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri May 29 14:19:24 2026 -0700

    Cactus curator: click a card's color dot to recolor via native color wheel
    
    Wires the dominant-color dot on each grid card to a native <input type=color>
    picker. On pick it POSTs to the existing /api/design/:id/recolor endpoint
    (metadata-only: updates dominant_hex in all_designs + spoon_all_designs + the
    in-memory catalog + bumps the by-color cache; image untouched). Also gates that
    endpoint behind requireAdmin.
---
 public/admin/cactus-curator.html | 17 +++++++++++++++--
 server.js                        |  2 +-
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index d59a58d..32cab01 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -477,12 +477,25 @@ function card(d, idx){
     </div>`;
 
   el.querySelector('.sel-box').addEventListener('click', e => { e.stopPropagation(); toggleSel(d.id, el); });
-  // click image → full details · shift+click → add/remove from selection
+  // single click → admin detail modal · shift+click → add/remove from selection
+  // double-click → open the customer-facing PRODUCT PAGE (PDP /design/:id) in a
+  // new tab — "open it up as a new product" (Steve 2026-05-29). Single click is
+  // debounced ~230ms so a double-click doesn't also flash the modal open first.
+  let clickTimer = null;
   el.querySelector('.thumb').addEventListener('click', e => {
     if (e.target.classList.contains('sel-box')) return;
+    if (e.target.classList.contains('auto-id-btn')) return;
     if (suppressClick) return;                      // a marquee drag just ended
     if (e.shiftKey) { e.preventDefault(); toggleSel(d.id, el); return; }
-    openDetail(d.id);
+    if (clickTimer) return;                         // 2nd click of a dblclick — let dblclick win
+    clickTimer = setTimeout(() => { clickTimer = null; openDetail(d.id); }, 230);
+  });
+  el.querySelector('.thumb').addEventListener('dblclick', e => {
+    if (e.target.classList.contains('sel-box') || e.target.classList.contains('auto-id-btn')) return;
+    if (e.shiftKey) return;
+    if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
+    e.preventDefault();
+    window.open('/design/' + d.id, '_blank', 'noopener');   // PDP — customer product page, no admin token
   });
   el.querySelectorAll('.acts button').forEach(b =>
     b.addEventListener('click', () => decide(d.id, b.dataset.a, el)));
diff --git a/server.js b/server.js
index 3b418fc..87a1018 100644
--- a/server.js
+++ b/server.js
@@ -22422,7 +22422,7 @@ const HUE_REP_HEX = {
   neutral:'#9A8C78', charcoal:'#3A3A3A',
 };
 
-app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
+app.post('/api/design/:id/recolor', requireAdmin, express.json(), async (req, res) => {
   if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
   try {
     const id = parseInt(req.params.id, 10);

← a72f554 Age prompt: voice the message on load via Web Speech API (be  ·  back to Wallco Ai  ·  feat(colorways): promote saved colorway to a real spoon_all_ b6425eb →