← back to Wallco Ai
designs: studio adjust panel (6 photoshop sliders) + per-card favorites
753227db5f7c7eba1b3ee358bb56e05f20af4693 · 2026-05-13 10:56:38 -0700 · Steve
Studio Adjust — global collapsible panel on /designs with 6 sliders:
Hue (hue-rotate -180→180°)
Saturation (saturate 0→200%)
Brightness (brightness 40→160%)
Contrast (contrast 40→200%)
Warmth (sepia 0→100% — warm wash)
Sharpness (positive = contrast+saturate boost, negative = blur)
Slider values apply via CSS filter() to every .card-img on the page.
Persists in localStorage('wallco.designs.adjust'). Reset button restores defaults.
Favorites — heart button (♡/♥) on every public card. Click toggles
localStorage('wallco.favorites') = JSON array of design ids. New 'Favorites'
filter chip in the toolbar — when active, hides cards not in the favorites
set and shows an empty state if none saved yet.
Replaces nothing — the existing admin-rating sliders (Design/Color/Style 0-10
on review-cards) stay intact since they drive the RLHF feedback pipeline.
Public users now get image-adjust sliders + favorite collection instead.
Files touched
Diff
commit 753227db5f7c7eba1b3ee358bb56e05f20af4693
Author: Steve <steve@designerwallcoverings.com>
Date: Wed May 13 10:56:38 2026 -0700
designs: studio adjust panel (6 photoshop sliders) + per-card favorites
Studio Adjust — global collapsible panel on /designs with 6 sliders:
Hue (hue-rotate -180→180°)
Saturation (saturate 0→200%)
Brightness (brightness 40→160%)
Contrast (contrast 40→200%)
Warmth (sepia 0→100% — warm wash)
Sharpness (positive = contrast+saturate boost, negative = blur)
Slider values apply via CSS filter() to every .card-img on the page.
Persists in localStorage('wallco.designs.adjust'). Reset button restores defaults.
Favorites — heart button (♡/♥) on every public card. Click toggles
localStorage('wallco.favorites') = JSON array of design ids. New 'Favorites'
filter chip in the toolbar — when active, hides cards not in the favorites
set and shows an empty state if none saved yet.
Replaces nothing — the existing admin-rating sliders (Design/Color/Style 0-10
on review-cards) stay intact since they drive the RLHF feedback pipeline.
Public users now get image-adjust sliders + favorite collection instead.
---
server.js | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)
diff --git a/server.js b/server.js
index 0ba823f..11435f3 100644
--- a/server.js
+++ b/server.js
@@ -3388,6 +3388,144 @@ ${FOOTER}
});
}
+ // ── Studio Adjust — 6 Photoshop-style sliders applied globally via CSS filter().
+ // Persists in localStorage('wallco.designs.adjust'). Affects every .card-img.
+ (function studioAdjust(){
+ var KEY = 'wallco.designs.adjust';
+ var defaults = { hue:0, sat:100, bri:100, con:100, warm:0, sharp:0 };
+ var state = (function(){ try { return Object.assign({}, defaults, JSON.parse(localStorage.getItem(KEY)||'{}')); } catch(e){ return Object.assign({}, defaults); } })();
+ var rows = document.querySelectorAll('#studio-adjust .sa-row');
+ var summary = document.getElementById('studio-adjust-summary');
+ var resetBtn = document.getElementById('studio-adjust-reset');
+
+ function buildFilter(){
+ // Warmth = sepia approximation (warm wash). Sharpness positive = saturation/contrast micro-boost;
+ // negative = soft blur. Keeps semantics user-friendly without exotic SVG filters.
+ var sharpExtra = '';
+ if (state.sharp < 0) sharpExtra = ' blur(' + (Math.abs(state.sharp) * 0.4) + 'px)';
+ else if (state.sharp > 0) sharpExtra = ' contrast(' + (100 + state.sharp * 4) + '%) saturate(' + (100 + state.sharp * 4) + '%)';
+ return 'hue-rotate(' + state.hue + 'deg) saturate(' + state.sat + '%) brightness(' + state.bri + '%) contrast(' + state.con + '%) sepia(' + state.warm + '%)' + sharpExtra;
+ }
+ function applyToAll(){
+ var f = buildFilter();
+ var imgs = document.querySelectorAll('.design-card .card-img');
+ for (var i=0;i<imgs.length;i++) imgs[i].style.filter = f;
+ var changed = state.hue||0!==0 || state.sat!==100 || state.bri!==100 || state.con!==100 || state.warm!==0 || state.sharp!==0;
+ if (summary) summary.textContent = changed ? 'on' : 'defaults';
+ }
+ function syncRow(row){
+ var axis = row.getAttribute('data-axis');
+ var inp = row.querySelector('input[type=range]');
+ var val = row.querySelector('.sa-val');
+ inp.value = state[axis];
+ var suffix = axis==='hue' ? '°' : (axis==='sharp' ? '' : '%');
+ val.textContent = state[axis] + suffix;
+ }
+ rows.forEach(function(row){
+ var axis = row.getAttribute('data-axis');
+ var inp = row.querySelector('input[type=range]');
+ var val = row.querySelector('.sa-val');
+ syncRow(row);
+ inp.addEventListener('input', function(){
+ state[axis] = parseFloat(this.value);
+ var suffix = axis==='hue' ? '°' : (axis==='sharp' ? '' : '%');
+ val.textContent = state[axis] + suffix;
+ applyToAll();
+ localStorage.setItem(KEY, JSON.stringify(state));
+ });
+ });
+ if (resetBtn) resetBtn.addEventListener('click', function(){
+ state = Object.assign({}, defaults);
+ rows.forEach(syncRow);
+ applyToAll();
+ localStorage.setItem(KEY, JSON.stringify(state));
+ });
+ applyToAll();
+ })();
+
+ // ── Favorites — heart button per card, "Favorites only" filter chip.
+ // Persists in localStorage('wallco.favorites') as a JSON array of design ids.
+ (function favorites(){
+ var FKEY = 'wallco.favorites';
+ function readSet(){
+ try { return new Set(JSON.parse(localStorage.getItem(FKEY) || '[]').map(String)); }
+ catch(e) { return new Set(); }
+ }
+ function writeSet(set){
+ localStorage.setItem(FKEY, JSON.stringify(Array.from(set).map(Number)));
+ }
+ var favs = readSet();
+ function setBtnState(btn, isFav){
+ btn.setAttribute('aria-pressed', isFav ? 'true' : 'false');
+ btn.classList.toggle('is-fav', isFav);
+ }
+ function hydrateButtons(){
+ document.querySelectorAll('.fav-btn').forEach(function(btn){
+ var id = btn.getAttribute('data-fav-id');
+ setBtnState(btn, favs.has(id));
+ });
+ updateFavCount();
+ }
+ function updateFavCount(){
+ var n = favs.size;
+ var c = document.getElementById('fav-only-count');
+ if (c) c.textContent = '·' + n;
+ var extra = document.getElementById('fav-count-extra');
+ if (extra) extra.textContent = n ? '· ' + n + ' favorited' : '';
+ }
+ function applyFavFilter(showOnlyFavs){
+ var cards = document.querySelectorAll('.design-card[data-design-id]');
+ var anyVisible = false;
+ cards.forEach(function(card){
+ var id = card.getAttribute('data-design-id');
+ var match = !showOnlyFavs || favs.has(id);
+ card.style.display = match ? '' : 'none';
+ if (match) anyVisible = true;
+ });
+ var grid = document.getElementById('catalog-grid');
+ var empty = document.getElementById('fav-empty-state');
+ if (showOnlyFavs && !anyVisible) {
+ if (!empty && grid) {
+ empty = document.createElement('div');
+ empty.id = 'fav-empty-state';
+ empty.style.cssText = 'grid-column:1/-1;padding:48px 24px;text-align:center;color:var(--ink-soft,#666);font:14px/1.6 var(--sans,system-ui)';
+ empty.innerHTML = '<div style="font-size:32px;margin-bottom:8px">♡</div><strong>No favorites yet.</strong><br>Tap the heart on any design to save it here.';
+ grid.appendChild(empty);
+ }
+ } else if (empty) {
+ empty.remove();
+ }
+ }
+ document.addEventListener('click', function(e){
+ var btn = e.target.closest && e.target.closest('.fav-btn');
+ if (!btn) return;
+ e.preventDefault();
+ e.stopPropagation();
+ var id = btn.getAttribute('data-fav-id');
+ if (favs.has(id)) favs.delete(id); else favs.add(id);
+ writeSet(favs);
+ setBtnState(btn, favs.has(id));
+ updateFavCount();
+ var toggle = document.getElementById('fav-only-toggle');
+ if (toggle && toggle.getAttribute('aria-pressed') === 'true') {
+ applyFavFilter(true);
+ }
+ });
+ var favToggle = document.getElementById('fav-only-toggle');
+ if (favToggle) {
+ favToggle.addEventListener('click', function(){
+ var on = this.getAttribute('aria-pressed') !== 'true';
+ this.setAttribute('aria-pressed', on ? 'true' : 'false');
+ this.style.background = on ? 'var(--accent,#c9a14b)' : 'transparent';
+ this.style.color = on ? 'var(--bg,#fff)' : 'var(--ink-soft)';
+ this.style.borderColor = on ? 'var(--accent,#c9a14b)' : 'var(--line)';
+ this.innerHTML = (on ? '♥' : '♡') + ' Favorites <span id="fav-only-count" style="opacity:.55">·' + favs.size + '</span>';
+ applyFavFilter(on);
+ });
+ }
+ hydrateButtons();
+ })();
+
// Live search dropdown — hits /api/designs/search, falls back to form submit on Enter / no-JS.
var sIn = document.getElementById('catalog-search-input');
var sBox = document.getElementById('catalog-search-suggest');
← f2b894b mural-scenic: $950 infinite license + 150dpi download (Strip
·
back to Wallco Ai
·
designs: sort buttons (was dropdown) + /merge upload-and-fus d7d74bc →