← back to Lifestyle Asset Intel
public/js/app.js
109 lines
(function () {
'use strict';
// -- theme toggle ------------------------------------------------------
var themeBtn = document.getElementById('themeToggle');
if (themeBtn) {
themeBtn.addEventListener('click', function () {
var cur = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
var next = cur === 'dark' ? 'light' : 'dark';
if (next === 'dark') document.documentElement.dataset.theme = 'dark';
else delete document.documentElement.dataset.theme;
try { localStorage.setItem('lai-theme', next); } catch (e) {}
});
}
// -- hamburger nav ----------------------------------------------------
var navBtn = document.getElementById('navToggle');
var nav = document.getElementById('topnav');
if (navBtn && nav) {
navBtn.addEventListener('click', function () {
var open = nav.classList.toggle('open');
navBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
});
}
// -- grid: density + sort + search ------------------------------------
var grid = document.getElementById('grid');
if (!grid) return;
var density = document.getElementById('density');
var densityOut = document.getElementById('densityOut');
var sort = document.getElementById('sort');
var search = document.getElementById('search');
var initial = {
density: parseInt(localStorage.getItem('lai-density') || '5', 10),
sort: localStorage.getItem('lai-sort') || 'newest',
search: localStorage.getItem('lai-search') || ''
};
function applyDensity(n) {
document.documentElement.style.setProperty('--cols', String(n));
if (densityOut) densityOut.textContent = String(n);
try { localStorage.setItem('lai-density', String(n)); } catch (e) {}
}
function applySort(mode) {
var cards = Array.prototype.slice.call(grid.querySelectorAll('.card'));
var num = function (el, key) { return parseFloat(el.getAttribute('data-' + key) || '0') || 0; };
var str = function (el, key) { return (el.getAttribute('data-' + key) || '').toLowerCase(); };
var cmp;
switch (mode) {
case 'brand': cmp = function (a, b) { return str(a,'brand').localeCompare(str(b,'brand')); }; break;
case 'family': cmp = function (a, b) { return str(a,'family').localeCompare(str(b,'family')); }; break;
case 'q50_asc': cmp = function (a, b) { return num(a,'q50') - num(b,'q50'); }; break;
case 'q50_desc': cmp = function (a, b) { return num(b,'q50') - num(a,'q50'); }; break;
case 'liquidity_desc': cmp = function (a, b) { return num(b,'liquidity') - num(a,'liquidity'); }; break;
case 'confidence_desc': cmp = function (a, b) { return num(b,'confidence') - num(a,'confidence'); }; break;
case 'recency_desc': cmp = function (a, b) { return str(b,'asof').localeCompare(str(a,'asof')); }; break;
case 'newest':
default: cmp = function (a, b) { return str(b,'asof').localeCompare(str(a,'asof')); };
}
cards.sort(cmp).forEach(function (c) { grid.appendChild(c); });
try { localStorage.setItem('lai-sort', mode); } catch (e) {}
}
function applySearch(q) {
var query = (q || '').toLowerCase().trim();
var cards = grid.querySelectorAll('.card');
cards.forEach(function (c) {
if (!query) { c.style.display = ''; return; }
var hay = ['brand','family','material','color']
.map(function (k) { return (c.getAttribute('data-' + k) || '').toLowerCase(); })
.join(' ');
c.style.display = hay.indexOf(query) >= 0 ? '' : 'none';
});
try { localStorage.setItem('lai-search', query); } catch (e) {}
}
if (density) {
density.value = String(initial.density);
applyDensity(initial.density);
density.addEventListener('input', function () { applyDensity(parseInt(density.value, 10)); });
}
if (sort) {
sort.value = initial.sort;
applySort(initial.sort);
sort.addEventListener('change', function () { applySort(sort.value); });
}
if (search) {
// Server-side scoping: pressing Enter navigates to /?q=… so the
// grid is filtered by the SQL LIKE in lib/valuation.listAssets,
// which keeps working past the client-side DOM size limit.
search.addEventListener('keydown', function (ev) {
if (ev.key === 'Enter') {
ev.preventDefault();
var q = (search.value || '').trim();
var u = new URL(window.location.href);
if (q) u.searchParams.set('q', q);
else u.searchParams.delete('q');
window.location.href = u.toString();
}
});
// If the page was rendered with a server-side q, prefer that over
// localStorage; else fall back to the persisted client-side search.
if (!search.value) search.value = initial.search;
if (search.value) applySearch(search.value);
search.addEventListener('input', function () { applySearch(search.value); });
}
})();