[object Object]

← back to Prestige Car Wash

Booking: honest 'typical busy-ness today' wait indicator (GET /api/wait, LA-tz, qualitative not fabricated minutes) — dtd:A, contrarian-gated

3ccfc3cb0632b716e89483c0b3f0d47c2fe5e96c · 2026-07-26 08:36:43 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 3ccfc3cb0632b716e89483c0b3f0d47c2fe5e96c
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Sun Jul 26 08:36:43 2026 -0700

    Booking: honest 'typical busy-ness today' wait indicator (GET /api/wait, LA-tz, qualitative not fabricated minutes) — dtd:A, contrarian-gated
---
 public/contact.html |  7 +++++++
 public/index.html   |  7 +++++++
 server.js           | 22 ++++++++++++++++++++++
 3 files changed, 36 insertions(+)

diff --git a/public/contact.html b/public/contact.html
index 777abc1..b55d99c 100644
--- a/public/contact.html
+++ b/public/contact.html
@@ -31,6 +31,7 @@
 <section class="section"><div class="wrap">
   <h2>Book your wash</h2>
   <p class="sub">Tell us what you need — we'll follow up to confirm a time, usually within a few hours. No upsell pressure, ever.</p>
+  <div id="waitchip" style="display:none;margin:0 0 18px;padding:8px 15px;border:1px solid var(--line);border-radius:999px;font-size:14px;width:fit-content" title=""></div>
   <div class="ok" id="ok"></div>
   <form id="f">
     <div class="two">
@@ -53,6 +54,12 @@
 <footer class="foot"><div class="wrap">© 2026 Prestige Car Wash · San Fernando Valley, CA · Instagram @prestigecwash</div></footer>
 
 <script>
+// Honest "typical wait" chip — estimate from the demand model, never a live queue count.
+fetch('/api/wait').then(r=>r.json()).then(w=>{
+  const el=document.getElementById('waitchip');
+  el.innerHTML=`⏱️ ${w.day} is typically <b>${w.level}</b> — ${w.phrase} <span style="opacity:.65">· book ahead to skip it</span>`;
+  el.title=w.basis||'';el.style.display='';
+}).catch(()=>{});
 fetch('/api/services?sort=featured').then(r=>r.json()).then(list=>{
   document.getElementById('svcSel').innerHTML += list.map(s=>`<option value="${s.name}">${s.name} — ${s.price_display||('$'+s.price)}</option>`).join('');
 });
diff --git a/public/index.html b/public/index.html
index 2ac8604..d71c226 100644
--- a/public/index.html
+++ b/public/index.html
@@ -29,7 +29,14 @@
     <a class="btn" href="/contact">Book a Wash</a>
     <a class="btn ghost" href="/services">See Services & Pricing</a>
   </div>
+  <div id="waitchip" style="display:none;margin-top:16px;padding:7px 14px;border:1px solid rgba(255,255,255,.35);border-radius:999px;font-size:13.5px;width:fit-content" title=""></div>
 </div></header>
+<script>
+// Honest "typical wait" chip on the hero — estimate from the demand model, not a live count.
+fetch('/api/wait').then(r=>r.json()).then(w=>{const el=document.getElementById('waitchip');
+  el.innerHTML=`⏱️ ${w.day} is typically <b>${w.level.toLowerCase()}</b> — ${w.phrase}. <a href="/contact" style="color:inherit;text-decoration:underline">book ahead</a>`;
+  el.title=w.basis||'';el.style.display='';}).catch(()=>{});
+</script>
 
 <section class="section"><div class="wrap">
   <h2>Why drivers switch to Prestige</h2>
diff --git a/server.js b/server.js
index 8c5983a..3d255f1 100644
--- a/server.js
+++ b/server.js
@@ -312,6 +312,28 @@ app.get('/api/admin/leads', (req, res) => {
   } catch { res.json([]); }
 });
 
+// Public: HONEST "typical wait" estimate derived from the demand model.
+// It is NOT a live queue count — it's a transparent function of best-times.json
+// demand for the current weekday, always labeled as an estimate, so we never
+// display a number we can't verify (the places.json data-honesty rule). dtd:A 2026-07-26.
+app.get('/api/wait', (req, res) => {
+  const days = ((bestTimes() || {}).traffic || {}).by_day || [];
+  // Weekday in the shop's LOCAL tz (LA) — the server clock is UTC, so a Friday-evening
+  // CA visitor must not be shown Saturday's level. (contrarian gate, 2026-07-26)
+  const dayName = new Date().toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles', weekday: 'short' });
+  const today = days.find(d => d.day === dayName);
+  const demand = today ? Math.max(1, Math.min(5, Number(today.demand) || 3)) : 3;
+  // Report the demand LEVEL the data actually measures — a qualitative busy-ness, NOT a
+  // fabricated minute count we can't verify (Steve's data-honesty rule).
+  const LEVELS = { 1: ['Quiet', 'little to no wait'], 2: ['Quiet', 'little to no wait'], 3: ['Moderate', 'a short wait is typical'], 4: ['Busy', 'expect a wait'], 5: ['Very busy', 'expect a longer wait'] };
+  const [level, phrase] = LEVELS[demand];
+  res.json({
+    level, phrase, demand, day: dayName,
+    estimate: true,
+    basis: 'Based on typical demand for this day — not a live queue count'
+  });
+});
+
 // ---- static + clean URLs ---------------------------------------------------
 // Google Place photo proxy — fetches photo media with the server-side key and streams
 // it, so the key never reaches the browser. Cached to avoid re-billing on every view.

← cbf5a8b Growth ideas: backfill conservative SWOT + light costs for m  ·  back to Prestige Car Wash  ·  Membership: 'launching soon' call-out + interest capture via 2f4ad36 →