← back to Ventura Claw Leads
views/public/neighborhood.ejs
183 lines
<%- include('../partials/head', { title }) %>
<%- include('../partials/header') %>
<%
// BreadcrumbList JSON-LD: Home › Find › Neighborhood
var _publicUrl = (typeof publicUrl !== 'undefined' && publicUrl) ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com';
var _ldCrumbs = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: _publicUrl + '/' },
{ '@type': 'ListItem', position: 2, name: 'Find a business', item: _publicUrl + '/find' },
{ '@type': 'ListItem', position: 3, name: neighborhood.label, item: _publicUrl + '/neighborhood/' + neighborhood.slug }
]
};
// ItemList JSON-LD — same shape as /find. Surfaces the neighborhood page
// as a structured list of LocalBusiness entries to Google. Capped at 30
// items (Google's recommended depth for ItemList rich results).
var _ldList = {
'@context': 'https://schema.org',
'@type': 'ItemList',
name: neighborhood.label + ' businesses on Ventura Blvd',
itemListOrder: 'https://schema.org/ItemListOrderAscending',
numberOfItems: (typeof totalCount !== 'undefined' && totalCount) ? totalCount : businesses.length,
itemListElement: businesses.slice(0, 30).map(function(b, i){
return {
'@type': 'ListItem',
position: i + 1,
url: _publicUrl + '/business/' + b.slug,
name: b.business_name
};
})
};
%>
<script type="application/ld+json"><%- JSON.stringify(_ldCrumbs) %></script>
<script type="application/ld+json"><%- JSON.stringify(_ldList) %></script>
<nav class="breadcrumb" aria-label="Breadcrumb" style="padding:16px 28px 0;max-width:1440px;margin:0 auto;font-size:12px;color:var(--fg-muted);letter-spacing:0.04em">
<a href="/" style="border:none;color:var(--fg-muted)">Home</a>
<span style="margin:0 6px;opacity:0.5">›</span>
<a href="/find" style="border:none;color:var(--fg-muted)">Find</a>
<span style="margin:0 6px;opacity:0.5">›</span>
<span style="color:var(--fg)"><%= neighborhood.label %></span>
</nav>
<section class="find-page">
<p class="kicker">On Ventura Blvd</p>
<h1 class="display-sm">Businesses in <%= neighborhood.label %></h1>
<%
// Use !== undefined (not truthiness) so a legitimate 0 doesn't fall
// back to businesses.length. PROSECUTOR caught the falsy-zero edge in
// the codex-3way sweep; harmless in practice (both equal 0 for empty
// neighborhoods) but cleaner this way.
var _displayTotal = (typeof totalCount !== 'undefined') ? totalCount : businesses.length;
%>
<p class="lede"><%= _displayTotal %> business<%= _displayTotal === 1 ? '' : 'es' %> in <%= neighborhood.label %>. Search by name, browse by category, or jump straight to a profile and send a message.</p>
<%
// Per-vertical chip row + an inline 'browse by category' paragraph that
// gives Google crawlable anchor text linking each vertical+city combo
// page. Hide verticals with 0 matches so we don't ship soft-404 chips.
var _vMap = (typeof verticalCountMap !== 'undefined') ? verticalCountMap : {};
var _activeVerticals = verticals.filter(function(v){ return (_vMap[v.key] || 0) > 0; });
%>
<div class="grid-controls" role="region" aria-label="Filters" style="margin-top:24px">
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">
<% _activeVerticals.forEach(function(v){
var n = _vMap[v.key] || 0;
%>
<a href="/find?vertical=<%= v.key %>&city=<%= encodeURIComponent(neighborhood.label) %>" class="btn btn-ghost btn-sm" style="font-size:12px">
<%= v.icon %> <%= v.label %> <span class="muted" style="font-size:11px;font-weight:400">· <%= n %></span>
</a>
<% }); %>
</div>
<% var _sort = (typeof sort === 'string') ? sort : 'featured'; %>
<div class="grid-controls-right">
<label class="grid-control" for="grid-sort"><span>Sort</span>
<select id="grid-sort" aria-label="Sort businesses by">
<option value="featured" <%= _sort === 'featured' ? 'selected' : '' %>>Featured</option>
<option value="newest" <%= _sort === 'newest' ? 'selected' : '' %>>Newest</option>
<option value="updated" <%= _sort === 'updated' ? 'selected' : '' %>>Recently updated</option>
<option value="name_asc" <%= _sort === 'name_asc' ? 'selected' : '' %>>Name A→Z</option>
<option value="name_desc" <%= _sort === 'name_desc' ? 'selected' : '' %>>Name Z→A</option>
<option value="vertical" <%= _sort === 'vertical' ? 'selected' : '' %>>By category</option>
</select>
</label>
<label class="grid-control"><span>Density</span>
<input type="range" id="grid-density" min="220" max="480" step="20" value="300" aria-label="Card width">
</label>
</div>
</div>
<% if (_activeVerticals.length > 1) { %>
<p class="muted" style="margin:14px 0 0;font-size:13px;line-height:1.7">
Jump to a category in <%= neighborhood.label %>:
<% _activeVerticals.forEach(function(v, i){
var n = _vMap[v.key] || 0;
%><% if (i > 0) { %>, <% } %><a href="/find?vertical=<%= v.key %>&city=<%= encodeURIComponent(neighborhood.label) %>" style="font-weight:600"><%= v.label.toLowerCase() %> in <%= neighborhood.label %></a> (<%= n %>)<% }); %>.
</p>
<% } %>
<% if (businesses.length === 0) { %>
<div style="padding:40px 0;text-align:center">
<h3>No businesses listed in <%= neighborhood.label %> yet.</h3>
<p class="muted">We're adding new businesses every week. <a href="/find">Browse all listings</a>.</p>
</div>
<% } else { %>
<div class="business-grid" id="grid" style="margin-top:24px">
<% businesses.forEach(function(b){
var vMeta = verticals.find(function(vv){return vv.key===b.vertical});
%>
<a class="business-card" href="/business/<%= b.slug %>">
<% if (b.photo_path) { %>
<div class="biz-card-hero biz-card-hero-photo">
<img src="<%= b.photo_path %>" alt="<%= b.business_name %>" loading="lazy" decoding="async">
</div>
<% } else { %>
<div class="biz-card-hero vertical-hero-<%= b.vertical %>" aria-hidden="true">
<%- include('../partials/vertical-glyph', { verticalKey: b.vertical }) %>
</div>
<% } %>
<p class="biz-vertical"><%= vMeta ? vMeta.label : b.vertical %></p>
<p class="biz-name"><%= b.business_name %></p>
<p class="biz-loc"><%= [b.neighborhood, b.city, b.state].filter(Boolean).join(' · ') %></p>
<% if (b.headline) { %><p class="biz-headline"><%= b.headline %></p><% } %>
<div class="biz-badges">
<% if (b.tier === 'premier') { %><span class="biz-badge is-premier">Featured</span>
<% } else if (b.tier === 'standard') { %><span class="biz-badge is-standard">Standard</span><% } %>
<% if (b.claim_status === 'self' || b.claim_status === 'claimed') { %><span class="biz-badge is-claimed">Claimed</span><% } %>
<% if (b.verified) { %><span class="biz-badge">Verified</span><% } %>
</div>
</a>
<% }); %>
</div>
<% } %>
</section>
<script>
// Sort + density: same pattern as /find — persist to localStorage,
// hydrate the saved value BEFORE the first grid render, sort change
// reloads with ?sort=, density updates a CSS var live (no reload).
// Reuses the 'vcl-grid-density' / 'vcl-grid-sort' keys so preferences
// carry between /find and /neighborhood/:slug.
(function(){
var grid = document.getElementById('grid');
var sortSel = document.getElementById('grid-sort');
var dens = document.getElementById('grid-density');
if (!grid || !sortSel || !dens) return;
function applyDensity(px){
grid.style.setProperty('--vcl-card-min', px + 'px');
try { localStorage.setItem('vcl-grid-density', String(px)); } catch(e){}
}
var savedDens = null;
try { savedDens = localStorage.getItem('vcl-grid-density'); } catch(e){}
if (savedDens && Number(savedDens) >= 220 && Number(savedDens) <= 480) {
dens.value = savedDens;
applyDensity(Number(savedDens));
} else {
applyDensity(Number(dens.value));
}
dens.addEventListener('input', function(){ applyDensity(Number(this.value)); });
var savedSort = null;
try { savedSort = localStorage.getItem('vcl-grid-sort'); } catch(e){}
var urlSort = new URLSearchParams(window.location.search).get('sort');
if (!urlSort && savedSort && savedSort !== 'featured') {
var p = new URLSearchParams(window.location.search);
p.set('sort', savedSort);
window.location.search = p.toString();
return;
}
sortSel.addEventListener('change', function(){
try { localStorage.setItem('vcl-grid-sort', this.value); } catch(e){}
var p = new URLSearchParams(window.location.search);
if (this.value === 'featured') p.delete('sort'); else p.set('sort', this.value);
window.location.search = p.toString();
});
})();
</script>
<%- include('../partials/footer') %>