1const errorsCache = new Map(); 2 3let currentOnClose = null; 4let popupVisibleTimer = null; 5 6const ICONS = { 7 success: ` 8 <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"> 9 <path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm-1.2 13.6-4-4 1.4-1.4 2.6 2.6 5.6-5.6 1.4 1.4-7 7Z" /> 10 </svg>`, 11 error: ` 12 <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"> 13 <path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm3.7 12.3-1.4 1.4-2.3-2.3-2.3 2.3-1.4-1.4 2.3-2.3-2.3-2.3 1.4-1.4 2.3 2.3 2.3-2.3 1.4 1.4-2.3 2.3 2.3 2.3Z" /> 14 </svg>`, 15 info: ` 16 <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"> 17 <path d="M11 10h2v7h-2v-7Zm0-3h2v2h-2V7Z" /> 18 <path d="M12 2a10 10 0 1 0 .01 0H12Zm0 18a8 8 0 1 1 .01-16A8 8 0 0 1 12 20Z" /> 19 </svg>`, 20 warning: ` 21 <svg viewBox="0 0 96 96" aria-hidden="true" focusable="false"> 22 <path d="M48 8 6 82h84L48 8Z" opacity=".12" /> 23 <path d="M48 10 8 80h80L48 10Zm0 8 33.2 58H14.8L48 18Z" /> 24 <path d="M44 36h8v22h-8V36Zm0 28h8v8h-8v-8Z" /> 25 </svg>`, 26 gift: ` 27 <svg viewBox="0 0 96 96" aria-hidden="true" focusable="false"> 28 <path d="M16 38h64v42H16V38Z" opacity=".12" /> 29 <path d="M14 34h68v16h-6v32H20V50h-6V34Zm14 16v24h16V50H28Zm24 0v24h16V50H52Zm22-8H22v4h52v-4Z" /> 30 <path d="M43 34c-9.5-.2-17-4.1-17-10.3C26 18.8 30 16 34.2 16 41.7 16 46 24.4 48 30.5 50 24.4 54.3 16 61.8 16 66 16 70 18.8 70 23.7 70 29.9 62.5 33.8 53 34h-10Zm-1.6-8.1C39.3 22 37 20 34.2 20 31.7 20 30 21.5 30 23.7c0 3 4.5 5.6 12.1 6.2-.2-1.1-.5-2.5-.7-4Zm12.5 4C61.5 29.3 66 26.7 66 23.7c0-2.2-1.7-3.7-4.2-3.7-2.8 0-5.1 2-7.2 5.9-.2 1.5-.5 2.9-.7 4Z" /> 31 </svg>`, 32 aml: ` 33 <svg viewBox="0 0 96 96" aria-hidden="true" focusable="false"> 34 <path d="M48 8 18 20v24c0 19 12.8 36.8 30 42 17.2-5.2 30-23 30-42V20L48 8Z" opacity=".12" /> 35 <path d="M48 6 16 18.8V44c0 20.6 13.8 39.8 32 45.2C66.2 83.8 80 64.6 80 44V18.8L48 6Zm24 18.2V44c0 16.6-10 32.2-24 37-14-4.8-24-20.4-24-37V24.2l24-9.6 24 9.6Z" /> 36 <path d="m43.6 57.2-11-11 5.6-5.6 5.4 5.4 15-15 5.6 5.6-20.6 20.6Z" /> 37 </svg>`, 38 completed: ` 39 <svg viewBox="0 0 96 96" aria-hidden="true" focusable="false"> 40 <path d="M48 8a40 40 0 1 0 0 80 40 40 0 0 0 0-80Z" opacity=".12" /> 41 <path d="M48 6a42 42 0 1 0 0 84 42 42 0 0 0 0-84Zm0 76a34 34 0 1 1 0-68 34 34 0 0 1 0 68Z" /> 42 <path d="m41.5 58.5-13-13 5.7-5.7 7.3 7.3 20.3-20.3 5.7 5.7-26 26Z" /> 43 </svg>`, 44 45 pending: ` 46 <svg viewBox="0 0 96 96" aria-hidden="true" focusable="false"> 47 <path d="M48 8a40 40 0 1 0 0 80 40 40 0 0 0 0-80Z" opacity=".12" /> 48 <path d="M48 6a42 42 0 1 0 0 84 42 42 0 0 0 0-84Zm0 76a34 34 0 1 1 0-68 34 34 0 0 1 0 68Z" /> 49 <path d="M44 24h8v26l18 10-4 7-22-12V24Z" /> 50 </svg>` 51}; 52 53function injectNotificationStyles() { 54 if (document.getElementById('modern-notification-styles')) { 55 return; 56 } 57 58 const style = document.createElement('style'); 59 style.id = 'modern-notification-styles'; 60 style.textContent = ` 61 #v-notifications { 62 position: fixed; 63 left: 50%; 64 bottom: 24px; 65 z-index: 999999999; 66 display: grid; 67 gap: 10px; 68 width: min(440px, calc(100vw - 32px)); 69 transform: translateX(-50%); 70 pointer-events: none; 71 } 72 73 #v-notifications .v-snack__wrapper { 74 display: flex; 75 align-items: center; 76 justify-content: space-between; 77 gap: 16px; 78 min-height: 52px; 79 padding: 12px 14px 12px 18px; 80 border: 1px solid rgba(255, 255, 255, .12); 81 border-radius: 16px; 82 background: rgba(17, 24, 39, .92); 83 color: #fff; 84 box-shadow: 0 18px 45px rgba(0, 0, 0, .28); 85 backdrop-filter: blur(14px); 86 pointer-events: auto; 87 animation: notiSlideIn .22s ease-out; 88 } 89 90 #v-notifications .v-snack__wrapper.is-leaving { 91 animation: notiSlideOut .18s ease-in forwards; 92 } 93 94 #v-notifications .v-snack__content { 95 font-size: 14px; 96 line-height: 1.45;
97 overflow-wrap: anywhere; 98 } 99 100 #v-notifications .v-snack__btn, 101 .dialog__close { 102 display: inline-grid; 103 place-items: center; 104 border: 0; 105 background: transparent; 106 cursor: pointer; 107 } 108 109 #v-notifications .v-snack__btn { 110 width: 34px; 111 height: 34px; 112 border-radius: 999px; 113 color: currentColor; 114 opacity: .9; 115 } 116 117 #v-notifications .v-snack__btn:hover, 118 .dialog__close:hover { 119 background: rgba(255, 255, 255, .1); 120 } 121 122 #v-notifications svg, 123 .dialog__close svg { 124 width: 22px; 125 height: 22px; 126 fill: currentColor; 127 } 128 129 .custom-popup { 130 position: fixed; 131 inset: 0; 132 z-index: 99999999999999999; 133 display: grid; 134 place-items: center; 135 padding: 20px; 136 background: rgba(15, 23, 42, .58); 137 backdrop-filter: blur(8px); 138 animation: popupFadeIn .18s ease-out; 139 } 140 141 .custom-popup-fade-out { 142 animation: popupFadeOut .18s ease-in forwards; 143 } 144 145 .custom-popup .discard-modal { 146 position: relative; 147 width: min(var(--popup-width, 500px), calc(100vw - 32px)); 148 overflow: hidden; 149 border: 1px solid rgba(148, 163, 184, .22); 150 border-radius: 24px; 151 background: radial-gradient(circle at 50% 10%, rgba(250, 204, 21, .22), transparent 45%), linear-gradient(135deg, #fff7ed 0%, #eef2ff 100%); 152 color: #0f172a; 153 text-align: center; 154 box-shadow: 0 24px 70px rgba(2, 6, 23, .32); 155 transform-origin: center center; 156 } 157 158 .custom-popup .dialog__close { 159 position: absolute; 160 top: 14px; 161 right: 14px; 162 z-index: 2; 163 width: 38px; 164 height: 38px; 165 border-radius: 999px; 166 color: #334155; 167 } 168 169 .custom-popup__visual { 170 display: grid; 171 place-items: center; 172 min-height: 172px; 173 174 color: #f59e0b; 175 } 176 177 .custom-popup__visual.is-error { color: #ef4444; } 178 .custom-popup__visual.is-gift { color: #8b5cf6; } 179 .custom-popup__visual.is-aml { color: #0f766e; } 180 .custom-popup__visual.is-info { color: #2563eb; } 181 .custom-popup__visual.is-completed { color: #16a34a; } 182 .custom-popup__visual.is-pending { color: #f59e0b; } 183 184 .custom-popup__visual svg { 185 width: 108px; 186 height: 108px; 187 fill: currentColor; 188 } 189 190 .custom-popup__body { 191 padding: 30px 32px 32px; 192 } 193 194 .custom-popup .discard-modal__title { 195 display: inline-block; 196 margin-bottom: 12px; 197 font-size: clamp(1.35rem, 3vw, 1.65rem); 198 font-weight: 700; 199 line-height: 1.2; 200 } 201 202 .custom-popup .discard-modal__text { 203 margin-bottom: 24px; 204 color: #475569; 205 font-size: 15px; 206 line-height: 1.6; 207 overflow-wrap: anywhere; 208 } 209 210 .custom-popup .discard-modal__verification-btn { 211 width: 100%; 212 min-height: 46px; 213 border: 0; 214 border-radius: 14px; 215 background: #111827; 216 color: #fff; 217 font-weight: 700; 218 cursor: pointer; 219 box-shadow: 0 12px 24px rgba(17, 24, 39, .18); 220 transition: transform .16s ease, box-shadow .16s ease, background .16s ease; 221 } 222 223 .custom-popup .discard-modal__verification-btn:hover { 224 background: #020617; 225 box-shadow: 0 16px 30px rgba(17, 24, 39, .24); 226 transform: translateY(-1px); 227 } 228 229 @keyframes notiSlideIn { 230 from { opacity: 0; transform: translateY(10px) scale(.98); } 231 to { opacity: 1; transform: translateY(0) scale(1); } 232 } 233 234 @keyframes notiSlideOut { 235 to { opacity: 0; transform: translateY(8px) scale(.98); } 236 } 237 238 @keyframes popupFadeIn { 239 from { opacity: 0; } 240 to { opacity: 1; } 241 } 242 243 @keyframes popupFadeOut { 244 to { opacity: 0; } 245 } 246 247 @media (max-width: 520px) { 248 #v-notifications { 249 bottom: 16px; 250 } 251 252 .custom-popup { 253 padding: 14px; 254 } 255 256 .custom-popup__body { 257 padding: 24px 20px 22px; 258 } 259 260 .custom-popup__visual { 261 min-height: 142px; 262 } 263 264 .custom-popup__visual svg { 265 width: 88px; 266 height: 88px; 267 } 268 } 269 `; 270 document.head.appendChild(style); 271} 272 273function htmlToFragment(html) { 274 const template = document.createElement('template'); 275 template.innerHTML = String(html || '').trim(); 276 return template.content.firstElementChild; 277} 278 279function getTextMessage(key, fallback = '') { 280 if (typeof getMessage === 'function') { 281 return getMessage(key); 282 } 283 284 return fallback; 285} 286 287function ensureNotificationsRoot() { 288 injectNotificationStyles(); 289 290 let root = document.getElementById('v-notifications'); 291 292 if (root) { 293 return root; 294 } 295 296 root = document.createElement('div'); 297 root.id = 'v-notifications'; 298 root.className = 'v-snack v-snack--active v-snack--bottom v-snack--has-background'; 299 300 const app = document.getElementById('app') || document.body; 301 app.prepend(root); 302 303 return root; 304} 305 306function removeElementAnimated(element, className = 'is-leaving', delay = 180) { 307 if (!element || element.dataset.removing === 'true') { 308 return; 309 } 310 311 element.dataset.removing = 'true'; 312 element.classList.add(className); 313 314 window.setTimeout(() => { 315 element.remove(); 316 }, delay); 317} 318 319function getNotificationIcon(status) { 320 if (status === 'error') { 321 return ICONS.error; 322 } 323 324 if (status === 'info') { 325 return ICONS.info; 326 } 327 328 return ICONS.success; 329} 330 331function noti(msg, status = 'success', legacyStatus, options) { 332 if (typeof window.siteToast === 'function') { 333 return window.siteToast(msg, status, legacyStatus, options); 334 } 335 336 const notifications = ensureNotificationsRoot(); 337 338 while (notifications.querySelectorAll('.v-snack__wrapper').length >= 3) { 339 notifications.querySelector('.v-snack__wrapper')?.remove(); 340 } 341 342 const notification = htmlToFragment(` 343 <div class="v-snack__wrapper v-sheet theme--dark" data-status="${status}">
344 <div role="status" aria-live="polite" class="v-snack__content">${msg || ''}</div> 345 <div class="v-snack__action"> 346 <button type="button" class="v-btn v-btn--text theme--dark v-size--default white--text v-snack__btn" aria-label="Close notification"> 347 <span class="v-btn__content">${getNotificationIcon(status)}</span> 348 </button> 349 </div> 350 </div> 351 `); 352 353 notification.querySelector('.v-snack__btn').addEventListener('click', () => closeNoti(notification)); 354 notifications.append(notification); 355 356 window.setTimeout(() => closeNoti(notification), 2500); 357} 358 359function closeNoti(el) { 360 const notification = el?.closest ? el.closest('.v-snack__wrapper') : el; 361 removeElementAnimated(notification); 362} 363 364function isAuthorized() { 365 const input = document.getElementById('user-authorized'); 366 return Boolean(input && input.value !== 'false'); 367} 368 369function requireAuthorized() { 370 if (isAuthorized()) { 371 return true; 372 } 373 374 location.replace('../signin'); 375 return false; 376} 377 378function removeCurrentPopup() { 379 document.getElementById('custom-popup')?.remove(); 380} 381 382function getPopupIconType({ img, status, type }
382 = {}) { 383 const source = String(img || type || status || '').toLowerCase(); 384 385 if (source.includes('completed')) { 386 return 'completed'; 387 } 388 389 if (source.includes('pending')) { 390 return 'pending'; 391 } 392 393 if (source.includes('gift')) { 394 return 'gift'; 395 } 396 397 if (source.includes('aml')) { 398 return 'aml'; 399 } 400 401 if (source.includes('error') || status === 'error') { 402 return 'error'; 403 } 404 405 if (source.includes('info') || status === 'info') { 406 return 'info'; 407 } 408 409 return 'warning'; 410} 411 412function getPopupWidth(img) { 413 const source = String(img || '').toLowerCase(); 414 return source.includes('qrcode.svg') || source.includes('antiphishing.svg') ? 400 : 500; 415} 416 417function createPopup({ 418 title, 419 message, 420 img, 421 status, 422 type, 423 withBtn = false, 424 btnMessage, 425 buttonAction, 426 onClose 427 }) { 428 injectNotificationStyles(); 429 removeCurrentPopup(); 430 431 currentOnClose = typeof onClose === 'function' ? onClose : null; 432 433 const iconType = getPopupIconType({ img, status, type }); 434 435 const popup = htmlToFragment(` 436 <div id="custom-popup" role="dialog" aria-modal="true" class="custom-popup v-dialog__content v-dialog__content--active" showed="false"> 437 <div tabindex="0" class="discard-modal v-dialog dialog v-dialog--active" style="--popup-width: ${getPopupWidth(img)}px;"> 438 <button type="button" class="dialog__close" aria-label="Close popup"> 439 ${ICONS.error} 440 </button> 441 <div class="custom-popup__visual is-${iconType}"> 442 ${ICONS[iconType] || ICONS.warning} 443 </div> 444 <div class="custom-popup__body"> 445 <div class="discard-modal__title discard-title">${title || ''}</div> 446 <div class="discard-modal__text">${message || ''}</div> 447 ${withBtn ? ` 448 <button type="button" class="discard-modal__verification-btn v-btn v-btn--is-elevated v-btn--has-bg theme--light v-size--default"> 449 <span class="v-btn__content">${btnMessage || getTextMessage('popup.button.good', 'OK')}</span> 450 </button>` : ''} 451 </div> 452 </div> 453 </div> 454 `); 455 456 popup.querySelector('.dialog__close').addEventListener('click', closePopup); 457 458 const button = popup.querySelector('.discard-modal__verification-btn'); 459 460 if (button) { 461 button.addEventListener('click', () => { 462 if (typeof buttonAction === 'function') { 463 buttonAction(); 464 return; 465 } 466 467 closePopup(); 468 }); 469 } 470 471 const target = document.querySelector('footer') || document.body; 472 target.after(popup); 473 474 clearTimeout(popupVisibleTimer); 475 popupVisibleTimer = window.setTimeout(() => { 476 document.getElementById('custom-popup')?.setAttribute('showed', 'true'); 477 }, 300); 478} 479 480function popup(title, message, img, status, withBtn, onClose, btnMessage) { 481 createPopup({ 482 title, 483 message, 484 img, 485 status, 486 withBtn, 487 onClose, 488 btnMessage 489 }); 490} 491 492async function profileRequest(payload, dataType = 'json') { 493 const response = await fetch('../api/user/profile', { 494 method: 'POST', 495 headers: { 496 'Content-Type': 'application/json; charset=UTF-8' 497 }, 498 body: JSON.stringify(payload) 499 }); 500 501 if (!response.ok) { 502 throw new Error(`Profile request failed: ${response.status}`); 503 } 504 505 if (dataType === 'text') { 506 return response.text(); 507 } 508 509 return response.json().catch(() => null); 510} 511 512async function addErrorAction(type) { 513 try { 514 await profileRequest({ 515 action: 'ADD_ACTION', 516 action_type: 'GET_ERROR', 517 type 518 }); 519 } catch (ignored) {} 520} 521 522async function getErrorMessage(type) { 523 if (errorsCache.has(type)) { 524 return errorsCache.get(type); 525 } 526 527 const response = await profileRequest({ 528 action: 'GET_ERROR_MESSAGE', 529 type 530 }, 'text'); 531 532 errorsCache.set(type, response); 533 return response; 534} 535 536async function showErrorPopup({ 537 type, 538 title, 539 buttonText, 540 buttonAction, 541 iconType = 'error', 542 onClose = null 543 }) { 544 if (!requireAuthorized()) { 545 return; 546 } 547 548 try { 549 const message = await getErrorMessage(type); 550 551 await addErrorAction(type); 552 553 createPopup({ 554 title, 555 message, 556 type: iconType, 557 withBtn: true, 558 btnMessage: buttonText, 559 buttonAction, 560 onClose 561 }); 562 } catch (error) { 563 noti(getTextMessage('popup.error.load', 'Something went wrong. Please try again.'), 'error'); 564 } 565} 566 567function verificationPopup(event) { 568 event?.preventDefault(); 569 570 showErrorPopup({ 571 type: 'OTHER', 572 title: getTextMessage('popup.title.verification'), 573 buttonText: getTextMessage('popup.button.verification'), 574 buttonAction: () => { 575 window.location.href = '../profile/settings#verification'; 576 }, 577 iconType: 'error' 578 });
579} 580 581function errorPopup(type, onClose, event) { 582 event?.preventDefault(); 583 584 showErrorPopup({ 585 type, 586 title: getTextMessage('popup.title.verification'), 587 buttonText: getTextMessage('popup.button.good'), 588 buttonAction: closePopup, 589 iconType: 'error', 590 onClose 591 }); 592} 593 594function withdrawPopup(type, event) { 595 event?.preventDefault(); 596 597 showErrorPopup({ 598 type, 599 title: getTextMessage(type === 'WITHDRAW_AML' ? 'popup.aml.title' : 'popup.withdraw.title'), 600 buttonText: getTextMessage('popup.button.deposit.verification'), 601 buttonAction: () => { 602 location.replace('../profile/deposit-verification'); 603 }, 604 iconType: type === 'WITHDRAW_AML' ? 'aml' : 'error' 605 }); 606} 607 608function withdrawStatusPopup(response, onClose) { 609 if (response !== 'completed' && response !== 'pending') { 610 return false; 611 } 612 613 createPopup({ 614 title: getTextMessage(`withdraw.${response}.title`), 615 message: getTextMessage(`withdraw.${response}.description`), 616 type: response, 617 withBtn: true, 618 btnMessage: getTextMessage('popup.button.good', 'OK'), 619 buttonAction: closePopup, 620 onClose 621 }); 622 623 return true; 624} 625 626function closePopup() { 627 const popup = document.getElementById('custom-popup'); 628 629 if (!popup || popup.dataset.removing === 'true') { 630 return; 631 } 632 633 popup.dataset.removing = 'true'; 634 popup.setAttribute('showed', 'false'); 635 popup.classList.add('custom-popup-fade-out'); 636 637 window.setTimeout(() => { 638 popup.remove(); 639 640 if (typeof currentOnClose === 'function') { 641 currentOnClose(); 642 currentOnClose = null; 643 } 644 }, 180); 645} 646 647function handlePopupOutsideClick(event) { 648 const popup = document.getElementById('custom-popup'); 649 650 if (!popup || popup.getAttribute('showed') !== 'true') { 651 return; 652 } 653 654 const dialog = popup.querySelector('.discard-modal'); 655 656 if (dialog && !dialog.contains(event.target)) { 657 closePopup(); 658 } 659} 660 661function handlePopupEscape(event) { 662 if (event.key === 'Escape') { 663 closePopup(); 664 } 665} 666 667document.addEventListener('click', handlePopupOutsideClick); 668document.addEventListener('keydown', handlePopupEscape); 669 670window.noti = noti; 671window.closeNoti = closeNoti; 672window.popup = popup; 673window.addErrorAction = addErrorAction; 674window.verificationPopup = verificationPopup; 675window.errorPopup = errorPopup; 676window.withdrawPopup = withdrawPopup; 677window.withdrawStatusPopup = withdrawStatusPopup; 678window.closePopup = closePopup; 679 680if (typeof window.siteToast !== 'function') { 681 ensureNotificationsRoot(); 682}
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.