← back to Ventura Claw Leads
yolo tick 23: og:image + Twitter Card image + 'Share this listing' button
c73de6a104816ca25c326daf408e021a0251b280 · 2026-05-07 00:26:48 -0700 · Steve Abrams
OPEN GRAPH / TWITTER CARD
- views/partials/head.ejs: optional ogImage param. When passed (and only
then), emits og:image + og:image:width + og:image:height + twitter:image
+ twitter:card='summary_large_image' + twitter:title + twitter:description.
When ogImage absent, defaults to twitter:card='summary' (no large image).
- views/public/business.ejs: passes ogImage=publicUrl+photo_path when the
business has a cover photo. Same path also added to LocalBusiness JSON-LD
image field for Google.
- Net: a business with a photo now gets a properly attributed
social-share preview card on iMessage / Slack / Twitter / LinkedIn /
Facebook / Discord. Without a photo, falls back to the summary card.
SHARE THIS LISTING
- views/public/business.ejs: 'Share' button next to 'Send a message' /
'Visit website' / 'Call'. Uses navigator.share when available (Mobile
Safari, Chrome on Android), falls back to navigator.clipboard.writeText
with a '· copied' status flash.
- Pure progressive enhancement — works without JS for users with
copy-link muscle memory.
46/46 tests still pass. Verified on prod: og:image renders when photo set,
summary card otherwise; Share button present on every profile.
Files touched
M views/partials/head.ejsM views/public/business.ejs
Diff
commit c73de6a104816ca25c326daf408e021a0251b280
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 7 00:26:48 2026 -0700
yolo tick 23: og:image + Twitter Card image + 'Share this listing' button
OPEN GRAPH / TWITTER CARD
- views/partials/head.ejs: optional ogImage param. When passed (and only
then), emits og:image + og:image:width + og:image:height + twitter:image
+ twitter:card='summary_large_image' + twitter:title + twitter:description.
When ogImage absent, defaults to twitter:card='summary' (no large image).
- views/public/business.ejs: passes ogImage=publicUrl+photo_path when the
business has a cover photo. Same path also added to LocalBusiness JSON-LD
image field for Google.
- Net: a business with a photo now gets a properly attributed
social-share preview card on iMessage / Slack / Twitter / LinkedIn /
Facebook / Discord. Without a photo, falls back to the summary card.
SHARE THIS LISTING
- views/public/business.ejs: 'Share' button next to 'Send a message' /
'Visit website' / 'Call'. Uses navigator.share when available (Mobile
Safari, Chrome on Android), falls back to navigator.clipboard.writeText
with a '· copied' status flash.
- Pure progressive enhancement — works without JS for users with
copy-link muscle memory.
46/46 tests still pass. Verified on prod: og:image renders when photo set,
summary card otherwise; Share button present on every profile.
---
views/partials/head.ejs | 11 +++++++++++
views/public/business.ejs | 31 ++++++++++++++++++++++++++++++-
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/views/partials/head.ejs b/views/partials/head.ejs
index 6182e39..d9bf589 100644
--- a/views/partials/head.ejs
+++ b/views/partials/head.ejs
@@ -18,6 +18,17 @@
<meta property="og:site_name" content="Ventura Claw">
<meta property="og:title" content="<%= typeof title !== 'undefined' ? title : 'Ventura Claw' %>">
<meta property="og:description" content="<%= _metaDesc %>">
+ <% if (typeof ogImage !== 'undefined' && ogImage) { %>
+ <meta property="og:image" content="<%= ogImage %>">
+ <meta property="og:image:width" content="1200">
+ <meta property="og:image:height" content="630">
+ <meta name="twitter:card" content="summary_large_image">
+ <meta name="twitter:image" content="<%= ogImage %>">
+ <meta name="twitter:title" content="<%= typeof title !== 'undefined' ? title : 'Ventura Claw' %>">
+ <meta name="twitter:description" content="<%= _metaDesc %>">
+ <% } else { %>
+ <meta name="twitter:card" content="summary">
+ <% } %>
<meta name="theme-color" content="#0e0e0e">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
diff --git a/views/public/business.ejs b/views/public/business.ejs
index b41128a..3491151 100644
--- a/views/public/business.ejs
+++ b/views/public/business.ejs
@@ -1,4 +1,9 @@
-<%- include('../partials/head', { title }) %>
+<%- include('../partials/head', {
+ title,
+ ogImage: business.photo_path
+ ? ((typeof publicUrl !== 'undefined' && publicUrl ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com') + business.photo_path)
+ : null
+}) %>
<%- include('../partials/header') %>
<%
@@ -26,6 +31,7 @@
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',
@@ -94,8 +100,31 @@
<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(){
+ 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 }); return; }
+ catch(e){ /* user cancelled — fall through to clipboard */ }
+ }
+ try {
+ await navigator.clipboard.writeText(url);
+ 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">
← 77c0ec3 yolo tick 22: /admin/leads pagination — 50/page with prev/ne
·
back to Ventura Claw Leads
·
yolo tick 24: home featured grid — photo-priority within tie e1590f8 →