[object Object]

← 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

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&nbsp;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&nbsp;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 →