← back to Stars of Design

views/public/clients.ejs

114 lines

<%- include('../partials/head', { title: 'DW Designer Clients · Stars of Design' }) %>
<%- include('../partials/header') %>

<main class="home">
  <section class="hero-gucci" aria-labelledby="hero-wordmark" style="min-height:42vh">
    <div class="hero-gucci-bg" role="img" aria-label="Editorial interior placeholder"></div>
    <div class="hero-gucci-stack">
      <h1 id="hero-wordmark" class="hero-gucci-wordmark" style="font-size:clamp(40px,5vw,72px)">DW Clients</h1>
      <p class="hero-gucci-tagline"><em>The designers and firms who actually buy from Designer Wallcoverings.</em></p>
      <div class="hero-gucci-cta">
        <a class="btn ghost" href="/designers">Editorial directory →</a>
      </div>
    </div>
  </section>

  <section class="section" id="clients">
    <div class="wrap">
      <div class="section-head">
        <h2>Active DW client list</h2>
        <span class="muted"><%= clients.length %> of <%= total %> · pulled from George Gmail + DW Shopify orders</span>
      </div>

      <form class="grid-controls" method="get" action="/clients" data-grid-controls>
        <div class="grid-controls-left">
          <input type="search" name="q" placeholder="Search name, firm, city" value="<%= (filter.q||'').replace(/"/g,'&quot;') %>" style="background:transparent;border:1px solid var(--line,#2a2620);color:inherit;padding:7px 12px;border-radius:6px;font:inherit;min-width:240px">
          <button type="submit" class="btn ghost" style="margin-left:8px">Search</button>
        </div>
        <div class="grid-controls-right">
          <label class="control">
            <span>Sort</span>
            <select id="sort-select" name="sort" data-sort onchange="this.form.submit()">
              <option value="default" <%= sortKey==='default' ? 'selected' : '' %>>Premium first</option>
              <option value="name_az" <%= sortKey==='name_az' ? 'selected' : '' %>>Name A→Z</option>
              <option value="name_za" <%= sortKey==='name_za' ? 'selected' : '' %>>Name Z→A</option>
              <option value="firm"    <%= sortKey==='firm'    ? 'selected' : '' %>>By firm</option>
              <option value="city"    <%= sortKey==='city'    ? 'selected' : '' %>>By city</option>
              <option value="claimed" <%= sortKey==='claimed' ? 'selected' : '' %>>Claimed first</option>
            </select>
          </label>
          <label class="control">
            <span>Density</span>
            <input id="density-slider" type="range" min="220" max="440" step="20" value="320" data-density>
          </label>
        </div>
      </form>

      <% if (!clients.length) { %>
        <p class="muted" style="padding:60px 0;text-align:center;font-style:italic">No clients match that filter yet.</p>
      <% } %>

      <div class="designer-grid" data-designer-grid>
        <% clients.forEach(function(c) { %>
          <article class="designer-card">
            <header>
              <span class="card-mark" aria-hidden="true"><%= (c.full_name||'?').split(' ').map(s=>s[0]).join('').slice(0,2).toUpperCase() %></span>
              <div>
                <h3><a href="/clients/<%= c.slug %>"><%= c.full_name %></a><% if (c.premium_tier) { %> <span class="chip gold" style="margin-left:6px"><%= c.premium_tier.toUpperCase() %></span><% } %></h3>
                <p class="muted">
                  <% if (c.role) { %><%= c.role %><% if (c.firm) { %> · <%= c.firm.name %><% } %>
                  <% } else if (c.firm) { %><%= c.firm.name %><% } %>
                  <% if (c.city || c.state) { %> · <%= [c.city, c.state, c.country && c.country !== 'United States' && c.country !== 'USA' ? c.country : null].filter(Boolean).join(', ') %><% } %>
                </p>
              </div>
            </header>

            <% if (c.areas && c.areas.length) { %>
              <ul class="tags">
                <% c.areas.slice(0,6).forEach(function(a) { %><li><%= a %></li><% }); %>
              </ul>
            <% } %>

            <% if (c.links && (c.links.linkedin || c.links['firm-site'] || c.links.instagram)) { %>
              <div class="card-links" style="display:flex;gap:10px;margin-top:10px;flex-wrap:wrap">
                <% (c.links.linkedin||[]).slice(0,1).forEach(function(u) { %>
                  <a href="<%= u %>" rel="nofollow noopener noreferrer" target="_blank" class="muted" style="font-size:12px">LinkedIn ↗</a>
                <% }); %>
                <% (c.links['firm-site']||[]).slice(0,1).forEach(function(u) { %>
                  <a href="<%= u %>" rel="nofollow noopener noreferrer" target="_blank" class="muted" style="font-size:12px">Website ↗</a>
                <% }); %>
                <% (c.links.instagram||[]).slice(0,1).forEach(function(u) { %>
                  <a href="<%= u %>" rel="nofollow noopener noreferrer" target="_blank" class="muted" style="font-size:12px">Instagram ↗</a>
                <% }); %>
              </div>
            <% } %>
          </article>
        <% }); %>
      </div>
    </div>
  </section>

  <section class="section alt">
    <div class="wrap">
      <h2>How this list is built</h2>
      <p class="prose">
        We pull DW order notifications, sample requests, trade-program signups,
        and project replies from the <code>info@designerwallcoverings.com</code>
        inbox via the George Gmail agent. Each contact's email signature is
        passed through a local LLM (Ollama gemma3:12b) to extract firm name,
        role, city, state, and areas served. <strong>We never extract street
        addresses or zip codes</strong> — that's a directory-PII policy.
      </p>
      <p class="prose">
        Designers can <a href="/submit">claim and edit their profile</a> at any
        time. Claimed profiles get a Verified badge, real headshot upload, and
        full portfolio gallery.
      </p>
    </div>
  </section>
</main>

<%- include('../partials/footer') %>
<script src="/js/grid-controls.js" defer></script>
<script src="/js/hamburger.js" defer></script>