← back to Designer Line
public/commerce.html
143 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>commerce · designerline</title>
<style>
:root { --ink:#0e0e0c; --ink-mute:#6e6e68; --paper:#faf8f2; --line:#d9d4c5; --cols:4; }
* { box-sizing: border-box; }
body { margin:0; background:var(--paper); color:var(--ink); font-family: ui-serif, Georgia, serif; }
header {
display:flex; justify-content:space-between; align-items:center;
padding:20px 28px; border-bottom:1px solid var(--line);
position:sticky; top:0; background:var(--paper); z-index:10;
}
header .mark { font-family:ui-sans-serif,system-ui,sans-serif; font-size:13px; letter-spacing:.22em; text-transform:uppercase; font-weight:600; }
header nav a { color:var(--ink-mute); text-decoration:none; font-family:ui-sans-serif,system-ui,sans-serif; font-size:11px; letter-spacing:.18em; text-transform:uppercase; margin-left:24px; }
header nav a:hover { color:var(--ink); }
.controls {
display:flex; gap:18px; align-items:center; padding:18px 28px;
border-bottom:1px solid var(--line); flex-wrap:wrap;
font-family:ui-sans-serif,system-ui,sans-serif; font-size:12px;
}
.controls label { color:var(--ink-mute); letter-spacing:.12em; text-transform:uppercase; font-size:11px; }
.controls select, .controls input[type=search] {
font-family:ui-sans-serif,system-ui,sans-serif; font-size:13px;
border:1px solid var(--line); background:#fff; padding:6px 10px; border-radius:0;
}
.controls input[type=range] { vertical-align: middle; width:140px; }
.controls .right { margin-left:auto; color:var(--ink-mute); font-size:12px; }
.grid {
display:grid; grid-template-columns: repeat(var(--cols), 1fr);
gap:24px; padding:24px 28px;
}
.card { background:#fff; border:1px solid var(--line); position:relative; }
.card .img { aspect-ratio: 1/1; background:#eee5cc; overflow:hidden; }
.card .img img { width:100%; height:100%; object-fit:cover; display:block; }
.card .meta { padding:10px 12px 14px; }
.card .ven { font-family:ui-sans-serif,system-ui,sans-serif; font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--ink-mute); }
.card .title { font-size:14px; margin:4px 0 6px; line-height:1.3; min-height:36px; }
.card .px { font-family:ui-sans-serif,system-ui,sans-serif; font-size:12px; color:var(--ink); }
.empty { padding:80px 28px; text-align:center; color:var(--ink-mute); font-style: italic; }
footer { padding:32px 28px 48px; color:var(--ink-mute); font-size:11px; font-family:ui-sans-serif,system-ui,sans-serif; letter-spacing:.12em; text-transform:uppercase; text-align:center; border-top:1px solid var(--line); margin-top:48px; }
</style>
</head>
<body>
<header>
<a href="/" style="text-decoration:none;color:var(--ink);"><span class="mark">designerline</span></a>
<nav>
<a href="/commerce.html" style="color:var(--ink);">commerce</a>
<a href="/advocacy.html">advocacy</a>
<a href="/community.html">community</a>
</nav>
</header>
<div class="controls">
<label>vendor</label>
<select id="vendor">
<option value="">all vendors</option>
<option value="Scalamandre">Scalamandre</option>
<option value="Schumacher">Schumacher</option>
<option value="Thibaut">Thibaut</option>
<option value="Phillip Jeffries">Phillip Jeffries</option>
<option value="Maya Romanoff">Maya Romanoff</option>
</select>
<label>search</label>
<input id="q" type="search" placeholder="title, sku…" />
<label>sort</label>
<select id="sort">
<option value="newest">newest</option>
<option value="title">title a→z</option>
<option value="title-desc">title z→a</option>
<option value="vendor">vendor a→z</option>
<option value="sku">sku a→z</option>
<option value="sku-desc">sku z→a</option>
<option value="price-asc">price ↑</option>
<option value="price-desc">price ↓</option>
</select>
<label>density</label>
<input id="cols" type="range" min="2" max="8" value="4" />
<span class="right" id="count">loading…</span>
</div>
<div class="grid" id="grid"></div>
<div id="empty" class="empty" style="display:none">no products matched. try a different vendor or clear search.</div>
<footer>commerce · designerline v0.1 · pulled live from dw_unified.shopify_products</footer>
<script>
const $ = (id) => document.getElementById(id);
const root = document.documentElement;
const load = (() => {
// restore controls from localStorage
['vendor','sort','q'].forEach(k => { const v = localStorage.getItem('dl.commerce.'+k); if (v !== null) $(k).value = v; });
const savedCols = localStorage.getItem('dl.commerce.cols') || '4';
$('cols').value = savedCols; root.style.setProperty('--cols', savedCols);
let req = 0;
return async function fetchProducts() {
const myReq = ++req;
const params = new URLSearchParams({
vendor: $('vendor').value, q: $('q').value, sort: $('sort').value, limit: '120',
});
['vendor','sort','q','cols'].forEach(k => localStorage.setItem('dl.commerce.'+k, $(k).value));
$('count').textContent = 'loading…';
let data;
try { data = await (await fetch('/api/commerce/products?'+params)).json(); }
catch (e) { $('count').textContent = 'err: '+e.message; return; }
if (myReq !== req) return;
if (data.error) { $('count').textContent = 'err: '+data.error; return; }
const ps = data.products || [];
$('count').textContent = ps.length + ' product' + (ps.length===1?'':'s');
$('empty').style.display = ps.length ? 'none' : 'block';
$('grid').innerHTML = ps.map(p => `
<div class="card">
<div class="img">${p.image_url ? `<img src="${p.image_url}" loading="lazy" alt="" />` : ''}</div>
<div class="meta">
<div class="ven">${escapeHTML(p.vendor||'')}</div>
<div class="title">${escapeHTML(p.title||'')}</div>
<div class="px">${p.net_price ? '$'+Number(p.net_price).toFixed(2)+' trade' : (p.retail_price ? '$'+Number(p.retail_price).toFixed(2) : '—')}</div>
</div>
</div>
`).join('');
};
})();
function escapeHTML(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
['vendor','sort'].forEach(id => $(id).addEventListener('change', load));
$('q').addEventListener('input', () => { clearTimeout(window._tq); window._tq = setTimeout(load, 280); });
$('cols').addEventListener('input', e => { root.style.setProperty('--cols', e.target.value); localStorage.setItem('dl.commerce.cols', e.target.value); });
load();
</script>
</body>
</html>