1/** 2 * Golden Retriever Finance Guide - Above Yoda Icon 3 * Positioned: above Yoda, right 20px with gap 4 * Z-index: 1001 (above Yoda 997) 5 */ 6 7(function() { 8 // Inject styles first 9 const style = document.createElement('style'); 10 style.textContent = ` 11 .gr-container { 12 position: fixed; 13 bottom: 516px; 14 right: 50px; 15 z-index: 1001; 16 pointer-events: auto; 17 user-select: none; 18 } 19 20 .gr-dog { 21 width: 100px; 22 height: 100px; 23 cursor: grab; 24 animation: breathe 3s infinite, sniff 0.8s infinite; 25 pointer-events: all; 26 } 27 28 .gr-dog:active { 29 cursor: grabbing; 30 } 31 32 .gr-bubble { 33 position: absolute; 34 bottom: 110px; 35 left: 50%; 36 transform: translateX(-50%); 37 background: linear-gradient(135deg, #FFB366, #FF9999); 38 color: white; 39 padding: 12px 16px; 40 border-radius: 12px; 41 max-width: 280px; 42 font-size: 14px; 43 z-index: 1001; 44 animation: slideIn 0.4s ease-out; 45 pointer-events: none; 46 box-shadow: 0 6px 20px rgba(0,0,0,0.2); 47 white-space: normal; 48 } 49 50 .gr-menu { 51 position: absolute; 52 bottom: 110px; 53 left: 50%; 54 transform: translateX(-50%); 55 background: white; 56 padding: 16px; 57 border-radius: 12px; 58 box-shadow: 0 8px 30px rgba(0,0,0,0.25); 59 z-index: 1001; 60 min-width: 220px; 61 pointer-events: all; 62 } 63 64 .gr-btn { 65 display: block; 66 background: linear-gradient(135deg, #1e3c72, #2a5298); 67 color: white; 68 border: none; 69 padding: 12px 14px; 70 border-radius: 8px; 71 margin: 6px 0; 72 font-size: 13px; 73 font-weight: 600; 74 cursor: pointer; 75 width: 100%; 76 text-align: left; 77 transition: all 0.3s; 78 pointer-events: auto; 79 } 80 81 .gr-btn:hover { 82 transform: translateX(4px); 83 box-shadow: 0 4px 12px rgba(30, 60, 114, 0.3); 84 } 85 86 .gr-close { 87 position: absolute; 88 top: 8px; 89 right: 8px; 90 width: 28px; 91 height: 28px; 92 border-radius: 50%; 93 background: #FF6B6B; 94 color: white; 95 border: none; 96 cursor: pointer; 97 font-weight: bold; 98 pointer-events: auto; 99 font-size: 16px; 100 } 101
102 .gr-close:hover { 103 background: #FF5252; 104 } 105 106 @keyframes breathe { 107 0%, 100% { transform: scale(1); } 108 50% { transform: scale(1.03); } 109 } 110 111 @keyframes slideIn { 112 0% { opacity: 0; transform: translateY(10px); } 113 100% { opacity: 1; transform: translateY(0); } 114 } 115 116 @keyframes wag { 117 0%, 100% { transform: rotate(0deg); } 118 50% { transform: rotate(-30deg); } 119 } 120 121 @keyframes sniff { 122 0%, 100% { transform: translateY(0); } 123 25% { transform: translateY(-4px); } 124 50% { transform: translateY(4px); } 125 75% { transform: translateY(-2px); } 126 } 127 128 @keyframes pant { 129 0%, 100% { opacity: 1; } 130 50% { opacity: 0.7; } 131 } 132 133 @keyframes coinFall { 134 0% { 135 transform: translateY(0) translateX(0) rotate(0deg) scale(1); 136 opacity: 1; 137 } 138 100% { 139 transform: translateY(300px) translateX(var(--spread-x, 0)) rotate(720deg) scale(0.7); 140 opacity: 0; 141 } 142 } 143 144 @keyframes mobileWalk { 145 0%, 100% { transform: translateX(0) translateY(0); } 146 25% { transform: translateX(20px) translateY(-4px); } 147 50% { transform: translateX(0) translateY(0); } 148 75% { transform: translateX(-20px) translateY(-4px); } 149 } 150 151 .gr-tongue { 152 animation: pant 1.2s infinite; 153 } 154 155 .gr-tail { 156 animation: wag 0.8s infinite; 157 transform-origin: 50% 120px; 158 } 159 160 .gr-dog.sniffing { 161 animation: sniff 0.6s infinite !important; 162 } 163 164 .gr-coin { 165 position: fixed; 166 width: 16px; 167 height: 16px; 168 background: radial-gradient(circle at 30% 30%, #FFD700, #FFA500); 169 border-radius: 50%; 170 border: 1px solid #FF8C00; 171 pointer-events: none; 172 z-index: 1000; 173 box-shadow: inset -2px -2px 3px rgba(0, 0, 0, 0.3), 2px 2px 3px rgba(0, 0, 0, 0.2); 174 } 175 176 /* Mobile Optimization */ 177 @media (max-width: 768px) { 178 .gr-container { 179 bottom: 410px; 180 right: 10px; 181 } 182 183 .gr-dog { 184 width: 75px; 185 height: 75px; 186 } 187 188 .gr-bubble { 189 bottom: 90px; 190 left: 50%; 191 transform: translateX(-50%); 192 max-width: 240px; 193 font-size: 12px; 194 padding: 10px 12px; 195 } 196 197 .gr-menu { 198 bottom: 90px; 199 left: 50%; 200 transform: translateX(-50%); 201 min-width: 200px; 202 padding: 12px; 203 } 204 205 .gr-btn { 206 padding: 10px 12px; 207 margin: 4px 0; 208 font-size: 12px; 209 } 210 } 211 212 @media (max-width: 480px) { 213 .gr-container { 214 bottom: 336px; 215 right: 111px; 216 } 217 } 218 `; 219 document.head.appendChild(style); 220 221 class GoldenRetriever { 222 constructor() { 223 this.messages = ["ð Woof! How I can Help you?", "1ï¸â£ YodaBot for Queries\n2ï¸â£ Booking for Packages\n3ï¸â£ Topmate for Coaching"]; 224 this.isDragging = false; 225 this.dragOffsetX = 0; 226 this.dragOffsetY = 0; 227 this.savedPosition = this.getSavedPosition(); 228 this.init(); 229 } 230 231 getSavedPosition() { 232 try { 233 const saved = localStorage.getItem('grDogPosition'); 234 return saved ? JSON.parse(saved) : null; 235 } catch (e) { 236 return null; 237 } 238 } 239 240 savePosition(bottom, right) { 241 try { 242 localStorage.setItem('grDogPosition', JSON.stringify({ bottom, right })); 243 } catch (e) { 244 // Silently fail if localStorage is not available 245 } 246 } 247 248 init() { 249 const html = ` 250 <div class="gr-container"> 251 <svg class="gr-dog" viewBox="0 0 200 200"><ellipse cx="100" cy="130" rx="45" ry="50" fill="#D4A574"/><ellipse cx="100" cy="110" rx="40" ry="45" fill="#E8C89F"/><circle cx="100" cy="70" r="35" fill="#E8C89F"/><ellipse cx="100" cy="85" rx="20" ry="16" fill="#F0D9B5"/><ellipse cx="100" cy="82" rx="8" ry="7" fill="#4A4A4A"/><circle cx="92" cy="62" r="5" fill="#4A4A4A"/><circle cx="108" cy="62" r="5" fill="#4A4A4A"/><circle cx="93" cy="60" r="2" fill="#fff"/><circle cx="109" cy="60" r="2" fill="#fff"/><ellipse cx="75" cy="50" rx="15" ry="25" fill="#D4A574"/><ellipse cx="125" cy="50" rx="15" ry="25" fill="#D4A574"/><path d="M 95 95 Q 100 100 105 95" stroke="#8B6B47" stroke-width="2" fill="none" stroke-linecap="round"/><ellipse cx="100" cy="105" rx="6" ry="8" fill="#FF6B9D" class="gr-tongue"/><path class="gr-tail" d="M 50 120 Q 35 110 40 90" stroke="#D4A574" stroke-width="12" fill="none" stroke-linecap="round"/></svg> 252 <div id="gr-bubble" class="gr-bubble" style="display:none;"></div> 253 <div id="gr-menu" class="gr-menu" style="display:none;"> 254 <button class="gr-btn" id="gr-chat-btn">ð¬ Click YodaBot for any Query</button> 255 <button class="gr-btn" id="gr-coaching-btn">ð¦ Click Booking for Packages</button> 256 <button class="gr-btn" id="gr-services-btn">ð¯ Click Topmate for Coaching</button> 257 <button class="gr-close" id="gr-close-btn">â</button> 258 </div> 259 </div> 260 `; 261 document.body.insertAdjacentHTML('beforeend', html); 262 263 // Restore saved position 264 const container = document.querySelector('.gr-container'); 265 if (this.savedPosition && container) { 266 container.style.bottom = this.savedPosition.bottom + 'px'; 267 container.style.right = this.savedPosition.right + 'px'; 268 } 269 270 // Set up event listeners with proper scope 271 const dog = document.querySelector('.gr-dog'); 272 if (dog) { 273 dog.addEventListener('mousedown', (e) => this.startDrag(e)); 274 dog.addEventListener('touchstart', (e) => this.startDrag(e)); 275 dog.addEventListener('click', (e) => { 276 // Only open menu if not dragging 277 if (!this.isDragging) { 278 this.showMenu(); 279 } 280 }); 281 } 282 283 const chatBtn = document.getElementById('gr-chat-btn'); 284 const coachingBtn = document.getElementById('gr-coaching-btn'); 285 const servicesBtn = document.getElementById('gr-services-btn'); 286 const closeBtn = document.getElementById('gr-close-btn'); 287 288 if (chatBtn) chatBtn.addEventListener('click', () => this.chat()); 289 if (coachingBtn) coachingBtn.addEventListener('click', () => this.coaching()); 290 if (servicesBtn) servicesBtn.addEventListener('click', () => this.services()); 291 if (closeBtn) closeBtn.addEventListener('click', () => this.hideMenu()); 292 293 // Add drag listeners globally 294 document.addEventListener('mousemove', (e) => this.drag(e)); 295 document.addEventListener('touchmove', (e) => this.drag(e), { passive: false }); 296 document.addEventListener('mouseup', () =>
296 this.stopDrag()); 297 document.addEventListener('touchend', () => this.stopDrag()); 298 299 setTimeout(() => this.welcome(), 1500); 300 } 301 302 startDrag(e) { 303 this.isDragging = true; 304 const container = document.querySelector('.gr-container'); 305 if (!container) return; 306 307 const rect = container.getBoundingClientRect(); 308 const clientX = e.touches ? e.touches[0].clientX : e.clientX; 309 const clientY = e.touches ? e.touches[0].clientY : e.clientY; 310 311 this.dragOffsetX = clientX - rect.left; 312 this.dragOffsetY = clientY - rect.top; 313 314 document.querySelector('.gr-dog').style.cursor = 'grabbing'; 315 } 316 317 drag(e) { 318 if (!this.isDragging) return; 319 320 const container = document.querySelector('.gr-container'); 321 if (!container) return; 322 323 const clientX = e.touches ? e.touches[0].clientX : e.clientX; 324 const clientY = e.touches ? e.touches[0].clientY : e.clientY; 325 326 const x = clientX - this.dragOffsetX; 327 const y = clientY - this.dragOffsetY; 328 329 // Keep within viewport bounds 330 const viewportWidth = window.innerWidth; 331 const viewportHeight = window.innerHeight; 332 const containerWidth = 100; 333 const containerHeight = 100; 334 335 const boundedX = Math.max(0, Math.min(x, viewportWidth - containerWidth)); 336 const boundedY = Math.max(0, Math.min(y, viewportHeight - containerHeight)); 337 338 container.style.left = boundedX + 'px'; 339 container.style.right = 'auto'; 340 container.style.top = boundedY + 'px'; 341 container.style.bottom = 'auto'; 342 } 343 344 stopDrag() { 345 if (this.isDragging) { 346 this.isDragging = false; 347 const container = document.querySelector('.gr-container'); 348 if (container) { 349 const rect = container.getBoundingClientRect(); 350 // Save position as left/top instead of bottom/right when dragged 351 this.savePosition(null, null); 352 // But store actual pixel position 353 try { 354 localStorage.setItem('grDogPosition', JSON.stringify({ 355 left: rect.left, 356 top: rect.top 357 })); 358 } catch (e) { 359 // Silently fail 360 } 361 } 362 document.querySelector('.gr-dog').style.cursor = 'grab'; 363 } 364 } 365 366 welcome() { 367 this.showBubble(this.messages[0]); 368 setTimeout(() => { 369 this.showBubble(this.messages[1]); 370 setTimeout(() => this.showMenu(), 3500); 371 }, 3500); 372 } 373 374 showBubble(text) { 375 const bubble = document.getElementById('gr-bubble'); 376 if (bubble) { 377 bubble.textContent = text; 378 bubble.style.display = 'block'; 379 setTimeout(() => { 380 if (bubble.style.display !== 'none') { 381 bubble.style.display = 'none'; 382 } 383 }, 3000); 384 } 385 } 386 387 showMenu() { 388 const bubble = document.getElementById('gr-bubble'); 389 const menu = document.getElementById('gr-menu'); 390 const dog = document.querySelector('.gr-dog'); 391 392 if (bubble) bubble.style.display = 'none'; 393 if (menu) menu.style.display = 'block'; 394 395 // Start sniffing and throw coins 396 this.sniff(); 397 this.throwCoins(); 398 } 399 400 hideMenu() { 401 const menu = document.getElementById('gr-menu'); 402 if (menu) menu.style.display = 'none'; 403 } 404 405 chat() { 406 this.hideMenu(); 407 const yoda = document.querySelector('.yoda-chatbot-btn'); 408 if (yoda) { 409 setTimeout(() =>
409 yoda.click(), 100); 410 } else { 411 this.showBubble('ð¬ YodaBot not found!'); 412 } 413 } 414 415 coaching() { 416 this.hideMenu(); 417 window.open('https://topmate.io/sanjeev_kumar_sharma', '_blank'); 418 } 419 420 services() { 421 this.hideMenu(); 422 window.open('https://sanjeevsharmasan.github.io/postmate-bookings-package.html', '_blank'); 423 } 424 425 sniff() { 426 const dog = document.querySelector('.gr-dog'); 427 if (!dog) return; 428 429 dog.classList.add('sniffing'); 430 setTimeout(() => { 431 dog.classList.remove('sniffing'); 432 }, 2000); 433 } 434 435 throwCoins() { 436 const container = document.querySelector('.gr-container'); 437 if (!container) return; 438 439 const rect = container.getBoundingClientRect(); 440 const dogX = rect.left + rect.width / 2; 441 const dogY = rect.top; 442 443 // Throw 3-5 coins 444 const coinCount = 3 + Math.floor(Math.random() * 3); 445 for (let i = 0; i < coinCount; i++) { 446 setTimeout(() => { 447 const coin = document.createElement('div'); 448 coin.className = 'gr-coin'; 449 450 // Random starting position near dog 451 const offsetX = (Math.random() - 0.5) * 40; 452 coin.style.left = (dogX + offsetX) + 'px'; 453 coin.style.top = dogY + 'px'; 454 455 // Random horizontal spread 456 const spreadX = (Math.random() - 0.5) * 200; 457 const duration = 1.8 + Math.random() * 0.4; 458 459 coin.style.animation = `coinFall ${duration}s ease-in forwards`; 460 coin.style.setProperty('--spread-x', spreadX + 'px'); 461 462 document.body.appendChild(coin); 463 464 // Clean up after animation 465 setTimeout(() => coin.remove(), duration * 1000); 466 }, i * 70); 467 } 468 } 469 470 show() { 471 const container = document.querySelector('.gr-container'); 472 if (container) container.style.display = 'block'; 473 } 474 475 hide() { 476 const container = document.querySelector('.gr-container'); 477 if (container) container.style.display = 'none'; 478 } 479 } 480 481 window.gr = new GoldenRetriever(); 482 window.goldenRetriever = window.gr; 483})();
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.