[object Object]

← back to Big Red

feat(big-red): embeddable widget.js — floating launcher for any host page

83c3c5227f7c13cc9d12985848bdbc352dd3a3d1 · 2026-05-11 23:47:26 -0700 · Steve Abrams

Files touched

Diff

commit 83c3c5227f7c13cc9d12985848bdbc352dd3a3d1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 11 23:47:26 2026 -0700

    feat(big-red): embeddable widget.js — floating launcher for any host page
---
 public/widget.js | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 200 insertions(+)

diff --git a/public/widget.js b/public/widget.js
new file mode 100644
index 0000000..34136d0
--- /dev/null
+++ b/public/widget.js
@@ -0,0 +1,200 @@
+// Big Red embed widget — drops a floating launcher into the lower-left of the
+// host page. Click → expands to an iframe of the Big Red chat UI.
+//
+// Usage on a host page:
+//   <script src="https://<big-red-host>/widget.js"
+//           data-host="https://<big-red-host>"
+//           defer></script>
+//
+// Or, if same-origin / dev:
+//   <script src="/widget.js" data-host="http://localhost:9935" defer></script>
+//
+// The script reads its own `src` to derive the default host if data-host is
+// omitted. Idempotent — safe to load twice; only one launcher is mounted.
+(function () {
+  if (window.__bigRedMounted) return;
+  window.__bigRedMounted = true;
+
+  const me = document.currentScript;
+  const host =
+    (me && me.dataset && me.dataset.host) ||
+    (me && me.src ? new URL(me.src, location.href).origin : '') ||
+    'http://localhost:9935';
+
+  const css = `
+    .bigred-launcher {
+      position: fixed;
+      left: 22px;
+      bottom: 22px;
+      z-index: 99990;
+      width: 60px;
+      height: 60px;
+      border-radius: 50%;
+      background:
+        radial-gradient(circle at 35% 30%, #ff5560 0%, #d8323a 45%, #8b1419 100%);
+      box-shadow:
+        0 0 0 2px rgba(255,255,255,.08),
+        0 8px 28px rgba(216,50,58,.55),
+        0 2px 10px rgba(0,0,0,.4);
+      border: 0;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", sans-serif;
+      font-weight: 900;
+      letter-spacing: 0.06em;
+      color: #fff;
+      font-size: 12px;
+      text-shadow: 0 0 8px rgba(0,0,0,.5);
+      transition: transform .18s cubic-bezier(.2,.7,.2,1), box-shadow .18s, opacity .18s;
+      animation: bigred-pulse 2.6s ease-in-out infinite;
+    }
+    .bigred-launcher:hover {
+      transform: scale(1.08);
+      box-shadow:
+        0 0 0 3px rgba(255,255,255,.14),
+        0 12px 40px rgba(216,50,58,.7),
+        0 2px 12px rgba(0,0,0,.5);
+    }
+    .bigred-launcher:active { transform: scale(0.96); }
+    .bigred-launcher.open { opacity: 0; pointer-events: none; }
+    .bigred-launcher .label {
+      pointer-events: none;
+      letter-spacing: 0.14em;
+    }
+    @keyframes bigred-pulse {
+      0%, 100% { box-shadow:
+        0 0 0 2px rgba(255,255,255,.08),
+        0 8px 28px rgba(216,50,58,.55),
+        0 2px 10px rgba(0,0,0,.4); }
+      50% { box-shadow:
+        0 0 0 2px rgba(255,255,255,.14),
+        0 8px 38px rgba(255,85,96,.78),
+        0 2px 14px rgba(0,0,0,.45); }
+    }
+    .bigred-panel {
+      position: fixed;
+      left: 22px;
+      bottom: 22px;
+      z-index: 99991;
+      width: min(420px, calc(100vw - 44px));
+      height: min(640px, calc(100vh - 44px));
+      background: #1a0506;
+      border: 1px solid #8b1419;
+      border-radius: 18px;
+      box-shadow:
+        0 0 0 1px rgba(255,85,96,.18),
+        0 24px 80px rgba(0,0,0,.55),
+        0 8px 24px rgba(216,50,58,.35);
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+      transform: translateY(20px) scale(.96);
+      opacity: 0;
+      pointer-events: none;
+      transition: transform .22s cubic-bezier(.2,.7,.2,1), opacity .18s;
+    }
+    .bigred-panel.open {
+      transform: translateY(0) scale(1);
+      opacity: 1;
+      pointer-events: auto;
+    }
+    .bigred-bar {
+      height: 38px;
+      flex: 0 0 38px;
+      background: linear-gradient(180deg, #2a0506 0%, #1a0506 100%);
+      border-bottom: 1px solid #44090b;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 12px 0 16px;
+      color: #ff5560;
+      font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", sans-serif;
+      font-size: 11px;
+      font-weight: 900;
+      letter-spacing: 0.18em;
+      text-shadow: 0 0 12px rgba(255,85,96,.5);
+    }
+    .bigred-bar .close {
+      background: transparent;
+      border: 0;
+      color: #ff5560;
+      cursor: pointer;
+      width: 26px;
+      height: 26px;
+      border-radius: 6px;
+      font-size: 18px;
+      line-height: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: background .12s;
+    }
+    .bigred-bar .close:hover { background: rgba(255,85,96,.16); }
+    .bigred-frame {
+      flex: 1;
+      width: 100%;
+      border: 0;
+      background: #1a0506;
+    }
+    @media (max-width: 640px) {
+      .bigred-launcher { left: 16px; bottom: 16px; width: 52px; height: 52px; font-size: 10px; }
+      .bigred-panel { left: 8px; right: 8px; bottom: 8px; width: auto; }
+    }
+  `;
+
+  const style = document.createElement('style');
+  style.id = 'bigred-widget-style';
+  style.textContent = css;
+  document.head.appendChild(style);
+
+  const launcher = document.createElement('button');
+  launcher.type = 'button';
+  launcher.className = 'bigred-launcher';
+  launcher.setAttribute('aria-label', 'Open Big Red chat');
+  launcher.innerHTML = '<span class="label">BIG<br>RED</span>';
+
+  const panel = document.createElement('div');
+  panel.className = 'bigred-panel';
+  panel.setAttribute('role', 'dialog');
+  panel.setAttribute('aria-label', 'Big Red chat');
+  panel.innerHTML = `
+    <div class="bigred-bar">
+      <span>BIG RED</span>
+      <button class="close" aria-label="Close">×</button>
+    </div>
+    <iframe class="bigred-frame" title="Big Red chat"
+            allow="microphone; camera; clipboard-read; clipboard-write"
+            src="about:blank"
+            loading="lazy"></iframe>
+  `;
+
+  document.body.appendChild(launcher);
+  document.body.appendChild(panel);
+
+  const iframe = panel.querySelector('.bigred-frame');
+  const closeBtn = panel.querySelector('.close');
+
+  function open() {
+    if (iframe.src === 'about:blank' || !iframe.src.startsWith(host)) {
+      iframe.src = host + '/?embed=1';
+    }
+    launcher.classList.add('open');
+    panel.classList.add('open');
+  }
+  function close() {
+    launcher.classList.remove('open');
+    panel.classList.remove('open');
+  }
+  launcher.addEventListener('click', open);
+  closeBtn.addEventListener('click', close);
+
+  // Esc closes the panel
+  document.addEventListener('keydown', (e) => {
+    if (e.key === 'Escape' && panel.classList.contains('open')) close();
+  });
+
+  // Expose a tiny API so host pages can toggle programmatically if they want
+  window.BigRed = { open, close, host };
+})();

← ed71399 initial scaffold — Big Red avatar app (chat + phone-cam anal  ·  back to Big Red  ·  feat(widget): avatar launcher with red->green live state on 09a1667 →