← back to Ventura Claw Leads
yolo tick 27: 'More <vertical> nearby' rail at the bottom of every profile
f40cfdad5683ed90472419b0c092a6ddc2d08828 · 2026-05-07 02:39:31 -0700 · Steve Abrams
routes/public.js GET /business/:slug: extra query when the business has
lat/lng — fetches 3 nearest same-vertical active businesses sorted by
squared cartesian distance on lat/lng. Skip the rail entirely when this
business has no coords (rare since seed-geocode runs on every business).
Squared-cartesian is good enough for a 12-mile corridor — proper
Haversine would give true great-circle distance but for a near-flat
slice of LA the absolute error is < 0.01% per pair. Avoids needing the
earthdistance/cube postgres extensions on prod.
views/public/business.ejs: new section after profile-body — H2 'More
<vertical> nearby', sub-text, 3-card grid (mobile collapses to 1 col).
Cards reuse the same hero treatment as /find (photo or vertical
gradient + emoji glyph).
public/css/public.css: .similar-nearby + .similar-grid + responsive
breakpoint at 768.
Live: 'More food & drink nearby' renders on Olive & Salt with 3 nearby
food businesses. Real engagement boost — visitors who land on one
profile via search-engine traffic now have a within-vertical browse path
without backtracking to /find. 46/46 tests still pass.
Files touched
M public/css/public.cssM routes/public.jsM views/public/business.ejs
Diff
commit f40cfdad5683ed90472419b0c092a6ddc2d08828
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 7 02:39:31 2026 -0700
yolo tick 27: 'More <vertical> nearby' rail at the bottom of every profile
routes/public.js GET /business/:slug: extra query when the business has
lat/lng — fetches 3 nearest same-vertical active businesses sorted by
squared cartesian distance on lat/lng. Skip the rail entirely when this
business has no coords (rare since seed-geocode runs on every business).
Squared-cartesian is good enough for a 12-mile corridor — proper
Haversine would give true great-circle distance but for a near-flat
slice of LA the absolute error is < 0.01% per pair. Avoids needing the
earthdistance/cube postgres extensions on prod.
views/public/business.ejs: new section after profile-body — H2 'More
<vertical> nearby', sub-text, 3-card grid (mobile collapses to 1 col).
Cards reuse the same hero treatment as /find (photo or vertical
gradient + emoji glyph).
public/css/public.css: .similar-nearby + .similar-grid + responsive
breakpoint at 768.
Live: 'More food & drink nearby' renders on Olive & Salt with 3 nearby
food businesses. Real engagement boost — visitors who land on one
profile via search-engine traffic now have a within-vertical browse path
without backtracking to /find. 46/46 tests still pass.
---
public/css/public.css | 8 ++++++++
routes/public.js | 20 +++++++++++++++++++-
views/public/business.ejs | 26 ++++++++++++++++++++++++++
3 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/public/css/public.css b/public/css/public.css
index 199f398..e2d8c66 100644
--- a/public/css/public.css
+++ b/public/css/public.css
@@ -123,6 +123,14 @@
.side-card .info-row:last-child { border-bottom: none; }
.side-card .info-row strong { min-width: 80px; color: var(--fg-muted); font-weight: 500; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; }
+/* Similar-nearby rail */
+.similar-nearby { padding: 32px 28px 48px; max-width: 1100px; margin: 0 auto; border-top: 1px solid var(--border); }
+.similar-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
+.similar-card { padding: 14px 16px; }
+@media (max-width: 768px) {
+ .similar-grid { grid-template-columns: 1fr; }
+}
+
/* Contact form */
.contact-form { padding: 18px 20px; border: 2px solid var(--brass); background: var(--bg-alt); border-radius: 8px; }
.contact-form h3 { margin: 0 0 6px; font-family: var(--serif); font-size: 22px; font-weight: 500; }
diff --git a/routes/public.js b/routes/public.js
index cb14044..fa93a3e 100644
--- a/routes/public.js
+++ b/routes/public.js
@@ -188,12 +188,30 @@ router.get('/business/:slug', async (req, res, next) => {
`, [biz.id]);
const showActivity = activity.msgs_30d >= 3 || activity.views_30d >= 25;
+ // Similar-nearby rail: 3 same-vertical businesses sorted by squared
+ // cartesian lat/lng distance (good enough for a 12-mile corridor; no
+ // PostGIS extension required). Only when this business has coords.
+ let nearby = [];
+ if (biz.latitude != null && biz.longitude != null) {
+ nearby = await db.many(`
+ SELECT slug, business_name, vertical, neighborhood, city, photo_path
+ FROM businesses
+ WHERE id != $1
+ AND vertical = $2
+ AND status = 'active'
+ AND latitude IS NOT NULL AND longitude IS NOT NULL
+ ORDER BY POW(latitude - $3, 2) + POW(longitude - $4, 2) ASC
+ LIMIT 3
+ `, [biz.id, biz.vertical, biz.latitude, biz.longitude]);
+ }
+
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,
- activity, showActivity
+ activity, showActivity, nearby,
+ verticals: VERTICALS
});
} catch (err) { next(err); }
});
diff --git a/views/public/business.ejs b/views/public/business.ejs
index 3491151..10dab53 100644
--- a/views/public/business.ejs
+++ b/views/public/business.ejs
@@ -200,4 +200,30 @@
</aside>
</section>
+<% if (typeof nearby !== 'undefined' && nearby && nearby.length > 0) { %>
+ <section class="similar-nearby">
+ <h2 style="font-family:var(--serif);font-weight:500;font-size:24px;margin:0 0 6px">More <%= verticalMeta ? verticalMeta.label.toLowerCase() : '' %> nearby</h2>
+ <p class="muted" style="margin:0 0 18px;font-size:13px">Other Ventura Blvd businesses close to <%= business.business_name %>.</p>
+ <div class="similar-grid">
+ <% nearby.forEach(function(n){
+ var nMeta = verticals.find(function(vv){return vv.key===n.vertical});
+ %>
+ <a class="business-card similar-card" href="/business/<%= n.slug %>">
+ <% if (n.photo_path) { %>
+ <div class="biz-card-hero biz-card-hero-photo">
+ <img src="<%= n.photo_path %>" alt="<%= n.business_name %>" loading="lazy" decoding="async">
+ </div>
+ <% } else { %>
+ <div class="biz-card-hero vertical-hero-<%= n.vertical %>" aria-hidden="true">
+ <span class="biz-card-glyph"><%= nMeta ? nMeta.icon : '•' %></span>
+ </div>
+ <% } %>
+ <p class="biz-name" style="font-size:16px;margin:6px 0 2px"><%= n.business_name %></p>
+ <p class="biz-loc" style="font-size:12px;margin:0"><%= [n.neighborhood, n.city].filter(Boolean).join(' · ') %></p>
+ </a>
+ <% }); %>
+ </div>
+ </section>
+<% } %>
+
<%- include('../partials/footer') %>
← f41ef50 yolo tick 26: per-neighborhood landing pages — /neighborhood
·
back to Ventura Claw Leads
·
yolo tick 28: home 'Recently joined' rail (conditional ≥3 cl 91c32a2 →