[object Object]

← back to Robet Site

auto-save: 2026-06-30T00:26:45 (2 files) — public/app.js public/styles.css

6e8cd060304ff14c7c44272288ff25d55f8984b1 · 2026-06-30 00:26:49 -0700 · Steve Abrams

Files touched

Diff

commit 6e8cd060304ff14c7c44272288ff25d55f8984b1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 30 00:26:49 2026 -0700

    auto-save: 2026-06-30T00:26:45 (2 files) — public/app.js public/styles.css
---
 public/app.js     | 42 ++++++++++++++++++++++++++++--------------
 public/styles.css | 10 ++++++++++
 2 files changed, 38 insertions(+), 14 deletions(-)

diff --git a/public/app.js b/public/app.js
index 05855b4..2cf2ede 100644
--- a/public/app.js
+++ b/public/app.js
@@ -51,21 +51,35 @@
     }
   });
 
-  // ── Smart Scheduling embed ──
+  // ── Smart Scheduling embed: skeleton → real widget OR fallback (never a perpetual spinner) ──
   const sched = cfg.scheduling || {};
-  const bookLink = $('#bookLink');
-  if (bookLink && sched.bookUrl) bookLink.href = sched.bookUrl;
-  if (sched.embedLoader) {
-    // The venturacorridor embed loader injects the booking widget where invoked.
-    const s = document.createElement('script');
-    s.src = sched.embedLoader;
-    if (sched.owner) s.setAttribute('data-owner', sched.owner);
-    s.async = true;
-    s.onerror = () => {
-      $('#bookFallback') && ($('#bookFallback').innerHTML =
-        `Booking widget unavailable right now — <a class="cta" href="${sched.bookUrl || '#'}" target="_blank" rel="noopener noreferrer">Open booking page →</a>`);
-    };
-    $('#embedBox').appendChild(s);
+  const embedBox = $('#embedBox');
+  const fallbackHTML = () =>
+    `<div class="fallback mono">Booking opens in a new tab — <a class="cta" href="${sched.bookUrl || '#'}" target="_blank" rel="noopener noreferrer">Open booking page →</a></div>`;
+  if (embedBox) {
+    if (!sched.embedLoader) {
+      embedBox.innerHTML = fallbackHTML();
+    } else {
+      // brutalist loading skeleton, shown immediately
+      embedBox.innerHTML = '<div class="embed-skeleton" aria-hidden="true"><span></span><span></span><span></span><span></span></div>';
+      let settled = false;
+      const settle = (mode) => {
+        if (settled) return; settled = true;
+        clearTimeout(timer); obs.disconnect();
+        const sk = embedBox.querySelector('.embed-skeleton'); if (sk) sk.remove();
+        if (mode === 'fallback' && !embedBox.querySelector('iframe')) embedBox.innerHTML = fallbackHTML();
+      };
+      // a rendered booking widget = an injected <iframe>; that's our "it worked" signal
+      const obs = new MutationObserver(() => { if (embedBox.querySelector('iframe')) settle('embed'); });
+      obs.observe(embedBox, { childList: true, subtree: true });
+      const timer = setTimeout(() => settle('fallback'), 7000);  // no widget in 7s → fallback link
+      const s = document.createElement('script');
+      s.src = sched.embedLoader;
+      if (sched.owner) s.setAttribute('data-owner', sched.owner);
+      s.async = true;
+      s.onerror = () => settle('fallback');
+      embedBox.appendChild(s);
+    }
   }
 
   // ── contact form ──
diff --git a/public/styles.css b/public/styles.css
index 7bbeee3..b28d5c7 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -123,6 +123,16 @@ section { padding: 70px 24px; border-bottom: var(--bord); }
 /* booking embed */
 #booking .embed-box { border: var(--bord); background: #fff; padding: 0; min-height: 420px; }
 #booking .fallback { padding: 22px; }
+.embed-skeleton { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; padding: 22px; }
+.embed-skeleton span {
+  height: 72px; border: 4px solid var(--ink);
+  background: repeating-linear-gradient(45deg, #e9e6de, #e9e6de 10px, #dcd9d1 10px, #dcd9d1 20px);
+  animation: embedpulse 1.1s ease-in-out infinite;
+}
+.embed-skeleton span:nth-child(2) { animation-delay: .15s; }
+.embed-skeleton span:nth-child(3) { animation-delay: .3s; }
+.embed-skeleton span:nth-child(4) { animation-delay: .45s; }
+@keyframes embedpulse { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
 
 /* contact */
 .contact-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }

← 967b466 loop ledger: round2 item (4) done + deployed  ·  back to Robet Site  ·  loop ledger: round2 item (6) done + deployed 32a72a3 →