[object Object]

← back to Wallco Ai

curator: add Bake button — render HSB+contrast adjustment to canvas, save as new unpublished design (reuses /api/design/:id/bake, original untouched)

674cae4649e4f238200ea16490358ee422081278 · 2026-05-31 19:54:40 -0700 · Steve Abrams

Files touched

Diff

commit 674cae4649e4f238200ea16490358ee422081278
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 31 19:54:40 2026 -0700

    curator: add Bake button — render HSB+contrast adjustment to canvas, save as new unpublished design (reuses /api/design/:id/bake, original untouched)
---
 public/admin/cactus-curator.html | 48 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index 40ac74d..b683955 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -163,6 +163,9 @@
   .modal-acts .bad{color:#ff9a9a;border-color:#5a2222;} .modal-acts .dig{color:#cbb8ff;border-color:#3d2f63;}
   .modal-acts .fix{color:#ffd98a;border-color:#5a4410;} .modal-acts .live{color:#9ff0a6;border-color:#1f4d2a;}
   .modal-acts .etsy{color:#ffb86b;border-color:#5a3f17;} .modal-acts .unpub{color:#a8b3bc;border-color:#3a3f44;}
+  .modal-bake { display:flex; gap:9px; align-items:center; flex-wrap:wrap; margin-top:11px; padding-top:11px; border-top:1px solid var(--line); }
+  .modal-bake .bake { font:600 12px inherit; padding:8px 13px; border-radius:7px; border:1px solid #1f4a5a; background:#13262d; color:#7fe3ff; cursor:pointer; }
+  .modal-bake .bake:disabled { opacity:.6; cursor:default; }
   .legend { font-size:11px; color:var(--mut); }
   .legend code { background:var(--panel); padding:1px 5px; border-radius:4px; border:1px solid var(--line); color:var(--ink); }
   /* HSB + contrast live adjustment panel (non-destructive CSS preview, Steve 2026-05-31) */
@@ -1019,6 +1022,10 @@ function detailHTML(d){
         <button class="fix"  data-a="fix">⚠ Fix</button>
         <button class="live" data-a="live">✓ Publish</button>
       </div>
+      <div class="modal-bake">
+        <button class="bake" data-bake="${d.id}" title="Render the current 🎨 Adjust settings onto this image and save it as a NEW unpublished design. The original is untouched.">⤓ Bake adjustment → new design</button>
+        <span class="adjnote" id="bakeMsg"></span>
+      </div>
     </div>`;
 }
 async function openDetail(id){
@@ -1034,8 +1041,38 @@ async function openDetail(id){
       decide(id, b.dataset.a, grid.querySelector(`.card[data-id="${id}"]`));
       closeDetail();
     }));
+    const bakeBtn = box.querySelector('[data-bake]');
+    if (bakeBtn) bakeBtn.addEventListener('click', ()=>bakeAdjustment(id, bakeBtn));
   }catch(e){ box.innerHTML='<div class="modal-side">Error loading #'+id+': '+esc(e.message)+' <button class="modal-close" onclick="closeDetail()">×</button></div>'; }
 }
+// Render the current 🎨 Adjust filter onto a full-res canvas (pixel-parity with
+// the preview) and POST to the existing /api/design/:id/bake endpoint, which
+// saves it as a NEW unpublished design — original untouched, settlement-gated.
+async function bakeAdjustment(id, btn){
+  const msg = document.getElementById('bakeMsg');
+  const filter = (window.ccAdjust && window.ccAdjust.filterString());
+  if (!filter){ if(msg){ msg.style.color='var(--mut)'; msg.textContent='Move the 🎨 Adjust sliders off neutral first — nothing to bake.'; } return; }
+  const orig = btn.textContent; btn.disabled = true; btn.textContent = 'Baking…';
+  if (msg){ msg.style.color='var(--mut)'; msg.textContent=''; }
+  try{
+    const img = new Image();
+    await new Promise((ok,err)=>{ img.onload=ok; img.onerror=()=>err(new Error('source image failed to load')); img.src='/designs/img/by-id/'+id; });
+    const w = img.naturalWidth||img.width, h = img.naturalHeight||img.height;
+    if (!w || !h) throw new Error('image has no dimensions');
+    const cv = document.createElement('canvas'); cv.width=w; cv.height=h;
+    const ctx = cv.getContext('2d'); ctx.filter = filter; ctx.drawImage(img,0,0,w,h);
+    const b64 = cv.toDataURL('image/png').split(',')[1];
+    const r = await fetch('/api/design/'+id+'/bake', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ image_b64: b64 }) });
+    const j = await r.json();
+    if (!j.ok) throw new Error(j.error || ('HTTP '+r.status));
+    const v = window.ccAdjust.values();
+    if (msg){ msg.style.color='#7fe3ff'; msg.innerHTML='✓ Baked '+w+'×'+h+' (H'+v.hue+' S'+v.sat+' B'+v.bri+' C'+v.con+') → new unpublished design <b>#'+j.design_id+'</b> · find it under <i>Show ▸ Unpublished</i>'; }
+    btn.textContent = '✓ Baked #'+j.design_id;
+  }catch(e){
+    if (msg){ msg.style.color='#ff9a9a'; msg.textContent='Bake failed: '+e.message; }
+    btn.textContent = orig; btn.disabled = false;
+  }
+}
 function closeDetail(){ $('modal').classList.remove('show'); }
 $('modal').addEventListener('click', e => { if(e.target.id==='modal') closeDetail(); });
 
@@ -1628,6 +1665,17 @@ load();
     var r = el(ids[k]); if(!r) return;
     r.addEventListener('input', function(){ set(k, r.value); var v=el(ids[k]+'V'); if(v) v.textContent=r.value; apply(); });
   }
+  // Expose the current adjustment so the modal "Bake" can render the SAME
+  // filter onto a canvas (pixel-parity with this live preview). Returns null
+  // when neutral (nothing to bake).
+  window.ccAdjust = {
+    values: function(){ return { hue:get('hue'), sat:get('sat'), bri:get('bri'), con:get('con') }; },
+    filterString: function(){
+      var s = this.values();
+      if (s.hue===NEUTRAL.hue && s.sat===NEUTRAL.sat && s.bri===NEUTRAL.bri && s.con===NEUTRAL.con) return null;
+      return filterStr(s);
+    }
+  };
   function init(){
     for (var k in ids) bind(k);
     var reset = el('adjReset');

← 0623808 Publish silk+linen colorway variants on default spec (54978-  ·  back to Wallco Ai  ·  stoned-animals title-drift fix — re-title 303 designs with a 5203dd4 →