← back to Wallco Ai
feat(theme-gallery): width + height sliders, dynamic resize on input
4e1f5e0ff4e57fe4eab9db938a421d41de2bb496 · 2026-05-29 13:36:33 -0700 · Steve Abrams
Replaces the discrete Desktop/Tablet/Mobile segmented control with
two continuous range sliders (room width 320-1920 / height 480-2400).
Each input event rAF-throttles a rescale() so dragging stays smooth.
The 3 presets are kept as quick-set buttons (set BOTH dims at once,
highlight when both match). Sliders persist via localStorage keys
tg-vw and tg-vh.
Files touched
M public/admin/theme-gallery.html
Diff
commit 4e1f5e0ff4e57fe4eab9db938a421d41de2bb496
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri May 29 13:36:33 2026 -0700
feat(theme-gallery): width + height sliders, dynamic resize on input
Replaces the discrete Desktop/Tablet/Mobile segmented control with
two continuous range sliders (room width 320-1920 / height 480-2400).
Each input event rAF-throttles a rescale() so dragging stays smooth.
The 3 presets are kept as quick-set buttons (set BOTH dims at once,
highlight when both match). Sliders persist via localStorage keys
tg-vw and tg-vh.
---
public/admin/theme-gallery.html | 67 ++++++++++++++++++++++++++++++++---------
1 file changed, 53 insertions(+), 14 deletions(-)
diff --git a/public/admin/theme-gallery.html b/public/admin/theme-gallery.html
index 26a8c49..87155cf 100644
--- a/public/admin/theme-gallery.html
+++ b/public/admin/theme-gallery.html
@@ -55,11 +55,18 @@
<span class="sub" id="title">—</span>
<span class="chip" id="when" title="" hidden>🕓 <span id="whenTxt"></span></span>
<span style="flex:1"></span>
- <span class="sub">Viewport</span>
- <span class="seg" id="seg">
- <button data-w="1280" class="on">Desktop</button>
- <button data-w="834">Tablet</button>
- <button data-w="390">Mobile</button>
+ <span class="sub">Room W</span>
+ <input type="range" id="wRange" min="320" max="1920" step="10" value="1280"
+ style="width:160px;accent-color:var(--accent)">
+ <code id="wVal" style="font:600 11px ui-monospace,Menlo,monospace;color:var(--mut);min-width:46px">1280</code>
+ <span class="sub">Room H</span>
+ <input type="range" id="hRange" min="480" max="2400" step="10" value="1700"
+ style="width:160px;accent-color:var(--accent)">
+ <code id="hVal" style="font:600 11px ui-monospace,Menlo,monospace;color:var(--mut);min-width:46px">1700</code>
+ <span class="seg" id="seg" title="quick presets">
+ <button data-w="1280" data-h="1700" class="on">Desktop</button>
+ <button data-w="834" data-h="1180">Tablet</button>
+ <button data-w="390" data-h="760">Mobile</button>
</span>
</div>
</header>
@@ -91,10 +98,10 @@ const VARIANTS = [
['roommin', 'V16 · Room-Left Minimal'],
['roomshow', 'V17 · Room-Left Showcase'],
];
-const LOGICAL_H = { 1280:1700, 834:1180, 390:760 }; // logical iframe height per width
const $ = s => document.querySelector(s);
let curId = null;
let logicalW = +(localStorage.getItem('tg-vw') || 1280);
+let logicalH = +(localStorage.getItem('tg-vh') || 1700);
function fmtWhen(iso){
if(!iso) return null;
@@ -140,11 +147,10 @@ function sizeFrame(frame){
const ifr = frame.querySelector('iframe'); if(!ifr) return;
const w = frame.clientWidth || 400;
const s = w / logicalW;
- const h = LOGICAL_H[logicalW] || 1600;
ifr.style.width = logicalW + 'px';
- ifr.style.height = h + 'px';
+ ifr.style.height = logicalH + 'px';
ifr.style.transform = `scale(${s})`;
- frame.style.height = (h * s) + 'px';
+ frame.style.height = (logicalH * s) + 'px';
}
function rescale(){ document.querySelectorAll('.frame').forEach(sizeFrame); }
@@ -186,15 +192,48 @@ async function newest(){
} catch(e){}
}
-// viewport segmented control
+// Sliders — drive logicalW + logicalH live; rescale every input event.
+// rAF-throttle so dragging stays butter-smooth even with many iframes mounted.
+let _rafQueued = false;
+function liveRescale(){
+ if (_rafQueued) return;
+ _rafQueued = true;
+ requestAnimationFrame(() => { _rafQueued = false; rescale(); });
+}
+function syncSliderUI(){
+ $('#wRange').value = logicalW; $('#wVal').textContent = logicalW;
+ $('#hRange').value = logicalH; $('#hVal').textContent = logicalH;
+ // Highlight matching preset (if any)
+ document.querySelectorAll('#seg button').forEach(b => {
+ b.classList.toggle('on', +b.dataset.w === logicalW && +b.dataset.h === logicalH);
+ });
+}
+$('#wRange').addEventListener('input', e => {
+ logicalW = +e.target.value;
+ $('#wVal').textContent = logicalW;
+ localStorage.setItem('tg-vw', logicalW);
+ document.querySelectorAll('#seg button').forEach(b =>
+ b.classList.toggle('on', +b.dataset.w === logicalW && +b.dataset.h === logicalH));
+ liveRescale();
+});
+$('#hRange').addEventListener('input', e => {
+ logicalH = +e.target.value;
+ $('#hVal').textContent = logicalH;
+ localStorage.setItem('tg-vh', logicalH);
+ document.querySelectorAll('#seg button').forEach(b =>
+ b.classList.toggle('on', +b.dataset.w === logicalW && +b.dataset.h === logicalH));
+ liveRescale();
+});
+// Preset buttons — set BOTH width and height in one click
$('#seg').addEventListener('click', e=>{
const b = e.target.closest('button[data-w]'); if(!b) return;
- document.querySelectorAll('#seg button').forEach(x=>x.classList.remove('on'));
- b.classList.add('on');
- logicalW = +b.dataset.w; localStorage.setItem('tg-vw', logicalW);
+ logicalW = +b.dataset.w; logicalH = +b.dataset.h;
+ localStorage.setItem('tg-vw', logicalW);
+ localStorage.setItem('tg-vh', logicalH);
+ syncSliderUI();
rescale();
});
-document.querySelectorAll('#seg button').forEach(b=>b.classList.toggle('on', +b.dataset.w===logicalW));
+syncSliderUI();
$('#loadBtn').addEventListener('click', ()=>go($('#idIn').value));
$('#idIn').addEventListener('keydown', e=>{ if(e.key==='Enter') go($('#idIn').value); });
$('#newestBtn').addEventListener('click', newest);
← 58b694b Save-this-colorway: per-user EXCLUSIVE versioned colorways.
·
back to Wallco Ai
·
Age prompt: add 'We are asking to serve the best theme for y 1ed5276 →