PageSourceSearch

https://cheeer.com/widgets/cheeer-agent-widget.js

js cheeer.com collected 2026-10-06 10:56:32 UTC 7,539 bytes, 265 lines download raw bytes

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.