← back to Prestige Car Wash
Mobile: persistent Book + click-to-call action bar on customer pages
076013950e162cecd798fbc298bfd9f113d93359 · 2026-07-26 20:34:40 -0700 · Steve Abrams
DTD unanimous (5/5) A. Server-injected via pageHtml, CSS-only <=640px, no JS.
tel: from real places().phone (single source, no fabrication). Contrarian FIX-FIRST:
Book button is page-aware — scrolls to #f on /contact instead of self-linking to top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 076013950e162cecd798fbc298bfd9f113d93359
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 20:34:40 2026 -0700
Mobile: persistent Book + click-to-call action bar on customer pages
DTD unanimous (5/5) A. Server-injected via pageHtml, CSS-only <=640px, no JS.
tel: from real places().phone (single source, no fabrication). Contrarian FIX-FIRST:
Book button is page-aware — scrolls to #f on /contact instead of self-linking to top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
server.js | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/server.js b/server.js
index 578c94c..4ffc727 100644
--- a/server.js
+++ b/server.js
@@ -467,6 +467,30 @@ function faqs() {
a: 'Send a request from the Book / Contact page with your vehicle and preferred time. We follow up to confirm — usually within a few hours during business hours. No pressure, no upsell.' }
];
}
+// Persistent mobile action bar — injected into customer pages so the primary CTA (Book)
+// and a click-to-call are always one thumb-tap away on phones (where most Valley car-wash
+// traffic is). CSS-only visibility (<=640px), no JS. The tel: number comes from the REAL
+// places().phone (single source, no fabrication); the Call button is omitted if no phone.
+function mobileBar(pagePath) {
+ const p = places() || {};
+ let d = String(p.phone || '').replace(/\D/g, '');
+ if (d.length === 11 && d[0] === '1') d = d.slice(1);
+ const tel = d.length === 10 ? '+1' + d : (d ? '+' + d : '');
+ const call = tel ? `<a class="mbar-btn call" href="tel:${tel}" aria-label="Call ${(p.name || 'Prestige Car Wash').replace(/"/g, '')}">📞 Call</a>` : '';
+ // On the contact page the form IS the booking action, so scroll to it (#f) rather than
+ // self-linking to the top of the page and losing any half-filled form state.
+ const bookHref = pagePath === '/contact' ? '#f' : '/contact';
+ return `<style>
+.mbar{display:none}
+@media(max-width:640px){
+ body{padding-bottom:74px}
+ .mbar{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:80;gap:10px;padding:10px 12px calc(10px + env(safe-area-inset-bottom,0px));background:rgba(11,15,22,.93);backdrop-filter:blur(10px);border-top:1px solid var(--line)}
+ .mbar-btn{flex:1;text-align:center;padding:13px 10px;border-radius:12px;font-weight:800;font-size:15px;text-decoration:none;display:flex;align-items:center;justify-content:center;gap:6px}
+ .mbar .call{background:transparent;color:var(--ink);border:1px solid var(--line)}
+ .mbar .book{background:linear-gradient(135deg,var(--brand),var(--brand2));color:#001018}
+}</style>
+<div class="mbar" aria-label="Quick actions">${call}<a class="mbar-btn book" href="${bookHref}">Book a Wash →</a></div>`;
+}
function buildFaqLd() {
const list = faqs();
if (!list.length) return null;
@@ -539,6 +563,8 @@ function pageHtml(file, pagePath, ld) {
if (html.includes('id="faqList"')) {
html = html.replace(/(<div id="faqList"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + faqHtml() + close);
}
+ // Persistent mobile Book/Call bar on every customer page (guard against double-inject).
+ if (!/class="mbar"/.test(html)) html = html.replace('</body>', mobileBar(pagePath) + '\n</body>');
let inject = buildSocialTags(html, pagePath);
// ld may be a single JSON-LD object OR an array of them (e.g. AutoWash + FAQPage on the
// homepage). Emit one <script> per object; escape "<" so the JSON can't break out of the tag.
← cc20300 FAQ: booking-objection accordion + FAQPage JSON-LD, single-s
·
back to Prestige Car Wash
·
Services: per-service 'Book this' deep-links pre-select the 9d17487 →