1/* =================================== */ 2/* =================================== */ 3/* =================================== */ 4/* Danylo Oliinyk - https://iamdev.lol/ */ 5/* =====October 2025 For Statoplan =====*/ 6/* =================================== */ 7/* =================================== */ 8/* =================================== */ 9/* 01. Script for the speakers slider */ 10/* 02. Script for the video slider */ 11/* 03. Script for displaying news */ 12/* 04. Script for the popup */ 13/* 05. Script for smooth scroll and navigation */ 14/* 06. Script for the burger menu */ 15/* 07. Script for the accordeon */ 16/* =================================== */ 17/* =================================== */ 18/* =================================== */ 19 20 21/* 01. Script for the speakers slider start*/ 22document.addEventListener('DOMContentLoaded', () => { 23 const root = document.querySelector('#speaker .speaker__content'); 24 if (!root) return; 25 26 const track = root.querySelector('.speaker__track'); 27 const prev = root.querySelector('.speaker__nav-left'); 28 const next = root.querySelector('.speaker__nav-right'); 29 30 if (!track || !prev || !next) return; 31 32 const state = { 33 index: 0, 34 max: 0, 35 gap: 0, 36 slideW: 0 37 }; 38 39 function measure() { 40 const first = track.querySelector('.speaker__item'); 41 if (!first) return; 42 43 const cs = getComputedStyle(track); 44 state.gap = parseFloat(cs.columnGap || cs.gap || 0) || 0; 45 state.slideW = first.getBoundingClientRect().width; 46 47 const viewportW = track.parentElement.getBoundingClientRect().width; 48 49 // ÑколÑко ÐÐÐÐЫХ каÑÑоÑек ÑеалÑно Ð²Ð»ÐµÐ·Ð°ÐµÑ 50 const perView = Math.max( 51 1, 52 Math.floor((viewportW + state.gap) / (state.slideW + state.gap)) 53 ); 54 55 state.max = Math.max(0, track.children.length - perView); 56 state.index = Math.min(state.index, state.max); 57 58 update(); 59 toggle(); 60 } 61 62 function update() { 63 const offset = -(state.slideW + state.gap) * state.index; 64 track.style.transform = `translateX(${offset}px)`; 65 } 66 67 function toggle() { 68 prev.classList.toggle('is-disabled', state.index === 0); 69 next.classList.toggle('is-disabled', state.index === state.max); 70 } 71 72 prev.addEventListener('click', (e) => { 73 e.preventDefault(); 74 if (state.index > 0) { 75 state.index--; 76 update(); 77 toggle(); 78 } 79 }); 80 81 next.addEventListener('click', (e) => { 82 e.preventDefault(); 83 if (state.index < state.max) { 84 state.index++; 85 update(); 86 toggle(); 87 } 88 }); 89 90 window.addEventListener('resize', measure, { passive: true }); 91 92 measure(); 93}); 94 95/* 01. Script for the speakers slider end */ 96 97 98/* 02. Script for the video slider start */ 99document.addEventListener('DOMContentLoaded', () => { 100 const root = document.querySelector('#video'); 101 if (!root) return; 102 103 /* slider */ 104 const track = root.querySelector('.video__track'); 105 const items = Array.from(track.children); 106 const prev = root.querySelector('.video__nav-left'); 107 const next = root.querySelector('.video__nav-right'); 108 109 const GAP = 30; 110 let perView = 2; 111 112 const mqMobile = window.matchMedia('(max-width:640px)'); 113 function recalcPerView(){ perView = mqMobile.matches ? 1 : 2; } 114 recalcPerView(); 115 mqMobile.addEventListener('change', recalcPerView); 116 117 let index = 0; 118 119 function clampIndex(){ 120 const max = Math.max(0, items.length - perView); 121 if (index < 0) index = 0; 122 if (index > max) index = max; 123 prev.classList.toggle('is-disabled', index === 0); 124 next.classList.toggle('is-disabled', index === max); 125 } 126 127 function slide(){ 128 const itemW = items[0]?.getBoundingClientRect().width || 0; 129 const offset = index * (itemW + GAP); 130 track.style.transform = `translateX(${-offset}px)`; 131 } 132 133 prev?.addEventListener('click', e => { e.preventDefault(); index--; clampIndex(); slide(); }); 134 next?.addEventListener('click', e => { e.preventDefault(); index++; clampIndex(); slide(); }); 135 window.addEventListener('resize', () => { clampIndex(); slide(); }); 136 137 clampIndex(); slide(); 138 139 /* players */ 140 root.querySelectorAll('.video__player').forEach(setupPlayer); 141 142 function setupPlayer(box){ 143 const poster = box.dataset.poster || ''; 144 if (poster) box.style.backgroundImage = `url("${poster}")`; 145 146 function play(){ 147 const src = box.dataset.src || ''; 148 if (!src) return; 149 150 // MP4/WEBM 151 if (/\.(mp4|m4v|webm)(\?|$)/i.test(src)) { 152 const v = document.createElement('video'); 153 v.src = src; 154 v.controls = true; 155 v.autoplay = true;
156 v.playsInline = true; 157 v.poster = poster || ''; 158 v.style.width = '100%'; 159 v.style.height = '100%'; 160 v.style.display = 'block'; 161 box.replaceChildren(v); 162 return; 163 } 164 165 // Iframe embeds (YouTube/Vimeo/дÑ.) 166 const iframe = document.createElement('iframe'); 167 iframe.src = src.includes('autoplay=1') ? src : (src + (src.includes('?') ? '&' : '?') + 'autoplay=1'); 168 iframe.allow = 'autoplay; encrypted-media; picture-in-picture'; 169 iframe.allowFullscreen = true; 170 iframe.title = 'Ðидео'; 171 iframe.style.width = '100%'; 172 iframe.style.height = '100%'; 173 iframe.frameBorder = '0'; 174 box.replaceChildren(iframe); 175 } 176 177 box.addEventListener('click', play); 178 box.addEventListener('keydown', e => { 179 if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); play(); } 180 }); 181 } 182}); 183 184/* 02. Script for the video slider end */ 185 186/* 03. Script for displaying news start */ 187/* document.addEventListener('DOMContentLoaded', () => { 188 const root = document.querySelector('#news'); 189 if (!root) return; 190 191 const rows = [...root.querySelectorAll('.news__wrap .news__row')]; 192 const btn = root.querySelector('.news__btn-group .btn'); 193 194 rows.slice(1).forEach(r => r.classList.remove('active')); 195 196 btn?.addEventListener('click', e => { 197 e.preventDefault(); 198 const hidden = rows.slice(1).filter(r => !r.classList.contains('active')); 199 if (hidden.length) { 200 hidden.forEach(r => r.classList.add('active')); 201 btn.textContent = 'СвеÑнÑÑÑ'; 202 } else { 203 rows.slice(1).forEach(r => r.classList.remove('active')); 204 btn.textContent = 'ÐолÑÑе новоÑÑей'; 205 root.scrollIntoView({behavior:'smooth', block:'start'}); 206 } 207 }); 208}); */ 209/* 03. Script for displaying news end */ 210 211 212 213/* Script for the Pop-Up start*/ 214(function(){ 215 const OPEN_IDS = new Set(['#tospeak', '#sponsored']); 216 217 function openModal(id){ 218 const m = document.querySelector(id); 219 if (!m) return; 220 m.classList.add('is-open'); 221 m.setAttribute('aria-hidden', 'false'); 222 document.body.classList.add('no-scroll'); 223 } 224 function closeModal(m){ 225 if (!m) return; 226 m.classList.remove('is-open'); 227 m.setAttribute('aria-hidden', 'true'); 228 document.body.classList.remove('no-scroll'); 229 if (location.hash && (location.hash === '#tospeak' || location.hash === '#sponsored')) { 230 history.replaceState('', document.title, location.pathname + location.search); 231 } 232 } 233 234 document.addEventListener('click', (e) => { 235 const a = e.target.closest('a[href^="#"]'); 236 if (a && OPEN_IDS.has(a.getAttribute('href'))) { 237 e.preventDefault(); 238 openModal(a.getAttribute('href')); 239 return; 240 } 241 if (e.target.matches('[data-close]')) { 242 closeModal(e.target.closest('.modal')); 243 return; 244 } 245 const modal = e.target.classList.contains('modal') ? e.target : null; 246 if (modal) closeModal(modal); 247 }); 248 249 document.addEventListener('keydown', (e) => { 250 if (e.key === 'Escape') { 251 document.querySelectorAll('.modal.is-open').forEach(closeModal); 252 } 253 }); 254 255 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 256 window.addEventListener('hashchange', () => { 257 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 258 }); 259})(); 260 261/* Script for the Pop-Up end*/ 262 263 264/* 05. Script for smooth scroll and navigation start */ 265/* (function(){ 266 const EASE = 0.10; // инеÑÑÐ¸Ñ 267 const STEP = 0.50; // базовÑй Ñаг Ð´Ð»Ñ ÐºÐ¾Ð»ÑÑика 268 let target = window.scrollY; 269 let y = target; 270 let rafId = null; 271 272 const header = document.querySelector('header'); 273 const headerH = () => (header ? header.offsetHeight : 0); 274 275 function isScrollable(el){ 276 while (el && el !== document.body){ 277 const s = getComputedStyle(el); 278 const canScroll = /(auto|scroll)/.test(s.overflow + s.overflowY + s.overflowX); 279 if (canScroll && el.scrollHeight > el.clientHeight) return true; 280 el = el.parentElement; 281 } 282 return false; 283 } 284 285 function animate(){ 286 const diff = target - y; 287 if (Math.abs(diff) < 0.1){ 288 y = target; 289 window.scrollTo(0, y); 290 rafId = null; 291 return; 292 } 293 y += diff * EASE; 294 window.scrollTo(0, y); 295 rafId = requestAnimationFrame(animate); 296 } 297 298 function clamp(v){ 299 const max = Math.max(0, document.documentElement.scrollHeight - window.innerHeight); 300 return Math.min(Math.max(0, v), max); 301 } 302 303 function toY(pos){ 304 target = clamp(pos); 305 if (!rafId) rafId = requestAnimationFrame(animate); 306 } 307 308 window.addEventListener('wheel', (e) => { 309 if (e.defaultPrevented) return; 310 if (isScrollable(e.target)) return; 311 e.preventDefault(); 312 const delta = (Math.abs(e.deltaY) > 1 ? e.deltaY : Math.sign(e.deltaY)) * STEP; 313 toY((target = clamp(target + delta))); 314 }, { passive: false }); 315 316 window.addEventListener('scroll', () => { 317 if (!rafId) { target = window.scrollY; y = window.scrollY; } 318 }, { passive: true }); 319 320 window.addEventListener('resize', () => { target = clamp(target); y = clamp(y); }); 321 322 323 document.addEventListener('click', (e) => { 324 const a = e.target.closest('a[href^="#"]'); 325 if (!a) return; 326 const href = a.getAttribute('href'); 327 if (!href || href === '#') return; 328 329 const id = href.slice(1); 330 const el = document.getElementById(id); 331 if (!el) return; 332 333 e.preventDefault(); 334 const rectTop = el.getBoundingClientRect().top; 335 const offset = headerH(); 336 const dest = (window.scrollY || 0) + rectTop - offset; 337 toY(dest); 338 339 if (history.replaceState) { 340 history.replaceState('', document.title, location.pathname + location.search); 341 } 342 }, { passive: false }); 343 344 window.addEventListener('load', () => { 345 if (location.hash && location.hash.length > 1){ 346 const el = document.getElementById(location.hash.slice(1)); 347 if (el){ 348 const rectTop = el.getBoundingClientRect().top; 349 toY((window.scrollY || 0) + rectTop - headerH()); 350 history.replaceState('', document.title, location.pathname + location.search); 351 } 352 } 353 }); 354})(); */ 355/* 05. Script for smooth scroll and navigation end */ 356 357/* 06. Script for the accordeon Start*/ 358// Accordeon Start 359(function () { 360 const root = document.querySelector('.program__acc-wrap'); 361 if (!root) return; 362 363 const items = Array.from(root.querySelectorAll('.program__acc')); 364 if (!items.length) return; 365 366 const PLUS = '<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19" fill="none"><path d="M9.1667 1V17.3334M1 9.16668H17.3333" stroke="var(--color-blue-strong)" stroke-width="2" stroke-linecap="square" stroke-linejoin="round"/></svg>'; 367 const MINUS = '<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19" fill="none"><path d="M1 9.16668H17.3333" stroke="var(--color-blue-strong)" stroke-width="2" stroke-linecap="square" stroke-linejoin="round"/></svg>'; 368 369 // ÐниÑиализаÑиÑ: вÑе закÑÑÑÑ, везде плÑÑ
370 items.forEach(item => { 371 const text = item.querySelector('.program__acc-text'); 372 const icon = item.querySelector('.program__acc-col-right'); 373 if (text) text.classList.remove('acc-text-active'); 374 if (icon) icon.innerHTML = PLUS; 375 }); 376 377 function toggleAcc(target) { 378 const targetText = target.querySelector('.program__acc-text'); 379 const targetIcon = target.querySelector('.program__acc-col-right'); 380 if (!targetText || !targetIcon) return; 381 382 const willOpen = !targetText.classList.contains('acc-text-active'); 383 384 // позиÑÐ¸Ñ Ð±Ð»Ð¾ÐºÐ° ÐРизменений 385 const beforeTop = target.getBoundingClientRect().top; 386 387 // закÑÑваем вÑÐµÑ Ð¸ ÑÑавим плÑÑики 388 items.forEach(item => { 389 const text = item.querySelector('.program__acc-text'); 390 const icon = item.querySelector('.program__acc-col-right'); 391 if (!text || !icon) return; 392 text.classList.remove('acc-text-active'); 393 icon.innerHTML = PLUS; 394 }); 395 396 // оÑкÑÑваем ÑолÑко Ñелевой (еÑли нÑжно) 397 if (willOpen) { 398 targetText.classList.add('acc-text-active'); 399 targetIcon.innerHTML = MINUS; 400 } 401 402 // компенÑаÑÐ¸Ñ ÑкÑолла â оÑÑавлÑем заголовок на меÑÑе 403 requestAnimationFrame(() => { 404 const afterTop = target.getBoundingClientRect().top; 405 const diff = afterTop - beforeTop; 406 if (Math.abs(diff) > 1) { 407 window.scrollBy(0, diff); 408 } 409 }); 410 } 411 412 root.addEventListener('click', (e) => { 413 const acc = e.target.closest('.program__acc'); 414 if (!acc || !root.contains(acc)) return; 415 e.preventDefault(); 416 toggleAcc(acc); 417 }); 418})(); 419 420/* Script for the accordeon End*/ 421(() => { 422 const burger = document.getElementById('burger-icon'); 423 const nav = document.getElementById('nav-links'); 424 if (!burger || !nav) return; 425 426 // a11y 427 burger.setAttribute('role','button'); 428 burger.setAttribute('aria-controls','nav-links'); 429 burger.setAttribute('aria-expanded','false'); 430 burger.tabIndex = 0; 431 432 const mq = window.matchMedia('(max-width:1024px)'); 433 434 const open = () => { 435 nav.classList.add('active'); 436 document.documentElement.style.overflow = 'hidden'; 437 burger.setAttribute('aria-expanded','true'); 438 }; 439 440 const close = () => { 441 nav.classList.remove('active'); 442 document.documentElement.style.overflow = ''; 443 burger.setAttribute('aria-expanded','false'); 444 }; 445 446 const toggle = () => (nav.classList.contains('active') ? close() : open()); 447 448 burger.addEventListener('click', toggle); 449 burger.addEventListener('keydown', e => { 450 if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); } 451 }); 452 453 // клик по ÑÑÑлке â закÑÑÑÑ 454 nav.addEventListener('click', e => { 455 if (e.target.closest('.header__link')) close(); 456 }); 457 458 // клик вне Ð¼ÐµÐ½Ñ â закÑÑÑÑ 459 document.addEventListener('click', e => { 460 if (!mq.matches) return; 461 const t = e.target; 462 if (nav.classList.contains('active') && !nav.contains(t) && !burger.contains(t)) close(); 463 }); 464 465 // Esc 466 document.addEventListener('keydown', e => { 467 if (e.key === 'Escape') close(); 468 }); 469 470 // ÑеÑÐµÑ Ð¿Ñи изменении бÑейкпоинÑа 471 window.addEventListener('resize', () => { 472 if (!mq.matches) close(); 473 }); 474})(); 475 476 477document.querySelectorAll('a[href^="#"]').forEach(anchor => { 478 anchor.addEventListener('click', function (e) { 479 const targetId = this.getAttribute('href').substring(1); // ÑбиÑаем # 480 if (!targetId) return; 481 482 const target = document.getElementById(targetId); 483 if (!target) return; 484 485 e.preventDefault(); // оÑклÑÑаем ÑÑандаÑÑнÑй пеÑÐµÑ Ð¾Ð´ Ñ ÑкоÑем в URL 486 487 const headerOffset = 80; // подгони под вÑÑоÑÑ Ñвоего ÑикÑиÑованного Ñ ÐµÐ´ÐµÑа 488 const elementPosition = target.getBoundingClientRect().top + window.pageYOffset; 489 const offsetPosition = elementPosition - headerOffset; 490 491 window.scrollTo({ 492 top: offsetPosition, 493 behavior: 'smooth' 494 }); 495 }); 496}); 497 498/* =================================== */ 499
500/* ===== Modal: РегиÑÑÑаÑÐ¸Ñ ===== */ 501(() => { 502 const modals = Array.from( 503 document.querySelectorAll('#reg-modal, #reg-modal-2, #reg-modal-3') 504 ); 505 if (!modals.length) return; 506 507 const html = document.documentElement; 508 const body = document.body; 509 510 // Ðаппинг: на какÑÑ ÐºÐ½Ð¾Ð¿ÐºÑ ÐºÐ°ÐºÐ¾Ð¹ модал 511 // ÐодÑÑÐ°Ð²Ñ Ñвои ÑелекÑоÑÑ ÐºÐ½Ð¾Ð¿Ð¾Ðº (или href), еÑли оÑлиÑаÑÑÑÑ. 512 const triggersMap = [ 513 { selectors: 'a.js-reg-alt, a[href="#reg-alt"]', modalId: 'reg-modal' }, 514 { selectors: 'a.js-reg-alt-2, a[href="#reg-alt-2"]', modalId: 'reg-modal-2' }, 515 { selectors: 'a.js-reg-alt-3, a[href="#reg-alt-3"]', modalId: 'reg-modal-3' }, 516 ]; 517 518 const modalById = new Map(modals.map(m => [m.id, m])); 519 520 let activeModal = null; 521 let prevFocus = null; 522 523 // ÐелегиÑование по кнопкам оÑкÑÑÑÐ¸Ñ 524 document.addEventListener('click', (e) => { 525 const hit = triggersMap.find(x => e.target.closest(x.selectors)); 526 if (!hit) return; 527 528 const modal = modalById.get(hit.modalId); 529 if (!modal) return; 530 531 e.preventDefault(); 532 open(modal); 533 }); 534 535 // ÐакÑÑÑие + бÑкдÑоп Ð´Ð»Ñ Ð²ÑÐµÑ Ð¼Ð¾Ð´Ð°Ð»Ð¾Ðº 536 modals.forEach((modal) => { 537 modal.addEventListener('click', (e) => { 538 if ( 539 e.target.closest('[data-close="modal"], .modal__close') || 540 e.target.classList.contains('modal__backdrop') 541 ) { 542 close(modal); 543 } 544 }); 545 }); 546 547 // Esc + trap focus 548 document.addEventListener('keydown', (e) => { 549 if (!activeModal || !isOpen(activeModal)) return; 550 551 if (e.key === 'Escape') close(activeModal); 552 if (e.key === 'Tab') trapFocus(e, activeModal); 553 }); 554 555 function isOpen(modal) { 556 return modal.classList.contains('is-open'); 557 } 558 559 function open(modal) { 560 if (activeModal && activeModal !== modal) close(activeModal); 561 if (isOpen(modal)) return; 562 563 prevFocus = document.activeElement; 564 activeModal = modal; 565 566 modal.classList.add('is-open'); 567 html.classList.add('modal-lock'); 568 body.classList.add('modal-lock'); 569 570 focusFirst(modal); 571 } 572 573 function close(modal) { 574 if (!isOpen(modal)) return; 575 576 modal.classList.remove('is-open'); 577 578 // Ñнимаем lock ÑолÑко еÑли болÑÑе Ð½ÐµÑ Ð¾ÑкÑÑÑÑÑ Ð¼Ð¾Ð´Ð°Ð»Ð¾Ðº 579 if (!modals.some(isOpen)) { 580 html.classList.remove('modal-lock'); 581 body.classList.remove('modal-lock'); 582 activeModal = null; 583 584 if (prevFocus && typeof prevFocus.focus === 'function') { 585 prevFocus.focus({ preventScroll: true }); 586 } 587 } else { 588 activeModal = modals.find(isOpen) || null; 589 } 590 } 591 592 function focusables(modal) { 593 return modal.querySelectorAll( 594 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 595 ); 596 } 597 598 function focusFirst(modal) { 599 const dialog = modal.querySelector('.modal__dialog'); 600 const f = focusables(modal); 601 (f[0] || dialog || modal).focus?.({ preventScroll: true }); 602 } 603 604 function trapFocus(e, modal) { 605 const f = Array.from(focusables(modal)); 606 if (!f.length) return; 607 608 const i = f.indexOf(document.activeElement); 609 if (e.shiftKey && i <= 0) { 610 e.preventDefault(); 611 f[f.length - 1].focus(); 612 } else if (!e.shiftKey && i === f.length - 1) { 613 e.preventDefault(); 614 f[0].focus(); 615 } 616 } 617})(); 618 619/* =================================== */ 620document.addEventListener('DOMContentLoaded', function () { 621 const tabs = document.querySelectorAll('.program__tabs-btn'); 622 const panes = document.querySelectorAll('.program-tabs__pane'); 623 624 if (!tabs.length || !panes.length) return; 625 626 const activateTab = (target) => {
627 tabs.forEach((tab) => { 628 tab.classList.toggle('is-active', tab.dataset.target === target); 629 }); 630 631 panes.forEach((pane) => { 632 pane.classList.toggle('is-active', pane.dataset.tab === target); 633 }); 634 }; 635 636 tabs.forEach((tab) => { 637 tab.addEventListener('click', function (e) { 638 e.preventDefault(); 639 const target = this.dataset.target; 640 if (!target) return; 641 activateTab(target); 642 }); 643 }); 644}); 645 646/* =================================== */ 647document.addEventListener('DOMContentLoaded', () => { 648 const mq = window.matchMedia('(max-width: 1024px)'); // mobile/tablet 649 const isMobile = () => mq.matches; 650 651 const left = document.querySelector('.topics__col-left'); 652 const wrap = document.querySelector('.topics__wrap'); 653 if (!left || !wrap) return; 654 655 const TOP = 110; 656 let raf = 0; 657 658 function reset() { 659 left.classList.remove('is-fixed'); 660 left.style.left = ''; 661 left.style.width = ''; 662 left.style.transform = ''; 663 } 664 665 function update() { 666 raf = 0; 667 668 // mobile/tablet â ÐЫÐÐ 669 if (isMobile()) { 670 reset(); 671 return; 672 } 673 674 const wrapRect = wrap.getBoundingClientRect(); 675 const leftRect = left.getBoundingClientRect(); 676 677 // пока ÑекÑÐ¸Ñ Ð½Ðµ доÑла до TOP â обÑÑное поведение 678 if (wrapRect.top > TOP) { 679 reset(); 680 return; 681 } 682 683 left.classList.add('is-fixed'); 684 left.style.left = wrapRect.left + 'px'; 685 left.style.width = leftRect.width + 'px'; 686 687 // ÑÑоп Ð²Ð½Ð¸Ð·Ñ ÑекÑии 688 const maxShift = wrapRect.height - leftRect.height; 689 const currentShift = Math.min(maxShift, Math.max(0, TOP - wrapRect.top)); 690 left.style.transform = `translateY(${currentShift}px)`; 691 } 692 693 function tick() { 694 if (!raf) raf = requestAnimationFrame(update); 695 } 696 697 window.addEventListener('scroll', tick, { passive: true }); 698 window.addEventListener('resize', tick); 699 mq.addEventListener?.('change', tick); 700 701 update(); 702}); 703/* =================================== */ 704/* Script for the Pop-Up start*/ 705(function(){ 706 const OPEN_IDS = new Set(['#variant-1ru', '#variant-2ru']); 707 708 function openModal(id){ 709 const m = document.querySelector(id); 710 if (!m) return; 711 m.classList.add('is-open'); 712 m.setAttribute('aria-hidden', 'false'); 713 document.body.classList.add('no-scroll'); 714 } 715 function closeModal(m){ 716 if (!m) return; 717 m.classList.remove('is-open'); 718 m.setAttribute('aria-hidden', 'true'); 719 document.body.classList.remove('no-scroll'); 720 if (location.hash && (location.hash === '#variant-1ru' || location.hash === '#variant-2ru')) { 721 history.replaceState('', document.title, location.pathname + location.search); 722 } 723 } 724 725 document.addEventListener('click', (e) => { 726 const a = e.target.closest('a[href^="#"]'); 727 if (a && OPEN_IDS.has(a.getAttribute('href'))) { 728 e.preventDefault(); 729 openModal(a.getAttribute('href')); 730 return; 731 } 732 if (e.target.matches('[data-close]')) { 733 closeModal(e.target.closest('.modal')); 734 return; 735 } 736 const modal = e.target.classList.contains('modal') ? e.target : null; 737 if (modal) closeModal(modal); 738 }); 739 740 document.addEventListener('keydown', (e) => { 741 if (e.key === 'Escape') { 742 document.querySelectorAll('.modal.is-open').forEach(closeModal); 743 } 744 }); 745 746 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 747 window.addEventListener('hashchange', () => { 748 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 749 }); 750})(); 751 752/* Script for the Pop-Up start*/ 753(function(){ 754 const OPEN_IDS = new Set(['#variant-eu', '#variant-2eu']); 755 756 function closeModal(m){ 757 if (!m) return; 758 m.classList.remove('is-open'); 759 m.setAttribute('aria-hidden', 'true'); 760 document.body.classList.remove('no-scroll'); 761 762 // ÑиÑÑим hash ÑолÑко Ð´Ð»Ñ eu-ваÑианÑов 763 if (location.hash && (location.hash === '#variant-eu' || location.hash === '#variant-2eu')) { 764 history.replaceState('', document.title, location.pathname + location.search); 765 } 766 } 767 768 // ÐовÑй Ñ ÐµÐ»Ð¿ÐµÑ â закÑÑÑÑ Ð²Ñе оÑкÑÑÑÑе модалки (вклÑÑÐ°Ñ Ñе, ÑÑо ÑÑ-ÑкÑÐ¸Ð¿Ñ Ð¾ÑкÑÑл) 769 function closeAllModals() { 770 document.querySelectorAll('.modal.is-open').forEach(closeModal); 771 } 772 773 function openModal(id){ 774 // пÑи акÑиваÑии eu-попапа ÑнаÑала гаÑим вÑÑ 775 closeAllModals(); 776 777 const m = document.querySelector(id); 778 if (!m) return; 779 m.classList.add('is-open'); 780 m.setAttribute('aria-hidden', 'false'); 781 document.body.classList.add('no-scroll'); 782 } 783 784 document.addEventListener('click', (e) => { 785 const a = e.target.closest('a[href^="#"]'); 786 if (a && OPEN_IDS.has(a.getAttribute('href'))) { 787 e.preventDefault(); 788 openModal(a.getAttribute('href')); 789 return; 790 } 791 if (e.target.matches('[data-close]')) { 792 closeModal(e.target.closest('.modal')); 793 return; 794 } 795 const modal = e.target.classList.contains('modal') ? e.target : null; 796 if (modal) closeModal(modal); 797 }); 798 799 document.addEventListener('keydown', (e) => { 800 if (e.key === 'Escape') { 801 document.querySelectorAll('.modal.is-open').forEach(closeModal); 802 } 803 }); 804 805 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 806 window.addEventListener('hashchange', () => { 807 if (OPEN_IDS.has(location.hash)) openModal(location.hash); 808 }); 809})(); 810/* =================================== */ 811 812 813 814 815/* PAYMODAL â ONLY RADIOS (paymode/currency/months) + FD SYNC + STEP2 OVERLAY + MOBILE STEP2 FIX + SCROLL HINTS */ 816(function () { 817 const qs = (sel, root = document) => root.querySelector(sel); 818 const qsa = (sel, root = document) => Array.from(root.querySelectorAll(sel)); 819 820 const OPEN_MODAL_SEL = '.paymodal.is-open'; 821 const FD_IFRAME_SEL = 'iframe[src*="formdesigner"], iframe[src*="formdesigner."]'; 822 823 // Modal id -> package key in data/info.json. Prices/deadline live only in 824 // info.json now (support edits that file, everything below reads from it).
825 const MODAL_PACKAGE = { 826 payModalBasic: 'standard', 827 payModalTrainer: 'advanced', 828 payModalTransBasic: 'standard_trans', 829 payModalTransTrainer: 'advanced_trans' 830 }; 831 832 const getSiteInfo = () => window.__SITE_INFO__ || null; 833 const getCurrencySign = () => getSiteInfo()?.currency || 'â¬'; 834 const getPackageData = (key) => getSiteInfo()?.packages?.[key] || null; 835 const getModalPackageKey = (modalOrId) => { 836 const id = typeof modalOrId === 'string' ? modalOrId : modalOrId?.id; 837 return MODAL_PACKAGE[id] || null; 838 }; 839 840 // window.__PAYMODAL_DEBUG = true; 841 const isDebug = () => window.__PAYMODAL_DEBUG === true; 842 const log = (...a) => { if (isDebug()) console.log('[PAYMODAL]', ...a); }; 843 844 const lockScroll = (on) => { 845 document.documentElement.classList.toggle('no-scroll', on); 846 document.body.classList.toggle('no-scroll', on); 847 }; 848 849 const getOpenModal = () => qs(OPEN_MODAL_SEL); 850 const getFdIframe = (modal) => modal?.querySelector(FD_IFRAME_SEL) || null; 851 852 const normalizeNumber = (v) => { 853 if (typeof v === 'number') return v; 854 if (typeof v === 'string') return Number(v.replace(',', '.').trim()); 855 return Number(v); 856 }; 857 858 const formatPrice = (v) => { 859 const n = normalizeNumber(v); 860 if (!Number.isFinite(n)) return '0'; 861 return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/\.00$/, '').replace(/(\.\d)0$/, '$1'); 862 }; 863 864 const renderModalPrices = (modal) => { 865 if (!modal) return; 866 867 const pkgKey = getModalPackageKey(modal); 868 const pkg = pkgKey && getPackageData(pkgKey); 869 if (!pkg) return; 870 871 const fullPrice = normalizeNumber(pkg.actual); 872 if (!Number.isFinite(fullPrice) || !fullPrice) return; 873 874 const fullNode = qs('.js-price-full', modal); 875 if (fullNode) { 876 fullNode.textContent = formatPrice(fullPrice); 877 } 878 879 const oldFullNode = qs('.js-price-old del, .js-price-old', modal); 880 if (oldFullNode && Number.isFinite(normalizeNumber(pkg.old))) { 881 oldFullNode.textContent = formatPrice(pkg.old); 882 } 883 884 const payType = getPayType(modal); 885 const months = Number(getMonthsSelected(modal) || 0); 886 887 const rassrNode = qs('.js-price-rassr', modal); 888 if (rassrNode) { 889 if (payType === 'installment' && months > 0) { 890 const installmentTotal = fullPrice * 1; 891 rassrNode.textContent = formatPrice(Math.ceil(installmentTotal / months)); 892 } else { 893 rassrNode.textContent = formatPrice(fullPrice); 894 } 895 } 896 }; 897 898 const rerenderAllModalPrices = () => { 899 qsa('.paymodal').forEach(renderModalPrices); 900 }; 901 902 // ---------- card prices (register section) ---------- 903 const renderCardPrice = (card) => { 904 if (!card) return; 905 const openBtn = qs('.paypack-open[data-modal]', card); 906 const pkgKey = openBtn && getModalPackageKey(openBtn.dataset.modal); 907 const pkg = pkgKey && getPackageData(pkgKey); 908 if (!pkg) return; 909 910 const currency = getCurrencySign(); 911 const oldEl = qs('.register__option-prise-del', card); 912 const actualEl = qs('.register__option-prise-actual', card); 913 const instEl = qs('.js-installment-from', card); 914 915 if (oldEl && Number.isFinite(normalizeNumber(pkg.old))) { 916 oldEl.textContent = `${formatPrice(pkg.old)} ${currency}`; 917 } 918 if (actualEl && Number.isFinite(normalizeNumber(pkg.actual))) { 919 actualEl.textContent = `${formatPrice(pkg.actual)} ${currency}`; 920 } 921 if (instEl && Number.isFinite(normalizeNumber(pkg.actual))) { 922 const months = 9; 923 instEl.textContent = `Ð¾Ñ ${formatPrice(Math.ceil(pkg.actual / months))} ${currency}/меÑ`; 924 } 925 }; 926 927 const rerenderAllCardPrices = () => { 928 qsa('#register .register__card').forEach(renderCardPrice); 929 }; 930 931 // ---------- standard/advanced switcher (3rd card in #register and #format) ---------- 932 const initPriceSwitchers = () => { 933 qsa('[data-price-switch]').forEach((card) => { 934 const btns = qsa('[data-switch]', card); 935 const openBtn = qs('.paypack-open[data-modal]', card); 936 if (!btns.length) return; 937 938 const apply = (mode) => { 939 btns.forEach((b) => b.classList.toggle('is-active', b.dataset.switch === mode)); 940 if (openBtn) { 941 const modalId = mode === 'advanced' ? card.dataset.advancedModal : card.dataset.standardModal; 942 if (modalId) openBtn.dataset.modal = modalId; 943 } 944 renderCardPrice(card); 945 }; 946 947 btns.forEach((b) => { 948 b.addEventListener('click', () => apply(b.dataset.switch)); 949 }); 950 951 apply('standard'); 952 }); 953 }; 954 955 // ---------- injected css: preloader + step2 overlay (no layout shift) ---------- 956 const ensureCss = () => { 957 if (qs('#paymodal-onlyradios-css')) return; 958 const s = document.createElement('style'); 959 s.id = 'paymodal-onlyradios-css'; 960 s.textContent = ` 961.paymodal__dialog{ position:relative; } 962 963.paymodal__submitting{
964 position:absolute; inset:0; z-index:50; 965 display:none; align-items:center; justify-content:center; 966 background: rgb(var(--color-white-rgb) / .72); 967 backdrop-filter: blur(3px); 968} 969.paymodal.is-submitting .paymodal__submitting{ display:flex; } 970.paymodal__spinner{ 971 width:34px; height:34px; border-radius:999px; 972 border:3px solid rgb(var(--color-black-rgb) / .18); 973 border-top-color: rgb(var(--color-black-rgb) / .55); 974 animation: paymodalSpin .8s linear infinite; 975} 976@keyframes paymodalSpin{ to{ transform:rotate(360deg);} } 977 978.paymodal__step--2{ display:none; } 979 980/* STEP2 OVERLAY (ÑенÑÑ) */ 981.paymodal.is-step2-soft .paymodal__step--1{ 982 visibility:hidden; 983 pointer-events:none; 984} 985 986.paymodal.is-step2-soft .paymodal__step--2.is-active{ 987 display:flex !important; 988 position:absolute !important; 989 inset:0 !important; 990 z-index:10 !important; 991 gap: 50px !important; 992 993 align-items:start !important; 994 justify-content:center !important; 995 996 padding:40px; 997 box-sizing:border-box; 998} 999 1000/* огÑаниÑиваем ÑиÑÐ¸Ð½Ñ ÐºÐ¾Ð½ÑенÑа, ÑÑÐ¾Ð±Ñ Ð½Ðµ ÑазÑезжалоÑÑ */ 1001.paymodal.is-step2-soft .paymodal__thx{ 1002 width:100%; 1003} 1004 1005@media (max-width: 767px) { 1006 .paymodal.is-step2-soft .paymodal__step--2.is-active { 1007 gap: 0px !important; 1008 } 1009} 1010 1011/* ===== MOBILE SCROLL FIX + SCROLL HINTS ===== */ 1012@media (max-width: 767px){ 1013 1014 /* делаем Ð¼Ð¾Ð´Ð°Ð»ÐºÑ Ð½Ð¾ÑмалÑнÑм ÑкÑолл-конÑейнеÑом */ 1015 .paymodal__dialog{ 1016 max-height: 100dvh; 1017 overflow-y: auto; 1018 -webkit-overflow-scrolling: touch; 1019 overscroll-behavior: contain; 1020 } 1021 1022 /* Ñаг2 на мобиле ÑикÑиÑÑем к viewport, ÑÑÐ¾Ð±Ñ Ð½Ðµ завиÑеÑÑ Ð¾Ñ Ð¿ÑоÑкÑолла Ñага1 */ 1023 .paymodal.is-step2-soft .paymodal__step--2.is-active{ 1024 position: fixed !important; 1025 inset: 0 !important; 1026 height: 100dvh !important; 1027 overflow-y: auto !important; 1028 -webkit-overflow-scrolling: touch; 1029 overscroll-behavior: contain; 1030 /* padding: 20px !important; */ 1031 } 1032 1033 /* подÑказки ÑкÑолла (веÑÑ /низ) */ 1034 .paymodal__scrollhint{ 1035 position: relative; 1036 } 1037 .paymodal__scrollhint::before, 1038 .paymodal__scrollhint::after{ 1039 content:""; 1040 position: sticky; 1041 left: 0; 1042 right: 0; 1043 height: 22px; 1044 pointer-events: none; 1045 z-index: 60; 1046 display: none; 1047 } 1048 .paymodal__scrollhint::before{ 1049 top: 0; 1050 background: linear-gradient(to bottom, rgb(var(--color-white-rgb) / .92), rgb(var(--color-white-rgb) / 0)); 1051 } 1052 .paymodal__scrollhint::after{ 1053 bottom: 0; 1054 background: linear-gradient(to top, rgb(var(--color-white-rgb) / .92), rgb(var(--color-white-rgb) / 0)); 1055 } 1056 1057 .paymodal__scrollhint.has-topfade::before{ display:block; } 1058 .paymodal__scrollhint.has-botfade::after{ display:block; } 1059} 1060 1061 1062`; 1063 document.head.appendChild(s); 1064 }; 1065 1066 const ensureSubmittingOverlay = (modal) => { 1067 ensureCss(); 1068 const dialog = qs('.paymodal__dialog', modal) || modal; 1069 if (qs('.paymodal__submitting', dialog)) return; 1070 const wrap = document.createElement('div'); 1071 wrap.className = 'paymodal__submitting'; 1072 wrap.innerHTML = `<div class="paymodal__spinner" aria-label="ÐбÑабоÑка"></div>`; 1073 dialog.appendChild(wrap); 1074 }; 1075 1076 const setSubmitting = (modal, on) => { 1077 if (!modal) return; 1078 ensureSubmittingOverlay(modal); 1079 modal.classList.toggle('is-submitting', !!on); 1080 modal.setAttribute('aria-busy', on ? 'true' : 'false'); 1081 }; 1082 1083 // ---------- helpers: mobile + scroll hints ---------- 1084 const isMobile = () => window.matchMedia('(max-width: 767px)').matches; 1085 1086 const getStep2 = (modal) => qs('.paymodal__step--2', modal); 1087 const getStep1 = (modal) => qs('.paymodal__step--1', modal); 1088 1089 const pickScrollEl = (modal, stepEl) => { 1090 if (!stepEl) return null; 1091 if (stepEl === getStep2(modal) && isMobile()) return stepEl; 1092 return qs('.paymodal__dialog', modal) || stepEl; 1093 }; 1094 1095 const updateScrollHint = (scrollEl) => { 1096 if (!scrollEl) return; 1097 1098 const max = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight); 1099 const y = scrollEl.scrollTop; 1100 1101 scrollEl.classList.add('paymodal__scrollhint'); 1102 1103 if (max <= 2) { 1104 scrollEl.classList.remove('has-topfade', 'has-botfade'); 1105 return; 1106 } 1107 1108 scrollEl.classList.toggle('has-topfade', y > 2); 1109 scrollEl.classList.toggle('has-botfade', y < max - 2); 1110 }; 1111 1112 const bindScrollHint = (scrollEl) => { 1113 if (!scrollEl || scrollEl.__pmHintBound) return; 1114 scrollEl.__pmHintBound = true; 1115 1116 const onScroll = () => updateScrollHint(scrollEl); 1117 scrollEl.addEventListener('scroll', onScroll, { passive: true }); 1118 1119 requestAnimationFrame(onScroll); 1120 setTimeout(onScroll, 50); 1121 }; 1122 1123 const showStep2Soft = (modal, on = true) => { 1124 modal.classList.toggle('is-step2-soft', !!on); 1125 qs('.paymodal__step--2', modal)?.classList.toggle('is-active', !!on); 1126 qs('.paymodal__step--1', modal)?.classList.toggle('is-active', !on); 1127 1128 if (on) { 1129 const step2 = getStep2(modal); 1130 const scrollEl2 = pickScrollEl(modal, step2); 1131 1132 if (scrollEl2) scrollEl2.scrollTop = 0; 1133 1134 bindScrollHint(scrollEl2); 1135 updateScrollHint(scrollEl2); 1136 } else { 1137 const step1 = getStep1(modal); 1138 const scrollEl1 = pickScrollEl(modal, step1); 1139 1140 bindScrollHint(scrollEl1); 1141 updateScrollHint(scrollEl1); 1142 } 1143 }; 1144 1145 // ---------- months block + title ---------- 1146 const getMonthsWrap = (modal) => { 1147 const node = qs('[data-months]', modal); 1148 if (!node) return null; 1149 return node.closest('.paymodal__box, .paymodal__group, .paymodal__radrow, .paymodal__section, .paymodal__card') || node; 1150 }; 1151 1152 const toggleInstallmentTitle = (modal, on) => { 1153 qsa('*', modal).forEach((el) => { 1154 if (!el || el.children.length) return; 1155 const t = (el.textContent || '').trim().toLowerCase(); 1156 if (t === 'ÑÑок ÑаÑÑÑоÑки') el.style.display = on ? '' : 'none'; 1157 }); 1158 }; 1159 1160 const setMonthsBlockVisible = (modal, on) => { 1161 const wrap = getMonthsWrap(modal); 1162 if (wrap) { 1163 wrap.style.display = on ? '' : 'none'; 1164 wrap.setAttribute('aria-hidden', on ? 'false' : 'true'); 1165 } 1166 toggleInstallmentTitle(modal, on); 1167 }; 1168 1169 const clearMonthsSelection = (modal) => { 1170 qsa('[data-months] input[type="radio"]', modal).forEach(r => { r.checked = false; }); 1171 }; 1172 1173 // ---------- collect only radios ---------- 1174 const getCurrency = (modal) => { 1175 const checked = modal.querySelector('.paymodal__radrow input[name^="cur_"]:checked'); 1176 if (!checked) return ''; 1177 const t = (checked.closest('label')?.innerText || '').toLowerCase(); 1178 if (t.includes('евÑо')) return 'eur'; 1179 if (t.includes('ÑÑб')) return 'rub'; 1180 if (t.includes('кÑип')) return 'crypto'; 1181 return ''; 1182 }; 1183 1184 const getPayType = (modal) => { 1185 return modal.querySelector('.paymodal__seg-btn.is-active[data-paymode]')?.dataset?.paymode || 'installment'; 1186 }; 1187 1188 const getMonthsSelected = (modal) => { 1189 return modal.querySelector('[data-months] input[type="radio"]:checked')?.dataset?.month || ''; 1190 }; 1191 1192 const collectRadiosOnly = (modal) => { 1193 const pay_type = getPayType(modal); 1194 const months = pay_type === 'installment' ? getMonthsSelected(modal) : ''; 1195 const currency = getCurrency(modal); 1196 return { pay_type, months, currency }; 1197 }; 1198 1199 const sendToFdRadiosOnly = (modal) => { 1200 const iframe = getFdIframe(modal); 1201 if (!iframe?.contentWindow) return false;
1202 1203 const payload = collectRadiosOnly(modal); 1204 iframe.contentWindow.postMessage({ type: 'paymodal-data', payload }, '*'); 1205 log('-> FD paymodal-data (RADIOS ONLY)', payload); 1206 return true; 1207 }; 1208 1209 const scheduleSync = (modal) => { 1210 let tries = 0; 1211 const maxTries = 40; 1212 const t = setInterval(() => { 1213 tries += 1; 1214 const ok = sendToFdRadiosOnly(modal); 1215 if (ok || tries >= maxTries) clearInterval(t); 1216 }, 250); 1217 }; 1218 1219 // ---------- pay ui (ONLY) ---------- 1220 const setMonths = (month) => { 1221 if (typeof window.setRassrMonths === 'function') { 1222 const n = parseInt(month, 10); 1223 if (!Number.isNaN(n)) window.setRassrMonths(n); 1224 } 1225 }; 1226 1227 const setPayMode = (root, mode) => { 1228 qsa('[data-paymode]', root).forEach(btn => { 1229 btn.classList.toggle('is-active', btn.dataset.paymode === mode); 1230 }); 1231 1232 qsa('.payment-block', root).forEach(b => { 1233 b.classList.toggle('active', b.dataset.name === mode); 1234 }); 1235 1236 const modal = root.closest('.paymodal'); 1237 if (!modal) return; 1238 1239 if (mode === 'full') { 1240 setMonthsBlockVisible(modal, false); 1241 clearMonthsSelection(modal); 1242 if (typeof window.setRassrMonths === 'function') window.setRassrMonths(''); 1243 } else { 1244 setMonthsBlockVisible(modal, true); 1245 const checked = qs('[data-months] input[type="radio"]:checked', modal); 1246 if (!checked) { 1247 const r5 = qs('[data-months] input[type="radio"][data-month="5"]', modal); 1248 if (r5) r5.checked = true; 1249 setMonths(5); 1250 } else { 1251 setMonths(checked.dataset.month); 1252 } 1253 } 1254 1255 renderModalPrices(modal); 1256 sendToFdRadiosOnly(modal); 1257 }; 1258 1259 // ---------- modal lifecycle ---------- 1260 const resetModal = (modal) => { 1261 showStep2Soft(modal, false); 1262 setSubmitting(modal, false); 1263 1264 const root = qs('[data-pay-root]', modal); 1265 if (root) { 1266 setPayMode(root, 'installment'); 1267 const r5 = qs('[data-months] input[type="radio"][data-month="5"]', root); 1268 if (r5) r5.checked = true; 1269 setMonths(5); 1270 setMonthsBlockVisible(modal, true); 1271 } 1272 1273 renderModalPrices(modal); 1274 1275 const s1 = pickScrollEl(modal, getStep1(modal)); 1276 bindScrollHint(s1); 1277 updateScrollHint(s1); 1278 }; 1279 1280 const openModal = (id) => { 1281 const modal = document.getElementById(id); 1282 if (!modal) return; 1283 1284 resetModal(modal); 1285 1286 modal.classList.add('is-open'); 1287 modal.setAttribute('aria-hidden', 'false'); 1288 lockScroll(true); 1289 1290 scheduleSync(modal); 1291 renderModalPrices(modal); 1292 1293 const s1 = pickScrollEl(modal, getStep1(modal)); 1294 bindScrollHint(s1); 1295 updateScrollHint(s1); 1296 }; 1297 1298 const closeModal = (modal) => { 1299 showStep2Soft(modal, false); 1300 modal.classList.remove('is-open'); 1301 modal.setAttribute('aria-hidden', 'true'); 1302 lockScroll(false); 1303 }; 1304 1305 // ---------- open/close ---------- 1306 document.addEventListener('click', (e) => { 1307 const openBtn = e.target.closest('.paypack-open'); 1308 if (openBtn) { 1309 openModal(openBtn.dataset.modal); 1310 return; 1311 } 1312 1313 const closeBtn = e.target.closest('[data-close]'); 1314 if (closeBtn) { 1315 const modal = closeBtn.closest('.paymodal'); 1316 if (modal) closeModal(modal); 1317 return; 1318 } 1319 }); 1320 1321 document.addEventListener('keydown', (e) => { 1322 if (e.key !== 'Escape') return; 1323 qsa('.paymodal.is-open').forEach(closeModal); 1324 }); 1325 1326 // ---------- inside modal: ONLY radio controls ---------- 1327 qsa('.paymodal [data-pay-root]').forEach((root) => { 1328 root.addEventListener('click', (e) => { 1329 const b = e.target.closest('[data-paymode]'); 1330 if (!b) return; 1331 1332 e.preventDefault(); 1333 setPayMode(root, b.dataset.paymode); 1334 }); 1335 1336 root.addEventListener('change', (e) => { 1337 const modal = root.closest('.paymodal'); 1338 if (!modal) return; 1339 1340 const r = e.target.closest('[data-months] input[type="radio"][data-month]'); 1341 if (r) { 1342 setMonths(r.dataset.month); 1343 renderModalPrices(modal); 1344 sendToFdRadiosOnly(modal); 1345 return; 1346 } 1347 1348 if (e.target.closest('.paymodal__radrow')) { 1349 renderModalPrices(modal); 1350 sendToFdRadiosOnly(modal); 1351 } 1352 }); 1353 }); 1354
1355 // ---------- FD events: invalid/submit/success (tokenized) ---------- 1356 let pmTimer = null; 1357 let pmToken = 0; 1358 1359 window.addEventListener('message', (e) => { 1360 const d = e.data; 1361 if (!d || !d.type) return; 1362 1363 const origin = String(e.origin || ''); 1364 const isFromFD = origin.includes('formdesigner.'); 1365 if (!isFromFD && !(isDebug() && origin === window.location.origin)) return; 1366 1367 const modal = getOpenModal(); 1368 if (!modal) return; 1369 1370 const token = Number(d.payload?.token || d.token || 0); 1371 1372 if (d.type === 'fd-form-invalid') { 1373 clearTimeout(pmTimer); 1374 setSubmitting(modal, false); 1375 showStep2Soft(modal, false); 1376 return; 1377 } 1378 1379 if (d.type === 'fd-form-submit') { 1380 // ÐÑли пÑедÑдÑÑий ÑÐ°Ð±Ð¼Ð¸Ñ Ñак и не полÑÑил оÑвеÑа (invalid/success), 1381 // овеÑлей "ÐбÑабоÑка..." Ð·Ð°Ð²Ð¸Ñ â ÑÑÐ°Ð·Ñ Ñнимаем его вмеÑÑо повÑоÑного 1382 // 20-ÑекÑндного ожиданиÑ, ÑÑÐ¾Ð±Ñ Ð¿Ð¾Ð´ ним ÑÑала видна оÑибка валидаÑии. 1383 if (modal.classList.contains('is-submitting')) { 1384 clearTimeout(pmTimer); 1385 setSubmitting(modal, false); 1386 showStep2Soft(modal, false); 1387 return; 1388 } 1389 1390 pmToken = token || (pmToken + 1); 1391 1392 setSubmitting(modal, true); 1393 clearTimeout(pmTimer); 1394 1395 pmTimer = setTimeout(() => { 1396 setSubmitting(modal, false); 1397 showStep2Soft(modal, false); 1398 }, 3000); 1399 1400 return; 1401 } 1402 1403 if (d.type === 'fd-form-success') { 1404 if (token && pmToken && token !== pmToken) return; 1405 1406 clearTimeout(pmTimer); 1407 setSubmitting(modal, false); 1408 showStep2Soft(modal, true); 1409 return; 1410 } 1411 }, false); 1412 1413 rerenderAllModalPrices(); 1414 rerenderAllCardPrices(); 1415 initPriceSwitchers(); 1416 1417})(); 1418 1419 1420/* =================================== */ 1421/* Danylo Oliinyk - https://iamdev.lol/ */ 1422/* =====October 2025 For Statoplan =====*/ 1423/* =================================== */ 1424/* =================================== */ 1425/* =================================== */
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.