[object Object]

← back to Watches

Add sort + density slider with localStorage persistence to watch search

08f02d605ac70fc4a02ff60a1aa054208bd62ffb · 2026-05-19 21:37:15 -0700 · Steve Abrams

Sort hydrates BEFORE first /api/advanced-search call. Default sort flipped
from "relevance" to "Newest (Year)". Density slider rewrites a CSS var so
the auto-fill min card width updates without re-rendering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 08f02d605ac70fc4a02ff60a1aa054208bd62ffb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 21:37:15 2026 -0700

    Add sort + density slider with localStorage persistence to watch search
    
    Sort hydrates BEFORE first /api/advanced-search call. Default sort flipped
    from "relevance" to "Newest (Year)". Density slider rewrites a CSS var so
    the auto-fill min card width updates without re-rendering.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 public/search-interface.html | 71 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 57 insertions(+), 14 deletions(-)

diff --git a/public/search-interface.html b/public/search-interface.html
index 3cf8ad6..f734d91 100755
--- a/public/search-interface.html
+++ b/public/search-interface.html
@@ -274,9 +274,10 @@
         }
 
         /* Watch Grid */
+        :root { --watch-card-min: 300px; }
         .watch-grid {
             display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+            grid-template-columns: repeat(auto-fill, minmax(var(--watch-card-min), 1fr));
             gap: 20px;
         }
 
@@ -423,19 +424,26 @@
                 <button class="clear-btn" onclick="clearAll()">Clear All</button>
             </div>
 
-            <!-- Sort Dropdown -->
-            <div class="sort-section">
-                <label class="sort-label">Sort by:</label>
-                <select id="sortSelect" class="sort-select" onchange="performSearch()">
-                    <option value="relevance">Relevance</option>
-                    <option value="price_asc">Price: Low to High</option>
-                    <option value="price_desc">Price: High to Low</option>
-                    <option value="year_asc">Year: Oldest First</option>
-                    <option value="year_desc">Year: Newest First</option>
-                    <option value="appreciation_desc">Highest Appreciation</option>
-                    <option value="name_asc">Name: A to Z</option>
-                    <option value="name_desc">Name: Z to A</option>
-                </select>
+            <!-- Sort + Density Controls -->
+            <div class="sort-section" style="display:flex; gap:24px; align-items:center; flex-wrap:wrap;">
+                <div style="display:flex; gap:8px; align-items:center;">
+                    <label class="sort-label" for="sortSelect">Sort by:</label>
+                    <select id="sortSelect" class="sort-select" onchange="onSortChange()">
+                        <option value="year_desc">Newest (Year)</option>
+                        <option value="year_asc">Oldest (Year)</option>
+                        <option value="relevance">Relevance</option>
+                        <option value="name_asc">Model A→Z</option>
+                        <option value="name_desc">Model Z→A</option>
+                        <option value="price_asc">Price: Low to High</option>
+                        <option value="price_desc">Price: High to Low</option>
+                        <option value="appreciation_desc">Highest Appreciation</option>
+                    </select>
+                </div>
+                <div style="display:flex; gap:8px; align-items:center;">
+                    <label class="sort-label" for="densityRange">Density:</label>
+                    <input type="range" id="densityRange" min="200" max="480" step="20" value="300" oninput="onDensityChange(this.value)" style="width:160px;">
+                    <span id="densityVal" style="font-size:12px; color:#6c757d; min-width:48px;">300px</span>
+                </div>
             </div>
 
             <!-- Collection Filter Buttons -->
@@ -530,8 +538,43 @@
         let currentView = 'grid';
         let chart = null;
 
+        // ---- Sort + density persistence (run BEFORE first performSearch) ----
+        const LS_SORT = 'watches.search.sort';
+        const LS_DENSITY = 'watches.search.density';
+        function hydrateControls() {
+            try {
+                const savedSort = localStorage.getItem(LS_SORT);
+                if (savedSort) {
+                    const sel = document.getElementById('sortSelect');
+                    if ([...sel.options].some(o => o.value === savedSort)) sel.value = savedSort;
+                }
+                const savedDensity = parseInt(localStorage.getItem(LS_DENSITY), 10);
+                if (savedDensity >= 200 && savedDensity <= 480) {
+                    document.getElementById('densityRange').value = String(savedDensity);
+                    applyDensity(savedDensity);
+                } else {
+                    applyDensity(parseInt(document.getElementById('densityRange').value, 10));
+                }
+            } catch (e) { /* localStorage may be unavailable */ }
+        }
+        function applyDensity(px) {
+            document.documentElement.style.setProperty('--watch-card-min', px + 'px');
+            const lbl = document.getElementById('densityVal');
+            if (lbl) lbl.textContent = px + 'px';
+        }
+        function onSortChange() {
+            try { localStorage.setItem(LS_SORT, document.getElementById('sortSelect').value); } catch (e) {}
+            performSearch();
+        }
+        function onDensityChange(v) {
+            const px = parseInt(v, 10);
+            applyDensity(px);
+            try { localStorage.setItem(LS_DENSITY, String(px)); } catch (e) {}
+        }
+
         // Initialize on page load
         document.addEventListener('DOMContentLoaded', () => {
+            hydrateControls();
             performSearch();
             setupAutocomplete();
         });

← 96a9a73 snapshot: 1 file(s) changed, ~1 modified  ·  back to Watches  ·  Broaden .gitignore: snapshot leftovers + SQLite WAL/SHM 253afbe →