[object Object]

← back to Wallco Ai

ui(designs): move chat pop-out to upper-right; strip ALL sliders + admin clutter

f7a54b8fd0db197a88cb68fdfa8424d8a977c325 · 2026-05-20 15:55:37 -0700 · Steve Abrams

Steve directive 2026-05-20 from screenshot review:

1. Move 'Chat with our designer' pop-out — was bottom-right (bottom:22px;
   right:22px). Now upper-right (top:22px; right:22px). Panel anchored
   under the FAB instead of above it — slides DOWN on open.

2. Remove ALL sliders globally on /designs —
   - density-slider (1-12 range) — top toolbar
   - spacing-slider (0-40 admin range) — top toolbar
   - scrub-hue + scrub-style (color/style filter sliders block) — below toolbar
   - Per-card admin Design/Color/Style sliders (0-10) — review-flyout

3. Strip admin review-flyout per-card markup (sliders, Keep/Reject/Pair
   buttons, why-chip, chips). Admin cards now match the public-card UX:
   hover reveals just View + Sample. The bulk-select + favorites paths
   still run via wireCard.

4. Null-guard 4 JS handlers that referenced now-removed DOM elements:
   density-slider boot, wireCard K/R/Pair/why-chip handlers,
   loadChipsForVisibleCards .chips innerHTML. All return early when the
   element is absent, so script.js works whether the page renders the
   admin review controls or not.

Verified locally — 0 JS errors, 0 input[type=range] elements in rendered
HTML, 60 cards with 60 Sample+View action pairs, FAB at (y=22, right=22).

Files touched

Diff

commit f7a54b8fd0db197a88cb68fdfa8424d8a977c325
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 20 15:55:37 2026 -0700

    ui(designs): move chat pop-out to upper-right; strip ALL sliders + admin clutter
    
    Steve directive 2026-05-20 from screenshot review:
    
    1. Move 'Chat with our designer' pop-out — was bottom-right (bottom:22px;
       right:22px). Now upper-right (top:22px; right:22px). Panel anchored
       under the FAB instead of above it — slides DOWN on open.
    
    2. Remove ALL sliders globally on /designs —
       - density-slider (1-12 range) — top toolbar
       - spacing-slider (0-40 admin range) — top toolbar
       - scrub-hue + scrub-style (color/style filter sliders block) — below toolbar
       - Per-card admin Design/Color/Style sliders (0-10) — review-flyout
    
    3. Strip admin review-flyout per-card markup (sliders, Keep/Reject/Pair
       buttons, why-chip, chips). Admin cards now match the public-card UX:
       hover reveals just View + Sample. The bulk-select + favorites paths
       still run via wireCard.
    
    4. Null-guard 4 JS handlers that referenced now-removed DOM elements:
       density-slider boot, wireCard K/R/Pair/why-chip handlers,
       loadChipsForVisibleCards .chips innerHTML. All return early when the
       element is absent, so script.js works whether the page renders the
       admin review controls or not.
    
    Verified locally — 0 JS errors, 0 input[type=range] elements in rendered
    HTML, 60 cards with 60 Sample+View action pairs, FAB at (y=22, right=22).
---
 public/css/chat.css |  9 ++++--
 server.js           | 87 +++++++++++++++++------------------------------------
 2 files changed, 33 insertions(+), 63 deletions(-)

diff --git a/public/css/chat.css b/public/css/chat.css
index d1abc14..6818152 100644
--- a/public/css/chat.css
+++ b/public/css/chat.css
@@ -2,9 +2,10 @@
    Dark-mode aware. Inherits the page's theme. */
 
 /* ── floating button (FAB) ───────────────────────────────────────── */
+/* Pinned upper-right per Steve directive 2026-05-20 (was bottom-right). */
 #wallco-chat-fab {
   position: fixed;
-  bottom: 22px;
+  top: 22px;
   right: 22px;
   z-index: 9998;
   background: var(--accent, #0d0d0d);
@@ -25,9 +26,10 @@
 #wallco-chat-fab .fab-spark { font-family: var(--serif); font-size: 17px; color: var(--gold, #c9a14b); line-height: 1; }
 
 /* ── panel ───────────────────────────────────────────────────────── */
+/* Anchored under the FAB (now upper-right) — drops down instead of up. */
 #wallco-chat-panel {
   position: fixed;
-  bottom: 22px;
+  top: 78px;
   right: 22px;
   width: 400px; max-width: calc(100vw - 32px);
   height: 600px; max-height: calc(100vh - 44px);
@@ -41,7 +43,8 @@
   flex-direction: column;
   overflow: hidden;
   font-family: var(--sans);
-  transform: translateY(8px); opacity: 0;
+  /* Drops DOWN from the upper-right FAB now (was sliding up from bottom). */
+  transform: translateY(-8px); opacity: 0;
   transition: transform .22s ease, opacity .22s ease;
 }
 #wallco-chat-panel.is-open { display: flex; transform: translateY(0); opacity: 1; }
