← 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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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>