← back to Designerssalesreps

public/index.html

354 lines

<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script>(function(){try{var t=localStorage.getItem('theme');if(!t){t=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';}document.documentElement.setAttribute('data-theme',t);}catch(e){}})();</script>
<title>Designers Sales Reps — Map-Based Trade Rep Finder | DesignersSalesReps.com</title>
<meta name="description" content="Find design trade sales reps by geography. Map-first directory of 40+ showroom reps covering wallcoverings, textiles, and contract fabrics. Claim your free listing.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{--accent:#3a7a78;--accent-light:#5a9a98;--accent-dark:#2a5a58;--bg:#0a0a0a;--card-bg:rgba(245,241,232,0.04);--header-bg:rgba(10,10,10,0.92);--ink:#f5f1e8;--ink-soft:rgba(245,241,232,0.7);--ink-faint:rgba(245,241,232,0.35);--line:rgba(245,241,232,0.10)}
[data-theme="light"]{--bg:#f5f9f9;--card-bg:rgba(10,10,10,0.04);--header-bg:rgba(245,249,249,0.92);--ink:#0d1f1f;--ink-soft:rgba(10,10,10,0.7);--ink-faint:rgba(10,10,10,0.35);--line:rgba(10,10,10,0.10)}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--ink);min-height:100vh;transition:background .2s,color .2s}
.header{position:sticky;top:0;z-index:100;background:var(--header-bg);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);padding:0 2rem;display:flex;align-items:center;justify-content:space-between;height:64px}
.brand{display:flex;align-items:center;gap:12px;text-decoration:none;color:var(--ink)}
.brand-dot{width:28px;height:28px;border-radius:50%;background:radial-gradient(circle at 35% 35%,#5a9a98 0%,#3a7a78 60%,#2a5a58 100%);flex-shrink:0}
.brand-name{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.25rem;font-weight:600;letter-spacing:.02em}
.nav-actions{display:flex;align-items:center;gap:1rem}
.theme-toggle{background:none;border:1px solid var(--line);color:var(--ink);padding:6px 10px;border-radius:6px;cursor:pointer;display:flex;align-items:center;gap:6px}
[data-theme="dark"] .icon-sun{display:block}[data-theme="dark"] .icon-moon{display:none}
[data-theme="light"] .icon-sun{display:none}[data-theme="light"] .icon-moon{display:block}
.hero{padding:3rem 2rem 2rem;text-align:center}
.hero h1{font-family:'Cormorant Garamond',Georgia,serif;font-size:clamp(1.8rem,4vw,3rem);font-weight:400;line-height:1.2;margin-bottom:.75rem;color:var(--ink)}
.hero h1 em{font-style:italic;color:var(--accent)}
.hero p{color:var(--ink-soft);max-width:540px;margin:0 auto 1.5rem;font-size:1rem;line-height:1.6}
.stub-banner{background:rgba(58,122,120,0.12);border:1px solid rgba(58,122,120,0.3);color:var(--accent-light);padding:.5rem 1.5rem;text-align:center;font-size:.82rem;letter-spacing:.04em}
.search-bar{max-width:560px;margin:0 auto 2rem;display:flex;gap:.75rem;padding:0 1rem}
.search-input{flex:1;background:var(--card-bg);backdrop-filter:blur(12px);border:1px solid var(--line);color:var(--ink);padding:.75rem 1rem;border-radius:8px;font-size:1rem;font-family:inherit;outline:none}
.search-input:focus{border-color:var(--accent)}
.search-input::placeholder{color:var(--ink-faint)}
.layout{display:flex;height:calc(100vh - 200px);min-height:500px;max-height:680px;gap:0}
.map-panel{flex:1;position:relative;background:rgba(10,30,30,0.8);border-right:1px solid var(--line);overflow:hidden}
.map-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--ink-soft)}
.map-placeholder svg{opacity:.4;margin-bottom:1rem}
.map-grid{display:grid;grid-template-columns:repeat(5,1fr);grid-template-rows:repeat(4,1fr);gap:2px;padding:1rem;height:100%;width:100%}
.map-cell{border-radius:6px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;font-size:.7rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;padding:.25rem;text-align:center}
.map-cell.has-reps{background:rgba(58,122,120,0.2);border:1px solid rgba(58,122,120,0.3);color:var(--accent-light)}
.map-cell.has-reps:hover{background:rgba(58,122,120,0.4);border-color:var(--accent)}
.map-cell.empty{background:rgba(245,241,232,0.02);border:1px solid rgba(245,241,232,0.04);color:var(--ink-faint)}
.map-cell.selected{background:var(--accent);color:#fff;border-color:var(--accent-light)}
.list-panel{width:380px;display:flex;flex-direction:column;overflow:hidden}
.list-header{padding:1rem 1.25rem .75rem;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
.list-header h3{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.1rem}
.list-count{color:var(--ink-faint);font-size:.8rem}
.list-scroll{flex:1;overflow-y:auto;padding:.75rem}
.list-scroll::-webkit-scrollbar{width:4px}
.list-scroll::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px}
.rep-row{padding:.85rem 1rem;border:1px solid var(--line);border-radius:8px;margin-bottom:.5rem;cursor:pointer;transition:border-color .2s,background .2s;background:var(--card-bg);backdrop-filter:blur(12px)}
.rep-row:hover{border-color:var(--accent);background:rgba(58,122,120,0.06)}
.rep-row.active{border-color:var(--accent);background:rgba(58,122,120,0.1)}
.rep-row-name{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.05rem;font-weight:600;margin-bottom:.2rem}
.rep-row-agency{color:var(--accent);font-size:.78rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.3rem}
.rep-row-meta{color:var(--ink-faint);font-size:.78rem}
.rep-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.4rem}
.rep-tag{background:rgba(58,122,120,0.12);border:1px solid rgba(58,122,120,0.25);color:var(--accent-light);padding:.15rem .45rem;border-radius:12px;font-size:.68rem}
.detail-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:200;align-items:center;justify-content:center}
.detail-overlay.open{display:flex}
.detail-box{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:2rem;max-width:480px;width:90%;position:relative}
.detail-box h3{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.6rem;margin-bottom:.5rem}
.detail-close{position:absolute;top:1rem;right:1rem;background:none;border:none;color:var(--ink-soft);font-size:1.5rem;cursor:pointer}
.detail-agency{color:var(--accent);font-size:.85rem;text-transform:uppercase;letter-spacing:.06em;font-weight:600;margin-bottom:1.2rem}
.detail-grid{display:grid;grid-template-columns:auto 1fr;gap:.4rem .75rem;font-size:.88rem}
.detail-lbl{color:var(--ink-faint)}
.detail-val{color:var(--ink-soft)}
.detail-link{display:inline-block;margin-top:1.25rem;padding:.6rem 1.5rem;background:var(--accent);color:#fff;border-radius:8px;text-decoration:none;font-size:.85rem;font-weight:600;transition:background .2s}
.detail-link:hover{background:var(--accent-light)}
.claim-section{max-width:600px;margin:2rem auto 4rem;padding:2rem;background:var(--card-bg);backdrop-filter:blur(12px);border:1px solid var(--line);border-radius:14px;text-align:center}
.claim-section h2{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.8rem;margin-bottom:.5rem}
.claim-section p{color:var(--ink-soft);margin-bottom:1.5rem;font-size:.95rem}
.claim-form{display:flex;flex-direction:column;gap:.75rem;text-align:left}
.claim-form input,.claim-form textarea{background:var(--card-bg);border:1px solid var(--line);color:var(--ink);padding:.75rem;border-radius:8px;font-size:.9rem;font-family:inherit;outline:none;resize:vertical}
.claim-form input:focus,.claim-form textarea:focus{border-color:var(--accent)}
.claim-submit{background:var(--accent);color:#fff;border:none;padding:.8rem 2rem;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;transition:background .2s}
.claim-submit:hover{background:var(--accent-light)}
.footer{border-top:1px solid var(--line);padding:2rem;text-align:center;color:var(--ink-faint);font-size:.85rem}
.footer a{color:var(--accent);text-decoration:none}
</style>
</head>
<body>
<div class="stub-banner">Stub directory — claim your listing to be officially featured</div>
<header class="header">
  <a href="/" class="brand">
    <div class="brand-dot"></div>
    <span class="brand-name">DesignersSalesReps</span>
  </a>
  <div class="nav-actions">
    <button class="theme-toggle" id="theme-toggle" aria-label="Toggle theme">
      <svg class="icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
      <svg class="icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg>
    </button>
  </div>
</header>

<section class="hero">
  <h1>Trade Reps by <em>Territory</em></h1>
  <p>Select a region on the map to browse sales representatives covering that market.</p>
  <div class="search-bar">
    <input class="search-input" id="q" type="text" placeholder="Search rep, agency, or product line…" autocomplete="off">
  </div>
</section>

<div class="layout">
  <div class="map-panel">
    <div class="map-grid" id="map-grid"></div>
  </div>
  <div class="list-panel">
    <div class="list-header">
      <h3 id="region-label">All Regions</h3>
      <span class="list-count" id="list-count">40 reps</span>
    </div>
    <div class="list-scroll" id="list-scroll"></div>
  </div>
</div>

<div class="claim-section" id="claim">
  <h2>Claim Your Listing</h2>
  <p>Are you a design trade rep not yet in the directory? Get listed free.</p>
  <form class="claim-form" id="claim-form">
    <input type="text" name="name" placeholder="Your full name" required>
    <input type="text" name="agency" placeholder="Agency or firm name" required>
    <input type="email" name="email" placeholder="Business email" required>
    <input type="text" name="territories" placeholder="Territories covered (e.g., CA, OR, WA)">
    <input type="text" name="lines" placeholder="Product lines you represent">
    <textarea name="note" rows="3" placeholder="Anything else"></textarea>
    <button type="submit" class="claim-submit">Submit My Listing</button>
  </form>
</div>

<footer class="footer">
  <p>info@designerssalesreps.com &nbsp;|&nbsp; &copy; 2026 DesignersSalesReps.com &nbsp;|&nbsp; A partner of <a href="https://designerwallcoverings.com" target="_blank" rel="noopener noreferrer">Designer Wallcoverings</a></p>
  <script type="application/ld+json">{"@context":"https://schema.org","@type":"ItemList","name":"Designers Sales Reps Directory","description":"Trade sales reps by territory for design showrooms","url":"https://designerssalesreps.com"}</script>
</footer>

<div class="detail-overlay" id="detail">
  <div class="detail-box">
    <button class="detail-close" id="detail-close">&times;</button>
    <h3 id="d-name"></h3>
    <div class="detail-agency" id="d-agency"></div>
    <div class="detail-grid" id="d-grid"></div>
    <a href="#" id="d-link" class="detail-link" target="_blank" rel="noopener noreferrer">Contact Agency &rarr;</a>
  </div>
</div>

<script>
const regions = [
  {label:'New England',states:['MA','RI','VT','NH','ME','CT']},
  {label:'Tri-State',states:['NY','NJ','CT']},
  {label:'Mid-Atlantic',states:['PA','DE','MD','DC','VA']},
  {label:'Southeast',states:['GA','FL','SC','NC']},
  {label:'Gulf Coast',states:['AL','MS','LA','TN']},
  {label:'Texas',states:['TX','OK','NM','AR']},
  {label:'Great Lakes',states:['IL','WI','MN','IA']},
  {label:'Ohio Valley',states:['OH','MI','IN','KY','WV']},
  {label:'Mountain',states:['CO','UT','WY','MT','ID']},
  {label:'Southwest',states:['AZ','NV','NM']},
  {label:'Carolinas',states:['NC','SC']},
  {label:'Midwest',states:['KS','NE','MO','ND','SD']},
  {label:'Pacific NW',states:['WA','OR','ID']},
  {label:'California',states:['CA']},
  {label:'Hawaii/AK',states:['HI','AK']},
  {label:'National',states:[]}
];

let allReps = [], selectedRegion = null, searchQ = '';

fetch('/api/search?').then(r=>r.json()).then(d=>{
  allReps = d.results;
  buildMap();
  renderList(allReps, 'All Regions');
});

function buildMap(){
  const grid = document.getElementById('map-grid');
  grid.innerHTML = regions.map((reg, i) => {
    const count = reg.states.length ? allReps.filter(r => r.territories.some(t => reg.states.includes(t))).length : allReps.length;
    const cls = count > 0 ? 'has-reps' : 'empty';
    return `<div class="map-cell ${cls}" data-idx="${i}" onclick="selectRegion(${i})">${reg.label}<br><small style="font-size:.65rem;opacity:.7">${count} rep${count!==1?'s':''}</small></div>`;
  }).join('');
}

function selectRegion(idx){
  selectedRegion = idx;
  document.querySelectorAll('.map-cell').forEach((el,i) => {
    el.classList.toggle('selected', i===idx);
  });
  const reg = regions[idx];
  const filtered = reg.states.length ? allReps.filter(r => r.territories.some(t => reg.states.includes(t))) : allReps;
  const label = reg.label;
  renderList(applySearch(filtered), label);
}

function applySearch(list){
  if(!searchQ) return list;
  const q = searchQ.toLowerCase();
  return list.filter(r => r.name.toLowerCase().includes(q) || r.agency.toLowerCase().includes(q) || r.lines.some(l=>l.toLowerCase().includes(q)));
}

function renderList(reps, regionLabel){
  document.getElementById('region-label').textContent = regionLabel;
  document.getElementById('list-count').textContent = reps.length + ' rep' + (reps.length!==1?'s':'');
  document.getElementById('list-scroll').innerHTML = reps.map(r => `
    <div class="rep-row" onclick="showDetail(${r.id})">
      <div class="rep-row-name">${r.name}</div>
      <div class="rep-row-agency">${r.agency}</div>
      <div class="rep-row-meta">${r.city} &middot; ${r.territories.slice(0,3).join(', ')}</div>
      <div class="rep-tags">${r.vertical.slice(0,3).map(v=>`<span class="rep-tag">${v}</span>`).join('')}</div>
    </div>
  `).join('') || '<p style="color:var(--ink-faint);padding:1rem;text-align:center">No reps found in this region.</p>';
}

function showDetail(id){
  const r = allReps.find(x=>x.id===id); if(!r) return;
  document.getElementById('d-name').textContent = r.name;
  document.getElementById('d-agency').textContent = r.agency;
  document.getElementById('d-grid').innerHTML = `
    <span class="detail-lbl">City</span><span class="detail-val">${r.city}</span>
    <span class="detail-lbl">Territories</span><span class="detail-val">${r.territories.join(', ')}</span>
    <span class="detail-lbl">Verticals</span><span class="detail-val">${r.vertical.join(', ')}</span>
    <span class="detail-lbl">Lines</span><span class="detail-val">${r.lines.join(', ')}</span>
  `;
  document.getElementById('d-link').href = r.contact_url;
  document.getElementById('detail').classList.add('open');
}
document.getElementById('detail-close').onclick=()=>document.getElementById('detail').classList.remove('open');
document.getElementById('detail').onclick=e=>{if(e.target===document.getElementById('detail'))document.getElementById('detail').classList.remove('open');};

let debT;
document.getElementById('q').addEventListener('input', e=>{
  searchQ = e.target.value;
  clearTimeout(debT);
  debT = setTimeout(()=>{
    const base = selectedRegion !== null ? (regions[selectedRegion].states.length ? allReps.filter(r=>r.territories.some(t=>regions[selectedRegion].states.includes(t))) : allReps) : allReps;
    renderList(applySearch(base), selectedRegion !== null ? regions[selectedRegion].label : 'All Regions');
  }, 250);
});

document.getElementById('claim-form').onsubmit=e=>{
  e.preventDefault();
  const data=Object.fromEntries(new FormData(e.target));
  fetch('/api/claim',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})
    .then(()=>{e.target.reset();alert('Thank you! We will review and follow up.');});
};

const btn=document.getElementById('theme-toggle');
function syncTheme(){var t=document.documentElement.getAttribute('data-theme');btn.setAttribute('aria-pressed',t==='dark');}
syncTheme();
btn.addEventListener('click',function(){var cur=document.documentElement.getAttribute('data-theme')||'dark';var nxt=cur==='dark'?'light':'dark';document.documentElement.setAttribute('data-theme',nxt);try{localStorage.setItem('theme',nxt);}catch(e){}syncTheme();});
</script>
<!-- DW-HAMBURGER-NAV v1 (canonical 2026-05-10) -->
<style id="dw-hamburger-styles">
  /* Canonical hamburger nav — always visible top-right, all viewports.
     Standing rule (Steve, 2026-05-08): hamburger is the primary nav. */
  .dw-burger-btn {
    position: fixed; top: 18px; right: 18px; z-index: 9999;
    width: 44px; height: 44px;
    background: rgba(20,20,20,0.78); color: #f5f1e8;
    backdrop-filter: blur(8px) saturate(140%);
    -webkit-backdrop-filter: blur(8px) saturate(140%);
    border: 1px solid rgba(245,241,232,0.18); border-radius: 50%;
    cursor: pointer; padding: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 4px;
    box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    transition: background 200ms, transform 200ms;
  }
  .dw-burger-btn:hover { background: rgba(20,20,20,0.92); }
  .dw-burger-btn span {
    display: block; width: 20px; height: 1.5px; background: currentColor;
    transition: transform 220ms, opacity 220ms;
  }
  .dw-burger-btn[aria-expanded="true"] span:nth-child(1) {
    transform: translateY(5.5px) rotate(45deg);
  }
  .dw-burger-btn[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .dw-burger-btn[aria-expanded="true"] span:nth-child(3) {
    transform: translateY(-5.5px) rotate(-45deg);
  }
  .dw-burger-panel {
    position: fixed; top: 72px; right: 18px; z-index: 9998;
    min-width: 240px; max-width: 88vw;
    background: rgba(15,15,15,0.96);
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    border: 1px solid rgba(245,241,232,0.12);
    border-radius: 14px;
    box-shadow: 0 18px 50px rgba(0,0,0,0.55);
    padding: 10px 0;
    display: none;
    font-family: -apple-system, "SF Pro Text", system-ui, sans-serif;
  }
  .dw-burger-panel.open { display: block; }
  .dw-burger-panel a,
  .dw-burger-panel button {
    display: block; width: 100%;
    padding: 13px 22px;
    color: #f5f1e8 !important;
    background: transparent; border: 0; cursor: pointer;
    text-decoration: none !important;
    font-size: 15px; font-weight: 400;
    text-align: left;
    letter-spacing: 0;
    border-bottom: 1px solid rgba(245,241,232,0.06);
    transition: background 160ms, color 160ms;
    box-sizing: border-box;
  }
  .dw-burger-panel a:last-child,
  .dw-burger-panel button:last-child { border-bottom: 0; }
  .dw-burger-panel a:hover,
  .dw-burger-panel button:hover {
    background: rgba(245,241,232,0.06);
    color: #ffffff !important;
  }
  @media (max-width: 480px) {
    .dw-burger-btn { top: 12px; right: 12px; width: 40px; height: 40px; }
    .dw-burger-panel { top: 62px; right: 12px; }
  }
  /* Avoid double-print on sites already adopting a top-of-page header
     that overlaps z-index. Reserve safe space near top-right. */
</style>
<button type="button" class="dw-burger-btn" id="dwBurgerBtn"
        aria-label="Toggle menu" aria-controls="dwBurgerPanel"
        aria-expanded="false">
  <span></span><span></span><span></span>
</button>
<nav class="dw-burger-panel" id="dwBurgerPanel" aria-label="Primary">
  <a href="/">DesignersSalesReps</a>
  <a href="mailto:info@designerssalesreps.com">Contact</a>
</nav>
<script>
(function(){
  var btn = document.getElementById('dwBurgerBtn');
  var panel = document.getElementById('dwBurgerPanel');
  if (!btn || !panel) return;
  function close(){ panel.classList.remove('open'); btn.setAttribute('aria-expanded','false'); }
  function toggle(){
    var open = panel.classList.toggle('open');
    btn.setAttribute('aria-expanded', open ? 'true' : 'false');
  }
  btn.addEventListener('click', function(e){ e.stopPropagation(); toggle(); });
  document.addEventListener('click', function(e){
    if (panel.classList.contains('open') && !panel.contains(e.target) && e.target !== btn) close();
  });
  document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
})();
</script>
<!-- /DW-HAMBURGER-NAV v1 -->

</body>
</html>