← back to Ventura Claw Leads
views/public/business.ejs
254 lines
<%- include('../partials/head', {
title,
ogImage: typeof ogImage !== 'undefined' && ogImage ? ogImage : null
}) %>
<%- include('../partials/header') %>
<%
function _safeUrl(u) {
if (!u) return null;
try { var x = new URL(u.startsWith('http') ? u : 'https://' + u); return (x.protocol === 'http:' || x.protocol === 'https:') ? x.toString() : null; }
catch(e){ return null; }
}
var _site = _safeUrl(business.website);
var _addressOneline = [business.street, business.neighborhood || business.city, business.state, business.zip].filter(Boolean).join(', ');
// JSON-LD LocalBusiness — Google search rich-results + voice-search surface.
// We always emit type=LocalBusiness rather than mapping to subtypes (Restaurant,
// BeautySalon, etc.) because subtype mapping requires careful per-vertical
// schema.org alignment; LocalBusiness is the safe parent for all 8 verticals.
var _publicUrl = (typeof publicUrl !== 'undefined' && publicUrl) ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com';
var _ldBusiness = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'@id': _publicUrl + '/business/' + business.slug,
name: business.business_name,
url: _publicUrl + '/business/' + business.slug
};
if (business.headline) _ldBusiness.description = business.headline;
if (business.phone) _ldBusiness.telephone = business.phone;
if (business.email) _ldBusiness.email = business.email;
if (_site) _ldBusiness.sameAs = [_site];
if (business.photo_path) _ldBusiness.image = _publicUrl + business.photo_path;
if (business.street || business.city) {
_ldBusiness.address = {
'@type': 'PostalAddress',
streetAddress: business.street || undefined,
addressLocality: business.city || undefined,
addressRegion: business.state || 'CA',
postalCode: business.zip || undefined,
addressCountry: 'US'
};
}
if (business.latitude && business.longitude) {
_ldBusiness.geo = { '@type': 'GeoCoordinates', latitude: business.latitude, longitude: business.longitude };
}
%>
<script type="application/ld+json"><%- JSON.stringify(_ldBusiness) %></script>
<%
// BreadcrumbList — paired with the visible <nav> below. Google uses this
// to render rich-result breadcrumb chips above the result snippet.
var _crumbs = [
{ name: 'Home', url: _publicUrl + '/' },
{ name: 'Find a business', url: _publicUrl + '/find' }
];
if (verticalMeta) _crumbs.push({ name: verticalMeta.label, url: _publicUrl + '/find?vertical=' + business.vertical });
if (business.city) _crumbs.push({ name: business.city, url: _publicUrl + '/find?city=' + encodeURIComponent(business.city) + (business.vertical ? '&vertical=' + business.vertical : '') });
_crumbs.push({ name: business.business_name, url: _publicUrl + '/business/' + business.slug });
var _ldCrumbs = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: _crumbs.map(function(c, i){
return { '@type': 'ListItem', position: i + 1, name: c.name, item: c.url };
})
};
%>
<script type="application/ld+json"><%- JSON.stringify(_ldCrumbs) %></script>
<% if (business.claim_status === 'unclaimed') { %>
<div role="note" style="background:var(--bg);border-bottom:1px solid var(--border);padding:10px 28px;font-size:13px;text-align:center;color:var(--fg-muted)">
<span style="display:inline-block;vertical-align:middle">📋 This listing is unclaimed — sourced from Ventura corridor public records.</span>
<a href="#claim" style="display:inline-block;vertical-align:middle;margin-left:6px;font-weight:600;color:var(--brass)">Are you the owner? Claim it free →</a>
</div>
<% } %>
<nav class="breadcrumb" aria-label="Breadcrumb" style="padding:16px 28px 0;max-width:1100px;margin:0 auto;font-size:12px;color:var(--fg-muted);letter-spacing:0.04em">
<% _crumbs.forEach(function(c, i){
var isLast = i === _crumbs.length - 1;
%>
<% if (isLast) { %>
<span style="color:var(--fg)"><%= c.name %></span>
<% } else { %>
<a href="<%= c.url.replace(_publicUrl, '') %>" style="border:none;color:var(--fg-muted)"><%= c.name %></a>
<span style="margin:0 6px;opacity:0.5">›</span>
<% } %>
<% }); %>
</nav>
<% if (business.photo_path) { %>
<div class="vertical-hero vertical-hero-photo">
<img src="<%= business.photo_path %>" alt="<%= business.business_name %>" loading="lazy" decoding="async">
</div>
<% } else { %>
<div class="vertical-hero vertical-hero-<%= business.vertical %>" aria-hidden="true">
<div class="vertical-hero-glyph vertical-hero-glyph-svg">
<%- include('../partials/vertical-glyph', { verticalKey: business.vertical }) %>
</div>
</div>
<% } %>
<section class="profile-hero">
<p class="kicker"><%= verticalMeta ? verticalMeta.icon + ' ' + verticalMeta.label : business.vertical %></p>
<h1><%= business.business_name %></h1>
<p class="biz-meta"><%= [business.neighborhood, business.city, business.state].filter(Boolean).join(' · ') %></p>
<% if (business.headline) { %><p class="lede" style="margin-top:14px"><%= business.headline %></p><% } %>
<div class="profile-actions">
<a href="#contact" class="btn btn-primary btn-lg">Send a message</a>
<% if (_site) { %><a href="<%= _site %>" target="_blank" rel="noopener nofollow" class="btn btn-ghost">Visit website ↗</a><% } %>
<% if (business.phone) { %><a href="tel:<%= business.phone %>" class="btn btn-ghost">Call <%= business.phone %></a><% } %>
<button type="button" class="btn btn-ghost" data-share-url="<%= _publicUrl %>/business/<%= business.slug %>" data-share-title="<%= business.business_name %>">Share <span data-share-status></span></button>
</div>
</section>
<script>
(function(){
var slug = '<%= business.slug %>';
function trackShare() {
// Fire-and-forget — never block the UX on the counter ping.
try { fetch('/api/businesses/' + encodeURIComponent(slug) + '/share-track', { method: 'POST' }); } catch(e) {}
}
document.querySelectorAll('[data-share-url]').forEach(function(btn){
btn.addEventListener('click', async function(){
var url = btn.getAttribute('data-share-url');
var title = btn.getAttribute('data-share-title');
var status = btn.querySelector('[data-share-status]');
// Web Share API on mobile/Safari, clipboard fallback elsewhere.
if (navigator.share) {
try { await navigator.share({ title: title, url: url }); trackShare(); return; }
catch(e){ /* user cancelled — fall through to clipboard */ }
}
try {
await navigator.clipboard.writeText(url);
trackShare();
if (status) { status.textContent = '· copied'; setTimeout(function(){ status.textContent=''; }, 2000); }
} catch(e) {
if (status) { status.textContent = '· press ⌘C'; }
}
});
});
})();
</script>
<section class="profile-body">
<div class="profile-bio">
<% if (business.description) { %>
<h2>About</h2>
<p><%= business.description %></p>
<% } else if (business.claim_status === 'unclaimed') { %>
<h2>About</h2>
<p class="muted">This listing is sourced from Ventura corridor public records and hasn't been claimed by the business yet.
If you're the owner, you can <a href="#claim">claim this page free</a> to add hours, photos, and a real description.
</p>
<% } else { %>
<h2>About</h2>
<p class="muted">This business hasn't added a full description yet. Reach out below — they'll send you details directly.</p>
<% } %>
<h2 id="contact" style="margin-top:32px">Send <%= business.business_name %> a message</h2>
<form class="contact-form" action="/business/<%= business.slug %>/contact" method="post">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<p class="helper">Goes straight to the business — no middleman, no markup. They'll reply to your email.</p>
<div class="row">
<label>Your name<input type="text" name="name" required maxlength="120"></label>
<label>Email<input type="email" name="email" required maxlength="200"></label>
</div>
<div class="row">
<label>Phone (optional)<input type="tel" name="phone" maxlength="32"></label>
<label>ZIP (optional)<input type="text" name="zip" maxlength="10"></label>
</div>
<label>Message<textarea name="message" rows="4" maxlength="1500" placeholder="What are you looking for? Hours, services, pricing — they'll know best."></textarea></label>
<button type="submit" class="btn btn-primary btn-lg">Send message →</button>
<p class="muted" style="font-size:11px;margin-top:10px;line-height:1.5">By sending, you agree your contact info will be shared with <%= business.business_name %> only. Ventura Claw does not arrange transactions, set prices, or warrant goods or services.</p>
</form>
</div>
<aside class="profile-side">
<% if (typeof showActivity !== 'undefined' && showActivity) { %>
<div class="side-card" style="background:var(--bg);border-color:var(--brass)">
<% if (activity.msgs_30d >= 3) { %>
<div style="text-align:center;<%= activity.views_30d >= 25 ? 'border-bottom:1px dotted var(--border);padding-bottom:12px;margin-bottom:12px' : '' %>">
<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:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">messages routed · last 30 days</p>
</div>
<% } %>
<% if (activity.views_30d >= 25) { %>
<div style="text-align:center;<%= (business.share_count || 0) >= 10 ? 'border-bottom:1px dotted var(--border);padding-bottom:12px;margin-bottom:12px' : '' %>">
<p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= activity.views_30d %></p>
<p style="margin:6px 0 0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">profile views · last 30 days</p>
</div>
<% } %>
<% if ((business.share_count || 0) >= 10) { %>
<div style="text-align:center">
<p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= business.share_count %></p>
<p style="margin:6px 0 0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">times shared</p>
</div>
<% } %>
</div>
<% } %>
<div class="side-card">
<h3>Quick info</h3>
<% if (_addressOneline) { %><div class="info-row"><strong>Address</strong><span><%= _addressOneline %></span></div><% } %>
<% if (business.phone) { %><div class="info-row"><strong>Phone</strong><span><a href="tel:<%= business.phone %>"><%= business.phone %></a></span></div><% } %>
<% if (business.email) { %><div class="info-row"><strong>Email</strong><span><a href="mailto:<%= business.email %>"><%= business.email %></a></span></div><% } %>
<% if (_site) { %><div class="info-row"><strong>Web</strong><span><a href="<%= _site %>" target="_blank" rel="noopener nofollow"><%= _site.replace(/^https?:\/\//, '').replace(/\/+$/,'') %></a></span></div><% } %>
<% if (business.instagram_handle) { %><div class="info-row"><strong>Instagram</strong><span><a href="https://instagram.com/<%= encodeURIComponent(business.instagram_handle) %>" target="_blank" rel="noopener nofollow">@<%= business.instagram_handle %></a></span></div><% } %>
<div class="info-row"><strong>Status</strong><span><%= business.claim_status === 'unclaimed' ? 'Public listing — not yet claimed by the business' : 'Claimed by the business' %></span></div>
</div>
<% if (business.claim_status === 'unclaimed') { %>
<div class="side-card" style="background:var(--bg);border-color:var(--brass)" id="claim">
<h3 style="color:var(--brass)">Own this business?</h3>
<p style="font-size:13px;line-height:1.5;margin:0 0 10px">Claim your listing for free — control your description, photos, and how customers reach you. We'll email a confirmation link to verify ownership.</p>
<form action="/business/<%= business.slug %>/claim" method="post" style="display:flex;gap:6px;flex-wrap:wrap;margin:8px 0 0">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="email" name="email" required placeholder="you@<%= business.slug.split('-')[0] %>.com" style="flex:1;min-width:0">
<button type="submit" class="btn btn-primary btn-sm">Claim</button>
</form>
</div>
<% } else if (currentBusiness && currentBusiness.id === business.id) { %>
<div class="side-card" style="background:#dcfce7;border-color:#16a34a;color:#166534">
<h3 style="color:#166534">You own this listing</h3>
<p style="font-size:13px;line-height:1.5;margin:0 0 10px">Open your <a href="/admin">dashboard</a> to edit details or check leads.</p>
</div>
<% } %>
</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">
<%- include('../partials/vertical-glyph', { verticalKey: n.vertical }) %>
</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') %>