← 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 →