[object Object]

← back to Wallco Ai

curator: add HSB + contrast live image-adjustment panel (non-destructive CSS preview)

4d9032c63a254862575fbc4411ba9ae4dbab0e41 · 2026-05-31 19:47:20 -0700 · Steve Abrams

Files touched

Diff

commit 4d9032c63a254862575fbc4411ba9ae4dbab0e41
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 31 19:47:20 2026 -0700

    curator: add HSB + contrast live image-adjustment panel (non-destructive CSS preview)
---
 public/admin/cactus-curator.html | 71 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index a43133a..40ac74d 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -165,6 +165,15 @@
   .modal-acts .etsy{color:#ffb86b;border-color:#5a3f17;} .modal-acts .unpub{color:#a8b3bc;border-color:#3a3f44;}
   .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) */
+  #adjPanel { display:none; flex-wrap:wrap; gap:16px; align-items:center; padding:9px 14px; border-bottom:1px solid var(--line); background:#16191c; }
+  #adjPanel.on { display:flex; }
+  #adjPanel .adj { display:flex; align-items:center; gap:7px; font:600 12px inherit; color:var(--mut); }
+  #adjPanel .adj input[type=range] { width:120px; }
+  #adjPanel .adjval { display:inline-block; min-width:34px; text-align:right; color:var(--ink); font-variant-numeric:tabular-nums; }
+  #adjPanel .adjnote { font-size:11px; color:var(--mut); }
+  #adjToggle.on { background:#1f4d2a; color:#9ff0a6; border-color:#1f4d2a; }
+  #grid img, #modal img { filter: var(--img-adjust, none); }
 </style>
 </head>
 <body>
@@ -200,6 +209,7 @@
     <label class="ctl">Density
       <input type="range" id="density" min="2" max="9" step="1" value="5">
     </label>
+    <button id="adjToggle" class="ctl-btn" title="HSB + contrast live preview on all images — non-destructive, persists in localStorage">🎨 Adjust</button>
     <button id="autoall" class="ctl-btn" title="auto-detect bad areas on every visible card">🔍 Auto-ID all visible</button>
     <button id="selectall" class="ctl-btn" title="Toggle select all visible cards — click again to clear">☑ Select all visible</button>
     <button id="toggleacts" class="ctl-btn" title="show/hide the Bad/Digital/Fix/Publish buttons on every card (they always show on hover)">▸ Actions</button>
@@ -207,6 +217,16 @@
   </div>
 </header>
 
+<div id="adjPanel">
+  <span class="adj" style="color:var(--ink)">🎨 Image adjustments</span>
+  <label class="adj">Hue <input type="range" id="adjHue" min="0" max="255" step="1"><span class="adjval" id="adjHueV"></span></label>
+  <label class="adj">Sat <input type="range" id="adjSat" min="0" max="255" step="1"><span class="adjval" id="adjSatV"></span></label>
+  <label class="adj">Bri <input type="range" id="adjBri" min="0" max="255" step="1"><span class="adjval" id="adjBriV"></span></label>
+  <label class="adj">Con <input type="range" id="adjCon" min="0" max="200" step="1"><span class="adjval" id="adjConV"></span></label>
+  <button id="adjReset" class="ctl-btn" title="back to neutral (Hue 127 · Sat 127 · Bri 127 · Con 100)">Reset</button>
+  <span class="adjnote">live preview only — non-destructive, doesn't touch the stored image</span>
+</div>
+
 <div id="err" style="display:none"></div>
 <div id="empty" style="display:none">No designs match this filter.</div>
 <div id="hint" style="display:none;margin:8px 14px 0;padding:8px 12px;border:1px solid var(--line);border-radius:8px;background:#16191c;color:var(--mut);font-size:13px;">
@@ -1574,6 +1594,57 @@ load();
     else if (e.key === 'Backspace' && history.length) back();
   });
 })();
+</script>
+<script>
+// HSB + contrast live image-adjustment panel (Steve 2026-05-31).
+// Non-destructive: drives a CSS filter on every design image via --img-adjust.
+// Scales chosen so Steve's pasted preset (Hue 127 · Sat 158 · Bri 127 · Con 100)
+// reads as "neutral except a mild +24% saturation":
+//   hue-rotate = (Hue-127)/127*180deg   (127 = no shift)
+//   saturate   = Sat/127                (127 = 1.0)
+//   brightness = Bri/127                (127 = 1.0)
+//   contrast   = Con/100                (100 = 1.0)
+(function(){
+  var NEUTRAL = { hue:127, sat:127, bri:127, con:100 };
+  var DEFAULT = { hue:127, sat:158, bri:127, con:100 };   // Steve's pasted preset
+  var LS = 'cc_adj_';
+  function get(k){ var v=localStorage.getItem(LS+k); return v===null ? DEFAULT[k] : +v; }
+  function set(k,v){ localStorage.setItem(LS+k, v); }
+  var el = function(id){ return document.getElementById(id); };
+  var ids = { hue:'adjHue', sat:'adjSat', bri:'adjBri', con:'adjCon' };
+
+  function filterStr(s){
+    var deg = ((s.hue - 127) / 127 * 180).toFixed(1);
+    return 'hue-rotate('+deg+'deg) saturate('+(s.sat/127).toFixed(3)+') '
+         + 'brightness('+(s.bri/127).toFixed(3)+') contrast('+(s.con/100).toFixed(3)+')';
+  }
+  function apply(){
+    var s = { hue:get('hue'), sat:get('sat'), bri:get('bri'), con:get('con') };
+    var neutral = (s.hue===NEUTRAL.hue && s.sat===NEUTRAL.sat && s.bri===NEUTRAL.bri && s.con===NEUTRAL.con);
+    document.documentElement.style.setProperty('--img-adjust', neutral ? 'none' : filterStr(s));
+    for (var k in ids){ var r=el(ids[k]); if(r){ r.value=s[k]; } var v=el(ids[k]+'V'); if(v){ v.textContent=s[k]; } }
+  }
+  function bind(k){
+    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(); });
+  }
+  function init(){
+    for (var k in ids) bind(k);
+    var reset = el('adjReset');
+    if (reset) reset.addEventListener('click', function(){ for (var k in NEUTRAL) set(k, NEUTRAL[k]); apply(); });
+    var panel = el('adjPanel'), toggle = el('adjToggle');
+    function show(on){
+      if (panel) panel.classList.toggle('on', on);
+      if (toggle) toggle.classList.toggle('on', on);
+      localStorage.setItem(LS+'open', on ? '1' : '0');
+    }
+    if (toggle) toggle.addEventListener('click', function(){ show(!(panel && panel.classList.contains('on'))); });
+    show(localStorage.getItem(LS+'open') === '1');
+    apply();
+  }
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
+  else init();
+})();
 </script>
   <script src="/admin/admin-modal-kit.js" defer></script>
 </body>

← be9b245 Add curator-excluded-ids helper — publish loop respects cact  ·  back to Wallco Ai  ·  Publish silk+linen colorway variants on default spec (54978- 0623808 →