← 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
M routes/public.jsM views/public/business.ejsM views/public/find.ejs
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 →