[object Object]

← back to All Designerwallcoverings

snapshot before full-catalog rebuild (vendor directory preserved at /vendors.html)

455cb726dfbb52858dc43fe05b89c39dfb51fbcc · 2026-07-02 16:03:27 -0700 · Steve Abrams

Files touched

Diff

commit 455cb726dfbb52858dc43fe05b89c39dfb51fbcc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 2 16:03:27 2026 -0700

    snapshot before full-catalog rebuild (vendor directory preserved at /vendors.html)
---
 public/vendors.html | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)

diff --git a/public/vendors.html b/public/vendors.html
new file mode 100644
index 0000000..c625495
--- /dev/null
+++ b/public/vendors.html
@@ -0,0 +1,178 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>All Vendors — Designer Wallcoverings</title>
+<meta name="description" content="The complete vendor index at Designer Wallcoverings — every wallcovering and fabric house we carry, in one place.">
+<style>
+  :root { --cols: 4; --ink:#161412; --paper:#f7f5f1; --gold:#a08a5f; --line:#dedad2; }
+  * { box-sizing: border-box; margin: 0; }
+  body { background: var(--paper); color: var(--ink); font-family: "Optima", "Segoe UI", system-ui, sans-serif; }
+  a { color: inherit; text-decoration: none; }
+
+  header { display:flex; justify-content:space-between; align-items:center;
+    padding: 22px 34px; position: sticky; top:0; background: rgba(247,245,241,.92);
+    backdrop-filter: blur(8px); z-index: 20; border-bottom: 1px solid var(--line); }
+  .wordmark { letter-spacing: .22em; font-size: 15px; text-transform: uppercase; font-weight: 600; }
+  .wordmark small { display:block; letter-spacing:.34em; font-size:9px; color:var(--gold); margin-top:3px; }
+  .burger { cursor:pointer; background:none; border:none; padding:8px; }
+  .burger span { display:block; width:24px; height:1.5px; background:var(--ink); margin:5px 0; }
+  nav.menu { display:none; position:fixed; top:0; right:0; bottom:0; width:290px; background:#141210;
+    color:#eee9df; z-index:40; padding:80px 40px; flex-direction:column; gap:26px;
+    font-size:13px; letter-spacing:.18em; text-transform:uppercase; }
+  nav.menu.open { display:flex; }
+  nav.menu .close { position:absolute; top:24px; right:28px; font-size:26px; cursor:pointer; background:none; border:none; color:#eee9df; }
+  nav.menu a:hover { color: var(--gold); }
+
+  .hero { padding: 74px 34px 58px; text-align:center; background:#141210; color:#f2eee6; }
+  .hero h1 { font-family: Georgia, serif; font-weight: 400; font-size: clamp(30px, 5vw, 52px); letter-spacing:.04em; }
+  .hero p { margin-top:14px; color:#b6ad9c; font-size:15px; letter-spacing:.08em; }
+  .hero .stats { margin-top: 26px; display:flex; gap:44px; justify-content:center; font-size:12px; letter-spacing:.24em; text-transform:uppercase; color:var(--gold); }
+  .hero .stats b { display:block; font-size:26px; color:#f2eee6; font-family: Georgia, serif; font-weight:400; letter-spacing:.02em; }
+
+  .controls { display:flex; flex-wrap:wrap; gap:14px 22px; align-items:center;
+    padding: 20px 34px; border-bottom:1px solid var(--line); background:var(--paper);
+    position: sticky; top: 69px; z-index: 15; }
+  .controls input[type=search] { flex:1 1 220px; max-width: 340px; padding:10px 14px; border:1px solid var(--line);
+    background:#fff; font-size:14px; border-radius:2px; }
+  .controls select { padding:10px 12px; border:1px solid var(--line); background:#fff; font-size:13px; border-radius:2px; }
+  .seg { display:flex; border:1px solid var(--line); border-radius:2px; overflow:hidden; }
+  .seg button { padding:9px 16px; background:#fff; border:none; font-size:12px; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; color:#7d766a; }
+  .seg button.on { background:var(--ink); color:#f2eee6; }
+  .dens { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:#7d766a; }
+  .dens input { accent-color: var(--gold); width: 130px; }
+
+  .grid { display:grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 26px; padding: 34px; }
+  .card { background:#fff; border:1px solid var(--line); overflow:hidden; display:flex; flex-direction:column;
+    transition: box-shadow .25s, transform .25s; }
+  .card:hover { box-shadow: 0 14px 34px rgba(22,20,18,.14); transform: translateY(-3px); }
+  .collage { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; aspect-ratio: 4/3; gap:1px; background:var(--line); }
+  .collage.single { grid-template-columns:1fr; grid-template-rows:1fr; }
+  .collage img { width:100%; height:100%; object-fit:cover; display:block; }
+  .collage .ph { display:flex; align-items:center; justify-content:center; background:#efece6; color:#c6bfb1;
+    font-family:Georgia,serif; font-size:34px; }
+  .card .meta { padding: 16px 18px 18px; }
+  .card h3 { font-family: Georgia, serif; font-weight:400; font-size:17px; letter-spacing:.03em; }
+  .card .sub { margin-top:6px; font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:#9a927f; }
+  .card .cta { margin-top:12px; font-size:11px; letter-spacing:.22em; text-transform:uppercase; color:var(--gold); }
+
+  .sentinel { height: 10px; }
+  footer { padding: 44px 34px 60px; text-align:center; font-size:11px; letter-spacing:.2em; text-transform:uppercase; color:#9a927f; border-top:1px solid var(--line); }
+  @media (max-width: 900px){ .grid{ --cols: 2 !important; } .controls{ top:63px } }
+</style>
+</head>
+<body>
+
+<header>
+  <a class="wordmark" href="https://www.designerwallcoverings.com">Designer Wallcoverings<small>The Complete Vendor Index</small></a>
+  <button class="burger" aria-label="Menu" onclick="menu.classList.add('open')"><span></span><span></span><span></span></button>
+</header>
+
+<nav class="menu" id="menu">
+  <button class="close" aria-label="Close" onclick="menu.classList.remove('open')">&times;</button>
+  <a href="https://www.designerwallcoverings.com">Shop the Store</a>
+  <a href="https://www.designerwallcoverings.com/collections/trending-wallcoverings-collection-2026">New &amp; Trending</a>
+  <a href="https://www.designerwallcoverings.com/pages/contact-us">Contact a Designer</a>
+</nav>
+
+<section class="hero">
+  <h1>Every Line We Carry</h1>
+  <p>The world's wallcovering &amp; textile houses — indexed in one place.</p>
+  <div class="stats">
+    <span><b id="statVendors">—</b>Vendors</span>
+    <span><b id="statDesigns">—</b>Designs Live</span>
+  </div>
+</section>
+
+<div class="controls">
+  <input type="search" id="q" placeholder="Search vendors…" aria-label="Search vendors">
+  <div class="seg" role="tablist">
+    <button id="fLive" class="on">Available Now</button>
+    <button id="fAll">All Vendors</button>
+  </div>
+  <select id="sort" aria-label="Sort">
+    <option value="designs">Most Designs</option>
+    <option value="az">Name A→Z</option>
+    <option value="za">Name Z→A</option>
+  </select>
+  <label class="dens">Density <input type="range" id="density" min="2" max="8" step="1" value="4"></label>
+</div>
+
+<main class="grid" id="grid"></main>
+<div class="sentinel" id="sentinel"></div>
+
+<footer>Designer Wallcoverings &amp; Fabrics — To The Trade &amp; Beyond</footer>
+
+<script>
+const grid = document.getElementById('grid'), sentinel = document.getElementById('sentinel');
+const menu = document.getElementById('menu');
+const state = {
+  all: [], view: [], rendered: 0, BATCH: 40,
+  q: '', filter: localStorage.getItem('adw_filter') || 'live',
+  sort: localStorage.getItem('adw_sort') || 'designs',
+  cols: localStorage.getItem('adw_cols') || '4',
+};
+
+document.documentElement.style.setProperty('--cols', state.cols);
+density.value = state.cols;
+sort.value = state.sort;
+setSeg();
+
+function setSeg(){
+  fLive.classList.toggle('on', state.filter==='live');
+  fAll.classList.toggle('on', state.filter==='all');
+}
+
+fetch('/api/vendors').then(r=>r.json()).then(d=>{
+  state.all = d.vendors || [];
+  statVendors.textContent = state.all.length.toLocaleString();
+  statDesigns.textContent = state.all.reduce((s,v)=>s+(v.active_count||0),0).toLocaleString();
+  apply();
+});
+
+function apply(){
+  let v = state.all;
+  if (state.filter==='live') v = v.filter(x=>x.active_count>0);
+  if (state.q) { const q=state.q.toLowerCase(); v = v.filter(x=>x.name.toLowerCase().includes(q)); }
+  if (state.sort==='az') v=[...v].sort((a,b)=>a.name.localeCompare(b.name));
+  else if (state.sort==='za') v=[...v].sort((a,b)=>b.name.localeCompare(a.name));
+  else v=[...v].sort((a,b)=>b.active_count-a.active_count || a.name.localeCompare(b.name));
+  state.view = v; state.rendered = 0; grid.innerHTML=''; renderMore();
+}
+
+function card(v){
+  const el = document.createElement('a');
+  el.className='card'; el.href=v.shop_url; el.target='_blank'; el.rel='noopener noreferrer';
+  const imgs=(v.images||[]).slice(0,4);
+  const cells = imgs.length
+    ? imgs.map(u=>`<img loading="lazy" src="${u.replace(/"/g,'')}" alt="${v.name} design">`).join('')
+    : `<div class="ph">${v.name.charAt(0)}</div>`;
+  el.innerHTML = `
+    <div class="collage${imgs.length<=1?' single':''}">${cells}</div>
+    <div class="meta">
+      <h3></h3>
+      <div class="sub">${v.active_count>0 ? v.active_count.toLocaleString()+' designs live' : 'By inquiry'}</div>
+      <div class="cta">Shop the Line →</div>
+    </div>`;
+  el.querySelector('h3').textContent = v.name;
+  return el;
+}
+
+function renderMore(){
+  const next = state.view.slice(state.rendered, state.rendered + state.BATCH);
+  next.forEach(v=>grid.appendChild(card(v)));
+  state.rendered += next.length;
+}
+
+new IntersectionObserver(es=>{ if(es[0].isIntersecting) renderMore(); }, {rootMargin:'800px'}).observe(sentinel);
+
+q.addEventListener('input', e=>{ state.q=e.target.value.trim(); apply(); });
+sort.addEventListener('change', e=>{ state.sort=e.target.value; localStorage.setItem('adw_sort',state.sort); apply(); });
+density.addEventListener('input', e=>{ state.cols=e.target.value; localStorage.setItem('adw_cols',state.cols);
+  document.documentElement.style.setProperty('--cols', state.cols); });
+fLive.onclick=()=>{ state.filter='live'; localStorage.setItem('adw_filter','live'); setSeg(); apply(); };
+fAll.onclick=()=>{ state.filter='all'; localStorage.setItem('adw_filter','all'); setSeg(); apply(); };
+</script>
+</body>
+</html>

← 9e20e28 staged one-shot go-live script (rsync + pm2 + nginx public-I  ·  back to All Designerwallcoverings  ·  full-catalog browser: in-memory dw_unified snapshot (153k pr 0ff51f2 →