← 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
M public/admin/cactus-curator.html
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 →