1/* Cheeer Custom Agent Widget v1.1 2 Scalable setup: 3 - Generic widget engine: /widgets/cheeer-agent-widget.js 4 - Per-agent assets: /{agent-slug}/widget/mascot.png and /{agent-slug}/widget/outer-tire.png 5 6 Customer embed example: 7 <script 8 src="https://cheeer.com/widgets/cheeer-agent-widget.js" 9 data-agent="dunlop" 10 data-chatbase-id="M8ugv8LjlazeSgpcAs75f" 11 data-position="bottom-right" 12 data-size="118" 13 defer></script> 14*/ 15 16(function () { 17 "use strict"; 18 19 var currentScript = document.currentScript || (function () { 20 var scripts = document.getElementsByTagName("script"); 21 return scripts[scripts.length - 1]; 22 })(); 23 24 var dataset = currentScript ? currentScript.dataset : {}; 25 26 var CHATBASE_ID = dataset.chatbaseId || dataset.agentId || ""; 27 var CHATBASE_DOMAIN = dataset.chatbaseDomain || "www.chatbase.co"; 28 var AGENT_SLUG = dataset.agent || dataset.agentSlug || ""; 29 30 var scriptUrl = currentScript && currentScript.src ? new URL(currentScript.src) : null; 31 var cheeerOrigin = scriptUrl ? scriptUrl.origin : "https://cheeer.com"; 32 33 function ensureTrailingSlash(url) { 34 return url.endsWith("/") ? url : url + "/"; 35 } 36 37 var ASSET_BASE = dataset.assetBase 38 ? ensureTrailingSlash(dataset.assetBase) 39 : AGENT_SLUG 40 ? cheeerOrigin + "/" + AGENT_SLUG + "/widget/" 41 : scriptUrl 42 ? new URL("./", scriptUrl).href 43 : "https://cheeer.com/widgets/"; 44 45 var MASCOT_URL = dataset.mascotUrl || (ASSET_BASE + "mascot.png"); 46 var TIRE_URL = dataset.tireUrl || (ASSET_BASE + "outer-tire.png"); 47 48 var SIZE = parseInt(dataset.size || "118", 10); 49 var OFFSET = parseInt(dataset.offset || "24", 10); 50 var POSITION = dataset.position || "bottom-right"; 51 var Z_INDEX = parseInt(dataset.zIndex || "2147482500", 10); 52 53 var STYLE_ID = "cheeer-agent-widget-styles"; 54 var BUTTON_ID = "cheeer-agent-widget-launcher"; 55 56 if (!CHATBASE_ID) { 57 console.error("[Cheeer Agent Widget] Missing data-chatbase-id."); 58 return; 59 } 60 61 function installChatbase() { 62 if (!window.chatbase || window.chatbase("getState") !== "initialized") { 63 window.chatbase = (...args) => { 64 if (!window.chatbase.q) { 65 window.chatbase.q = []; 66 } 67 window.chatbase.q.push(args); 68 }; 69 70 window.chatbase = new Proxy(window.chatbase, { 71 get(target, prop) { 72 if (prop === "q") { 73 return target.q; 74 } 75 return (...args) => target(prop, ...args); 76 } 77 }); 78 } 79 80 if (!document.getElementById(CHATBASE_ID)) { 81 var script = document.createElement("script"); 82 script.src = "https://www.chatbase.co/embed.min.js"; 83 script.id = CHATBASE_ID; 84 script.domain = CHATBASE_DOMAIN; 85 document.body.appendChild(script); 86 } 87 } 88 89 function injectStyles() { 90 if (document.getElementById(STYLE_ID)) return; 91 92 var style = document.createElement("style"); 93 style.id = STYLE_ID; 94 style.textContent = ` 95 #chatbase-bubble-button, 96 iframe#chatbase-bubble-button, 97 iframe[id*="chatbase"][id*="bubble-button"] { 98 display: none !important; 99 visibility: hidden !important; 100 pointer-events: none !important; 101 } 102 103 #${BUTTON_ID} { 104 --cheeer-size: ${SIZE}px; 105 --cheeer-offset: ${OFFSET}px; 106 position: fixed; 107 width: var(--cheeer-size); 108 height: var(--cheeer-size); 109 border: 0; 110 padding: 0; 111 margin: 0; 112 background: transparent; 113 cursor: pointer; 114 z-index: ${Z_INDEX}; 115 -webkit-tap-highlight-color: transparent; 116 filter: drop-shadow(0 10px 18px rgba(0,0,0,0.28)); 117 } 118 119 #${BUTTON_ID}.cheeer-bottom-right { 120 right: calc(var(--cheeer-offset) + env(safe-area-inset-right, 0px)); 121 bottom: calc(var(--cheeer-offset) + env(safe-area-inset-bottom, 0px)); 122 } 123 124 #${BUTTON_ID}.cheeer-bottom-left { 125 left: calc(var(--cheeer-offset) + env(safe-area-inset-left, 0px)); 126 bottom: calc(var(--cheeer-offset) + env(safe-area-inset-bottom, 0px)); 127 } 128 129 #${BUTTON_ID} .cheeer-tire-wrap, 130 #${BUTTON_ID} .cheeer-mascot-wrap { 131 position: absolute; 132 inset: 0; 133 display: block; 134 } 135 136 #${BUTTON_ID} .cheeer-tire { 137 position: absolute; 138 inset: 0; 139 width: 100%; 140 height: 100%; 141 object-fit: contain; 142 transform-origin: 50% 50%; 143 animation: cheeerTireSpin 6.8s infinite; 144 will-change: transform; 145 } 146 147 #${BUTTON_ID} .cheeer-mascot-wrap { 148 display: flex; 149 align-items: center; 150 justify-content: center; 151 pointer-events: none; 152 } 153 154 #${BUTTON_ID} .cheeer-mascot { 155 width: 72%; 156 height: 72%; 157 object-fit: contain; 158 transform-origin: 50% 62%; 159 animation: cheeerMascotFriendly 6.8s infinite; 160 will-change: transform; 161 } 162 163 #${BUTTON_ID}:hover .cheeer-mascot { 164 transform: scale(1.04); 165 } 166 167 #${BUTTON_ID}:focus-visible { 168 outline: 3px solid #FFD400; 169 outline-offset: 5px; 170 border-radius: 50%; 171 } 172 173 @keyframes cheeerTireSpin { 174 0%, 60% { 175 transform: rotate(0deg); 176 } 177 68% { 178 transform: rotate(28deg); 179 } 180 81% { 181 transform: rotate(318deg); 182 } 183 90%, 100% { 184 transform: rotate(360deg); 185 } 186 } 187 188 @keyframes cheeerMascotFriendly { 189 0%, 58%, 100% { 190 transform: translateY(0) scale(1); 191 } 192 66% { 193 transform: translateY(-2px) rotate(-2deg) scale(1.02); 194 } 195 76% { 196 transform: translateY(0) rotate(2deg) scale(1.025); 197 } 198 86% { 199 transform: translateY(-1px) rotate(0deg) scale(1.01); 200 } 201 } 202 203 @media (max-width: 640px) { 204 #${BUTTON_ID} { 205 --cheeer-size: ${Math.max(94, Math.round(SIZE * 0.84))}px; 206 --cheeer-offset: 18px; 207 } 208 } 209 210 @media (prefers-reduced-motion: reduce) { 211 #${BUTTON_ID} .cheeer-tire, 212 #${BUTTON_ID} .cheeer-mascot { 213 animation: none !important; 214 } 215 } 216 `; 217 document.head.appendChild(style); 218 } 219 220 function createLauncher() { 221 if (document.getElementById(BUTTON_ID)) return; 222 223 var button = document.createElement("button"); 224 button.id = BUTTON_ID; 225 button.type = "button"; 226 button.className = POSITION === "bottom-left" ? "cheeer-bottom-left" : "cheeer-bottom-right"; 227 button.setAttribute("aria-label", dataset.label || "Open chat"); 228 229 button.innerHTML = 230 '<span class="cheeer-tire-wrap" aria-hidden="true">' + 231 '<img class="cheeer-tire" src="' + TIRE_URL + '" alt="" draggable="false">' + 232 '</span>' + 233 '<span class="cheeer-mascot-wrap" aria-hidden="true">' + 234 '<img class="cheeer-mascot" src="' + MASCOT_URL + '" alt="" draggable="false">' + 235 '</span>'; 236 237 button.addEventListener("click", function () { 238 function openChat() { 239 if (window.chatbase && typeof window.chatbase.open === "function") { 240 window.chatbase.open(); 241 return true; 242 } 243 return false;
244 } 245 246 if (!openChat()) { 247 setTimeout(openChat, 400); 248 } 249 }); 250 251 document.body.appendChild(button); 252 } 253 254 function init() { 255 installChatbase(); 256 injectStyles(); 257 createLauncher(); 258 } 259 260 if (document.readyState === "loading") { 261 document.addEventListener("DOMContentLoaded", init); 262 } else { 263 init(); 264 } 265})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.