diff --git a/server.js b/server.js
index fb387b8..e8846e5 100644
--- a/server.js
+++ b/server.js
@@ -3793,27 +3793,11 @@ app.get('/designs', (req, res) => {
         </div>
       </a>
       <span class="pattern-num" aria-hidden="true">${_digAdm}</span>
-      <div class="review-flyout">
-        <div class="card-meta">
-          <span class="card-title">${d.title}</span>
-          <span class="card-cat">${d.category}</span>
-          <span class="color-dot" style="background:${d.dominant_hex}"></span>
-        </div>
-        <div class="review-controls">
-          <div class="sliders">
-            <label>Design <input type="range" min="0" max="10" value="5" data-axis="design"><span>5</span></label>
-            <label>Color  <input type="range" min="0" max="10" value="5" data-axis="color"><span>5</span></label>
-            <label>Style  <input type="range" min="0" max="10" value="5" data-axis="style"><span>5</span></label>
-          </div>
-          <div class="decision">
-            <button class="btn-keep" type="button">Keep</button>
-            <button class="btn-reject" type="button">Reject</button>
-            <button class="btn-pair" type="button" title="Suggest combinations">Pair</button>
-          </div>
-          <div class="why-chip" title="Click to chat about this verdict"></div>
-          <div class="chips"></div>
-        </div>
-      </div>
+      <!-- 2026-05-20 Steve directive — stripped admin review-flyout (sliders +
+           Keep/Reject/Pair buttons + why-chip + chips). Admin cards now match
+           the public-card UX: hover reveals just View + Sample. Title/category
+           meta also dropped from the admin card since the hover overlay already
+           shows the DIG and the title is in the modal on click. -->
     </div>`;
   }).join('');
 
@@ -4088,14 +4072,8 @@ ${(req.query.source === 'all') ? `
             </optgroup>
           </select>
         </div>
-        <label class="density-label" style="display:inline-flex;align-items:center;gap:8px;font:11px var(--sans);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint)">Density
-          <input type="range" id="density-slider" min="1" max="12" step="1" value="6" class="density-range" style="flex:0 0 140px" aria-valuemin="1" aria-valuemax="12" aria-valuenow="6">
-          <span id="density-value" aria-live="polite" style="font:600 11px var(--sans);color:var(--ink-soft);min-width:18px;text-align:right;font-variant-numeric:tabular-nums">6</span>
-        </label>${_isAdmin ? `
-        <label class="spacing-label" style="display:inline-flex;align-items:center;gap:8px;font:11px var(--sans);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint)" title="Admin-only: visual gap between design cards">Spacing
-          <input type="range" id="spacing-slider" min="0" max="40" step="2" value="2" class="density-range" style="flex:0 0 140px" aria-valuemin="0" aria-valuemax="40" aria-valuenow="2">
-          <span id="spacing-value" aria-live="polite" style="font:600 11px var(--sans);color:var(--ink-soft);min-width:22px;text-align:right;font-variant-numeric:tabular-nums">2px</span>
-        </label>` : ''}
+        <!-- 2026-05-20 Steve directive — density + spacing sliders stripped. -->
+        ${''}
         <button type="button" id="surprise-btn" class="filter-select"
           style="background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);border:0;font-weight:500;padding:8px 14px;cursor:pointer;border-radius:6px;letter-spacing:.04em"
           title="Generate one fresh design in a random palette · ~30s">✦ Surprise me</button>
@@ -4104,30 +4082,8 @@ ${(req.query.source === 'all') ? `
       <input type="hidden" name="hue" id="hue-hidden" value="${hueQ}">
     </form>
 
-    <!-- ─── Color + Style sliders (client-side filter on data-hue / data-style-rank) ─── -->
-    <div id="scrub-sliders" style="margin:14px 0 6px;background:var(--card-bg,#faf8f3);border:1px solid var(--line,#ebe5d6);border-radius:10px;padding:14px 18px;display:grid;gap:12px">
-      <div class="scrub-row" style="display:grid;grid-template-columns:90px 1fr 80px;gap:14px;align-items:center">
-        <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.10em;color:var(--ink-faint)">Color</label>
-        <div style="position:relative">
-          <input type="range" id="scrub-hue" min="0" max="360" step="1" value="180"
-            style="width:100%;height:24px;-webkit-appearance:none;appearance:none;background:linear-gradient(90deg,#ff0000 0%,#ffff00 16%,#00ff00 33%,#00ffff 50%,#0000ff 66%,#ff00ff 83%,#ff0000 100%);border-radius:12px;outline:none;cursor:pointer">
-          <span id="scrub-hue-label" style="position:absolute;right:0;top:-18px;font:10px var(--sans);color:var(--ink-faint)">all colors</span>
-        </div>
-        <button type="button" id="scrub-hue-clear" style="font:11px var(--sans);padding:4px 10px;border:1px solid var(--line);background:transparent;color:var(--ink-soft);border-radius:6px;cursor:pointer">Clear</button>
-      </div>
-      <div class="scrub-row" style="display:grid;grid-template-columns:90px 1fr 80px;gap:14px;align-items:center">
-        <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.10em;color:var(--ink-faint)">Style</label>
-        <div style="position:relative">
-          <input type="range" id="scrub-style" min="0" max="100" step="1" value="50"
-            style="width:100%;height:8px;-webkit-appearance:none;appearance:none;background:linear-gradient(90deg,#7a5a3a 0%,#a08070 25%,#b8a890 50%,#9aa8b0 75%,#1a1816 100%);border-radius:6px;outline:none;cursor:pointer">
-          <span style="position:absolute;left:0;top:14px;font:10px var(--sans);color:var(--ink-faint)">Traditional</span>
-          <span style="position:absolute;right:0;top:14px;font:10px var(--sans);color:var(--ink-faint)">Modern</span>
-          <span id="scrub-style-label" style="position:absolute;left:50%;transform:translateX(-50%);top:-18px;font:10px var(--sans);color:var(--ink-faint)">all styles</span>
-        </div>
-        <button type="button" id="scrub-style-clear" style="font:11px var(--sans);padding:4px 10px;border:1px solid var(--line);background:transparent;color:var(--ink-soft);border-radius:6px;cursor:pointer">Clear</button>
-      </div>
-      <div id="scrub-count" style="font:11px var(--sans);color:var(--ink-faint);text-align:right">Showing all designs</div>
-    </div>
+    <!-- 2026-05-20 Steve directive — Color + Style scrub sliders stripped. -->
+    ${''}
 
     <!-- Color-wheel hue swatches -->
     <div class="hue-swatches" style="display:flex;flex-wrap:wrap;gap:6px;margin:18px 0 4px;align-items:center">
@@ -4817,6 +4773,9 @@ ${FOOTER}
   var slider = document.getElementById('density-slider');
   var grid   = document.getElementById('catalog-grid');
   var label  = document.getElementById('density-value');
+  // 2026-05-20 Steve directive — density slider removed. Bail if the element
+  // is missing so the rest of the page JS isn't blocked by the TypeError.
+  if (!slider || !grid) return;
   var saved  = localStorage.getItem('wallco-density');
   if (saved) slider.value = saved;
   // Clamp inline --cols for narrow viewports so a desktop "density=8" pref
@@ -5435,18 +5394,24 @@ ${_isAdmin ? `
 
   function wireCard(card) {
     const id = card.dataset.id;
+    // 2026-05-20 Steve directive — admin review-flyout stripped (sliders +
+    // Keep/Reject/Pair). All wireCard work is now no-ops when those elements
+    // don't exist. Each querySelector below is null-guarded so the bulk-select
+    // + favorites code paths still run on the slimmed-down admin card.
     // sliders
     card.querySelectorAll('.sliders input[type=range]').forEach(slider => {
       slider.addEventListener('input', () => { slider.nextElementSibling.textContent = slider.value; });
       slider.addEventListener('change', () => saveReview(card));
     });
     // K/R
-    card.querySelector('.btn-keep').addEventListener('click', e => {
+    const _keepBtn = card.querySelector('.btn-keep');
+    if (_keepBtn) _keepBtn.addEventListener('click', e => {
       e.preventDefault(); e.stopPropagation();
       card.dataset.decision = card.dataset.decision === 'keep' ? '' : 'keep';
       saveReview(card);
     });
-    card.querySelector('.btn-reject').addEventListener('click', e => {
+    const _rejectBtn = card.querySelector('.btn-reject');
+    if (_rejectBtn) _rejectBtn.addEventListener('click', e => {
       e.preventDefault(); e.stopPropagation();
       card.dataset.decision = card.dataset.decision === 'reject' ? '' : 'reject';
       saveReview(card);
@@ -5458,10 +5423,10 @@ ${_isAdmin ? `
       openPairings(id, card.querySelector('.card-title').textContent || '#'+id);
     });
     // why-chip → chat
-    card.querySelector('.why-chip').addEventListener('click', () => {
-      const why = card.querySelector('.why-chip');
-      if (!why.classList.contains('visible')) return;
-      openChat(id, '_verdict', '"' + why.textContent + '"');
+    const _whyChip = card.querySelector('.why-chip');
+    if (_whyChip) _whyChip.addEventListener('click', () => {
+      if (!_whyChip.classList.contains('visible')) return;
+      openChat(id, '_verdict', '"' + _whyChip.textContent + '"');
     });
   }
 
@@ -5501,7 +5466,9 @@ ${_isAdmin ? `
     cards.forEach(card => {
       const id = card.dataset.id;
       const chipsHTML = (CHIPS[id]||[]).map(c => '<span class="chip" data-chip="' + c.key + '">' + c.label + '</span>').join('');
-      card.querySelector('.chips').innerHTML = chipsHTML;
+      const _chips = card.querySelector('.chips');
+      if (!_chips) return;  // 2026-05-20 admin review-flyout (with .chips) stripped — no-op now
+      _chips.innerHTML = chipsHTML;
       card.querySelectorAll('.chip').forEach(chip => {
         chip.addEventListener('click', () => openChat(id, chip.dataset.chip, chip.textContent));
       });

← bb09c26 deploy-kamatera.sh: probe https://wallco.ai/health (CF) inst  ·  back to Wallco Ai  ·  chat: kill-switch on mobile (≤768px) — bail before DOM injec cd80337 →