[object Object]

← back to Ventura Claw Leads

yolo tick 16: profile activity badge (≥3 threshold) + /find empty-state with vertical pivot

53d59c15da5d9a52597e1b35b32afcc3029ccd18 · 2026-05-06 20:32:40 -0700 · Steve Abrams

PROFILE ACTIVITY BADGE
- routes/public.js GET /business/:slug: extra query for msgs_30d count.
  Threshold = 3 to show — anything below stays silent (a fresh listing with
  '0 messages received' is worse than no badge at all). Stat lives in a
  brass-bordered side card above 'Quick info' on the profile sidebar.
- Social proof for visitors weighing whether the business is reachable.

/FIND EMPTY-STATE PIVOT
- views/public/find.ejs: when filters yield zero results, replace the
  bare 'no matches' line with the same 8-vertical chip grid the home page
  uses. Pivots a dead-end search into 'browse by category' instead.
- Reuses .verticals-grid + .vertical-chip CSS from the home — zero new
  styles, just a reflow of the existing component into the empty state.

Verified live: badge correctly suppressed at 0 msgs (Olive & Salt has 0,
no badge rendered); will appear once a business crosses 3/month. Empty
state renders 8 chips when ?q=zzznoresults. 46/46 tests pass.

Files touched

Diff

commit 53d59c15da5d9a52597e1b35b32afcc3029ccd18
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 20:32:40 2026 -0700

    yolo tick 16: profile activity badge (≥3 threshold) + /find empty-state with vertical pivot
    
    PROFILE ACTIVITY BADGE
    - routes/public.js GET /business/:slug: extra query for msgs_30d count.
      Threshold = 3 to show — anything below stays silent (a fresh listing with
      '0 messages received' is worse than no badge at all). Stat lives in a
      brass-bordered side card above 'Quick info' on the profile sidebar.
    - Social proof for visitors weighing whether the business is reachable.
    
    /FIND EMPTY-STATE PIVOT
    - views/public/find.ejs: when filters yield zero results, replace the
      bare 'no matches' line with the same 8-vertical chip grid the home page
      uses. Pivots a dead-end search into 'browse by category' instead.
    - Reuses .verticals-grid + .vertical-chip CSS from the home — zero new
      styles, just a reflow of the existing component into the empty state.
    
    Verified live: badge correctly suppressed at 0 msgs (Olive & Salt has 0,
    no badge rendered); will appear once a business crosses 3/month. Empty
    state renders 8 chips when ?q=zzznoresults. 46/46 tests pass.
---
 routes/public.js          | 13 ++++++++++++-
 views/public/business.ejs |  6 ++++++
 views/public/find.ejs     | 18 +++++++++++++++---
 3 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/routes/public.js b/routes/public.js
index a96fcdb..222fe63 100644
--- a/routes/public.js
+++ b/routes/public.js
@@ -95,11 +95,22 @@ router.get('/business/:slug', async (req, res, next) => {
     `, [req.params.slug]);
     if (!biz) return res.status(404).render('public/404', { title: 'Not found' });
 
+    // Activity stat — only surface when meaningful. Threshold = 3 so newly
+    // listed businesses with zero traction don't show "0 messages received"
+    // which is worse than showing nothing.
+    const activity = await db.one(`
+      SELECT COUNT(*)::int AS msgs_30d
+        FROM business_interest
+       WHERE business_id = $1 AND created_at > now() - interval '30 days'
+    `, [biz.id]);
+    const showActivity = activity.msgs_30d >= 3;
+
     res.render('public/business', {
       title: `${biz.business_name} · ${BY_KEY[biz.vertical]?.label || biz.vertical} on Ventura Blvd`,
       metaDescription: biz.headline || `${biz.business_name} — ${BY_KEY[biz.vertical]?.label || biz.vertical} in ${biz.neighborhood || biz.city || 'the Ventura Blvd corridor'}.`,
       business: biz,
-      verticalMeta: BY_KEY[biz.vertical] || null
+      verticalMeta: BY_KEY[biz.vertical] || null,
+      activity, showActivity
     });
   } catch (err) { next(err); }
 });
diff --git a/views/public/business.ejs b/views/public/business.ejs
index 9cfa64e..bd71ebb 100644
--- a/views/public/business.ejs
+++ b/views/public/business.ejs
@@ -87,6 +87,12 @@
   </div>
 
   <aside class="profile-side">
+    <% if (typeof showActivity !== 'undefined' && showActivity) { %>
+      <div class="side-card" style="text-align:center;background:var(--bg);border-color:var(--brass)">
+        <p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= activity.msgs_30d %></p>
+        <p style="margin:6px 0 0;font-size:12px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">messages routed in the last 30 days</p>
+      </div>
+    <% } %>
     <div class="side-card">
       <h3>Quick info</h3>
       <% if (_addressOneline) { %><div class="info-row"><strong>Address</strong><span><%= _addressOneline %></span></div><% } %>
diff --git a/views/public/find.ejs b/views/public/find.ejs
index a5d7162..3698653 100644
--- a/views/public/find.ejs
+++ b/views/public/find.ejs
@@ -64,9 +64,21 @@
   </div>
 
   <% if (businesses.length === 0) { %>
-    <div style="padding:40px 0;text-align:center">
-      <h3>No matches yet.</h3>
-      <p class="muted">Try a broader search, or <a href="/find">browse all listings</a>. We're adding new businesses every week.</p>
+    <div style="padding:40px 0">
+      <h3 style="text-align:center;margin:0 0 12px">No matches yet.</h3>
+      <p class="muted" style="text-align:center;margin:0 0 32px">Try a broader search, or browse by category:</p>
+      <div class="verticals-grid">
+        <% verticals.forEach(function(vv){ %>
+          <a class="vertical-chip" href="/find?vertical=<%= vv.key %>">
+            <span class="v-icon" aria-hidden="true"><%= vv.icon %></span>
+            <span class="v-meta">
+              <span class="v-label"><%= vv.label %></span>
+              <span class="v-count"><%= vv.blurb %></span>
+            </span>
+          </a>
+        <% }); %>
+      </div>
+      <p class="muted" style="text-align:center;margin-top:24px;font-size:13px"><a href="/find">View all businesses</a></p>
     </div>
   <% } else { %>
     <div class="business-grid" id="grid">

← 4e2663d yolo tick 15: home-page 'How it works' 3-step explainer  ·  back to Ventura Claw Leads  ·  yolo tick 17: per-vertical SEO landing pages + home FAQ sect 8634cfd →