1document.addEventListener('DOMContentLoaded', function () { 2 3 // --- 1. Base constants --- 4 const breakpointMobile = 768; // breakpoint for mobile card accordion 5 const CARD_ITEM_CLASS = '.price__card-item'; 6 const ACCORDION_SWITCH_CLASS = '.price-accordion-switch'; 7 const ACCORDION_BODY_CLASS = '.price-accordion-body'; 8 const PLAN_SWITCH_CLASS = '.plan-switch'; 9 const SWITCH_MONTH_CLASS = '.swich-month'; 10 const SWITCH_YEAR_CLASS = '.swich-year'; 11 let activeMainPlanType = 'data'; // 'sql' or 'data' 12 13 // --- 2. DOM-elements (cache) --- 14 const mainArticle = document.querySelector('.switchable-cards'); 15 const plansSwitcher = mainArticle.querySelector('.plans-swicher'); 16 const planSwichs = Array.from(plansSwitcher.querySelectorAll('.plan-swich')); 17 const cardList = mainArticle.querySelector('.price__card-list'); 18 const allCards = Array.from(cardList.querySelectorAll(CARD_ITEM_CLASS)); 19 const noticeElement = mainArticle.querySelector('.notice.notice--light'); 20 21 const pseudoSelectContainer = document.querySelector('.price-plans-pseudoselect'); 22 const realTabsContainer = document.querySelector('.isp-real-tabs'); 23 const visibleRealTabsContainer = document.querySelector('.isp-visible-real-tabs'); 24 const pseudoselectButton = pseudoSelectContainer ? pseudoSelectContainer.querySelector('.pseudoselect-button') : null; 25 const pseudoselectLabel = pseudoSelectContainer ? pseudoSelectContainer.querySelector('.pseudoselect-label') : null; 26 const pseudoselectOptionsList = pseudoSelectContainer ? pseudoSelectContainer.querySelector('.pseudoselect-options') : null; 27 const realTabButtons = realTabsContainer ? Array.from(realTabsContainer.querySelectorAll('.su-tabs-nav span')) : []; 28 29 // Card groups 30 const lightCard = allCards.find(card => card.dataset.plan === 'full-light'); 31 const startCard = allCards.find(card => card.dataset.plan === 'full-start'); 32 const sqlCards = allCards.filter(card => card.dataset.plan === 'sql'); 33 34 35 const dataLightCards = allCards.filter(card => card.dataset.plan === 'data' && card.dataset.planGroup === 'light'); 36 const dataStarterCards = allCards.filter(card => card.dataset.plan === 'data' && card.dataset.planGroup === 'starter'); 37 38 const dataCardPairs = { 39 'light': { 40 month: dataLightCards.find(card => card.dataset.duration === 'month'), 41 year: dataLightCards.find(card => card.dataset.duration === 'year') 42 }, 43 'starter': { 44 month: dataStarterCards.find(card => card.dataset.duration === 'month'), 45 year: dataStarterCards.find(card => card.dataset.duration === 'year') 46 } 47 }; 48 49 function debounce(func, delay) { 50 let timeout; 51 return function (...args) { 52 const context = this; 53 clearTimeout(timeout); 54 timeout = setTimeout(() => func.apply(context, args), delay); 55 }; 56 } 57 58 function slideToggle(element, duration = 300) { 59 60 if (element.style.display === 'none' || element.style.display === '') { 61 // Open, make visible for scrollHeight and auto to take the full height 62 element.style.display = 'flex'; 63 element.style.height = 'auto'; 64 65 // Forced reflow 66 // This should force the browser to recalculate layout and scrollHeight. 67 const computedStyle = window.getComputedStyle(element); 68 const initialHeight = element.scrollHeight; // Getting the actual height of the content 69 70 element.style.height = '0'; // for starting animation 71 element.style.overflow = 'hidden'; 72 element.style.transition = `height ${duration}ms ease-in-out`; 73 74 // Starting animation on the next frame (to trigger the transition) 75 requestAnimationFrame(() => { 76 element.style.height = initialHeight + 'px'; 77 }); 78 79 element.addEventListener('transitionend', function handler() { 80 element.style.height = ''; 81 element.style.overflow = ''; 82 element.style.transition = ''; 83 element.removeEventListener('transitionend', handler); 84 }, { 85 once: true 86 }); 87 88 } else { 89 90 const currentHeight = element.scrollHeight; 91 element.style.height = currentHeight + 'px'; 92 element.style.overflow = 'hidden'; 93 element.style.transition = `height ${duration}ms ease-in-out`; 94 95 // Starting animation on the next frame. 96 requestAnimationFrame(() => { 97 element.style.height = '0'; 98 }); 99 100 element.addEventListener('transitionend', function handler() { 101 element.style.display = 'none'; 102 element.style.height = ''; 103 element.style.overflow = ''; 104 element.style.transition = ''; 105 element.removeEventListener('transitionend', handler); 106 }, { 107 once: true 108 }); 109 } 110 } 111 112 // --- 4. Functions for logic control --- 113 114 // 4.1. Accordion Logic (OWN ACCORDION) 115 // It might be a bit complicated, but the accordion only loads on mobile and unloads on desktop, 116 // so it's better to make it a bit more complicated, but ensure that events don't accumulate when changing 117 // permissions. However, by changing the breakpoint, you can load it anywhere. 118 const accordionSwitchHandlers = new Map(); 119 120 function initializeAccordion() { 121 const allPriceCards = document.querySelectorAll(CARD_ITEM_CLASS); 122 123 // Clear all current handlers for all accordion-switches
124 accordionSwitchHandlers.forEach((handlers, element) => { 125 element.removeEventListener('click', handlers.clickHandler); 126 element.removeEventListener('keydown', handlers.keyHandler); 127 }); 128 accordionSwitchHandlers.clear(); // ÐÑиÑаем Map 129 130 if (window.innerWidth < breakpointMobile) { 131 // Mobile 132 allPriceCards.forEach(cardItem => { 133 const accordionSwitch = cardItem.querySelector(ACCORDION_SWITCH_CLASS); 134 const accordionBody = cardItem.querySelector(ACCORDION_BODY_CLASS); 135 136 if (!accordionSwitch || !accordionBody) return; 137 138 // Init state 139 const isOpenInitially = cardItem.dataset.accordionIsOpen === 'true'; 140 if (isOpenInitially) { 141 accordionSwitch.classList.add('open'); 142 accordionBody.style.display = 'block'; 143 accordionBody.style.height = 'auto'; // height set, if card is open 144 } else { 145 accordionSwitch.classList.remove('open'); 146 accordionBody.style.display = 'none'; 147 accordionBody.style.height = '0'; // height 0, if card closed 148 } 149 // also reset the transition and overflow if they may have remained from the previous animation. 150 accordionBody.style.transition = ''; 151 accordionBody.style.overflow = ''; 152 153 // bind a click handler, and a separate one for the keyboard, since we don't have a <button> but a <div> 154 // that simulates an open/close button. We bind the handler and accessibility to the key. 155 const clickHandler = function () { 156 accordionSwitch.classList.toggle('open'); 157 slideToggle(accordionBody); 158 cardItem.dataset.accordionIsOpen = accordionSwitch.classList.contains('open') ? 'true' : 'false'; 159 accordionSwitch.setAttribute('aria-expanded', accordionSwitch.classList.contains('open') ? 'true' : 'false'); 160 }; 161 162 // Keyboard event handler for div.accordion-switch 163 const keyHandler = function (e) { 164 if (e.key === 'Enter' || e.key === ' ') { 165 e.preventDefault(); 166 clickHandler(); 167 } 168 }; 169 accordionSwitch.addEventListener('click', clickHandler); 170 accordionSwitch.addEventListener('keydown', keyHandler); 171 accordionSwitchHandlers.set(accordionSwitch, { 172 clickHandler, 173 keyHandler 174 }); 175 }); 176 } else { 177 // Desktop 178 allPriceCards.forEach(cardItem => { 179 const accordionSwitch = cardItem.querySelector(ACCORDION_SWITCH_CLASS); 180 const accordionBody = cardItem.querySelector(ACCORDION_BODY_CLASS); 181 182 if (!accordionSwitch || !accordionBody) return; 183 184 accordionSwitch.classList.remove('open'); // all 'open' state removed on all cards 185 186 // Making the accordion bodies are always visible on the desktop 187 accordionBody.style.display = 'block'; 188 accordionBody.style.height = 'auto'; 189 // Reset any animation styles that may remain. 190 accordionBody.style.transition = ''; 191 accordionBody.style.overflow = ''; 192 193 // clear the saved state of the accordion, since it is no longer relevant on the desktop. 194 delete cardItem.dataset.accordionIsOpen; 195 196 if (accordionSwitch) { 197 accordionSwitch.removeAttribute('tabindex'); 198 accordionSwitch.removeAttribute('role'); 199 accordionSwitch.setAttribute('aria-expanded', 'false'); // Must always be false on desktop 200 } 201 }); 202 } 203 } 204 205 // 4.2. CardPlan Logic -- logic of working with plan cards 206 207 function hideAllDynamicCards() { 208 allCards.forEach(card => { 209 if (card !== lightCard && card !== startCard) { 210 card.style.display = 'none'; 211 } 212 }); 213 } 214 215 function updateDataCardsDisplayAndSwitches() { 216 Object.values(dataCardPairs).forEach(pair => { 217 const currentActiveDurationForPair = pair.month.dataset.activeDuration || 'month'; 218 219 if (currentActiveDurationForPair === 'month') { 220 if (pair.month) { 221 pair.month.style.display = 'flex'; 222 updateCardSwitches(pair.month, 'month'); 223 } 224 if (pair.year) pair.year.style.display = 'none'; 225 } else { // 'year' 226 if (pair.month) pair.month.style.display = 'none'; 227 if (pair.year) { 228 pair.year.style.display = 'flex'; 229 updateCardSwitches(pair.year, 'year'); 230 } 231 } 232 }); 233 } 234 235 function updateNoticeVisibility() { 236 if (noticeElement) { 237 noticeElement.style.display = (activeMainPlanType === 'data') ? 'block' : 'none'; 238 } 239 } 240 241 function updateCardVisibility() { 242 hideAllDynamicCards(); 243 244 if (lightCard) lightCard.style.display = 'flex'; 245 if (startCard) startCard.style.display = 'flex'; 246 247 if (activeMainPlanType === 'sql') { 248 sqlCards.forEach(card => card.style.display = 'flex');
249 Object.values(dataCardPairs).forEach(pair => { 250 updateCardSwitches(pair.month, 'month'); 251 updateCardSwitches(pair.year, 'month'); 252 }); 253 } else if (activeMainPlanType === 'data') { 254 sqlCards.forEach(card => card.style.display = 'none'); 255 updateDataCardsDisplayAndSwitches(); 256 } 257 258 updateNoticeVisibility(); 259 } 260 261 // Helper function for updating switcher classes on a specific card 262 const updateCardSwitches = (cardElement, duration) => { 263 if (!cardElement) return; 264 const swichMonth = cardElement.querySelector(SWITCH_MONTH_CLASS); 265 const swichYear = cardElement.querySelector(SWITCH_YEAR_CLASS); 266 if (swichMonth) { 267 if (duration === 'month') { 268 swichMonth.classList.add('active'); 269 } else { 270 swichMonth.classList.remove('active'); 271 } 272 } 273 if (swichYear) { 274 if (duration === 'year') { 275 swichYear.classList.add('active'); 276 } else { 277 swichYear.classList.remove('active'); 278 } 279 } 280 }; 281 282 // 4.3. Pseudo-Select Logic (Previous Version Integrated) 283 function initializePseudoselect() { 284 if (!pseudoselectButton || !realTabsContainer) { 285 console.warn('Pseudoselect or real tabs container not found.'); 286 return; 287 } 288 289 let isOpen = false; 290 let currentSelectedOption = null; 291 292 realTabButtons.forEach((realButton, index) => { 293 const optionText = realButton.getAttribute('title') || realButton.innerHTML || realButton.textContent.trim() || `Tab ${index + 1}`; 294 295 const pseudoOption = document.createElement('li'); 296 297 const tempDiv = document.createElement('div'); 298 tempDiv.innerHTML = optionText; // get the content with markup and ID 299 300 // Find all elements within the cloned content that have an ID and delete them 301 tempDiv.querySelectorAll('[id]').forEach(el => { 302 el.removeAttribute('id'); 303 }); 304 305 // Now use the modified content for pseudoOption 306 pseudoOption.className = 'pseudoselect-option'; 307 pseudoOption.setAttribute('role', 'option'); 308 pseudoOption.innerHTML = tempDiv.innerHTML; 309 pseudoOption.tabIndex = -1; 310 311 pseudoOption.dataset.realTabIndex = index; 312 313 pseudoOption.addEventListener('click', () => { 314 realTabButtons[index].click(); // Real Tab Trigger 315 316 if (currentSelectedOption) { 317 currentSelectedOption.classList.remove('selected'); 318 } 319 pseudoOption.classList.add('selected'); 320 currentSelectedOption = pseudoOption; 321 pseudoselectLabel.innerHTML = optionText; 322 isOpen = true; 323 toggleSelect(); 324 325 // Clear the hash in the URL so that the selection will work properly if
326 // the user came from the anchor link. 327 if (window.location.hash) { 328 window.history.pushState("", document.title, window.location.pathname + window.location.search); 329 } 330 }); 331 pseudoselectOptionsList.appendChild(pseudoOption); 332 333 if (realButton.classList.contains('su-tabs-current') && !currentSelectedOption) { 334 pseudoOption.classList.add('selected'); 335 currentSelectedOption = pseudoOption; 336 pseudoselectLabel.innerHTML = optionText; 337 } 338 }); 339 340 // Initialization on load if no tab is active, which is unlikely, but still possible. 341 // It's better to click the first one. Ideally, you should read the class from the plugin. 342 // And if the tab is inserted by default into the active tab, then click it. 343 if (!currentSelectedOption && realTabButtons.length > 0) { 344 const firstPseudoOption = pseudoselectOptionsList.querySelector('.pseudoselect-option'); 345 if (firstPseudoOption) { 346 firstPseudoOption.classList.add('selected'); 347 currentSelectedOption = firstPseudoOption; 348 pseudoselectLabel.innerHTML = firstPseudoOption.innerHTML; 349 realTabButtons[0].click(); 350 } 351 } 352 353 function toggleSelect() { 354 isOpen = !isOpen; 355 pseudoselectOptionsList.style.display = isOpen ? 'block' : 'none'; 356 pseudoselectButton.setAttribute('aria-expanded', isOpen); 357 if (isOpen) { 358 const targetOption = currentSelectedOption || pseudoselectOptionsList.querySelector('.pseudoselect-option'); 359 if (targetOption) { 360 targetOption.focus(); 361 } 362 } else { 363 pseudoselectButton.focus(); 364 } 365 } 366 367 pseudoselectButton.addEventListener('click', toggleSelect); 368 369 document.addEventListener('click', (event) => { 370 if (isOpen && !pseudoSelectContainer.contains(event.target)) { 371 toggleSelect(); 372 } 373 }); 374 375 pseudoselectButton.addEventListener('keydown', (e) => { 376 if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown' || e.key === 'ArrowUp') { 377 e.preventDefault(); 378 toggleSelect(); 379 } 380 }); 381 382 pseudoselectOptionsList.addEventListener('keydown', (e) => { 383 const focusableOptions = Array.from(pseudoselectOptionsList.querySelectorAll('.pseudoselect-option')); 384 const currentIndex = focusableOptions.indexOf(document.activeElement); 385 386 if (e.key === 'Escape') { 387 e.preventDefault(); 388 toggleSelect(); 389 pseudoselectButton.focus(); 390 } else if (e.key === 'ArrowDown') { 391 e.preventDefault(); 392 const nextIndex = (currentIndex + 1) % focusableOptions.length; 393 focusableOptions[nextIndex].focus(); 394 } else if (e.key === 'ArrowUp') { 395 e.preventDefault(); 396 const prevIndex = (currentIndex - 1 + focusableOptions.length) % focusableOptions.length; 397 focusableOptions[prevIndex].focus(); 398 } else if (e.key === 'Enter' || e.key === ' ') { 399 e.preventDefault(); 400 document.activeElement.click(); 401 } 402 }); 403 } 404 405 visibleRealTabsContainer.addEventListener('click', () => { 406 // Clear the hash in the URL so that the select button transition works properly later, 407 // In this case, this is for navigating through the visible select button on the first 408 // page, when it's invisible. Brutal. I won't understand what he meant later. What a marketing quest. 409 if (window.location.hash) { 410 window.history.pushState("", document.title, window.location.pathname + window.location.search); 411 } 412 }); 413 414 // 4.4. Navigation Link Handlers (The previous ones have been integrated, and now without jQuery) 415 // simulates a click on the last tab, or in this case, the second tab 416 // if it has the appropriate class. 417 // in principle, it could be removed, leaving only handleHashAnchor() 418 // which simulates a click on a normal anchor link. 419 function initializeNavigationHandlers() { 420 const openLastTabBtn = document.querySelector('.open-last-tab'); 421 if (openLastTabBtn) { 422 openLastTabBtn.addEventListener('click', function (e) { 423 e.preventDefault(); 424 var lastTab = document.querySelector('.su-tabs-nav span:nth-child(2)'); 425 if (lastTab) { 426 lastTab.click(); 427 } 428 window.scrollTo({ 429 top: 0, 430 behavior: 'smooth' 431 }); 432 }); 433 } 434 435 // Simulate a click on a link in the specifications, so that clicking on an anchor like 436 // /pricing#tab-plg works as a link for the user, although 437 // the user actually needs to click on the plugin tab. 438 // We also take into account the hash change if this is the pricing page itself, and n
438ot an external click. 439 // The main logic that activates the tab and scrolls to the element. 440 function activateTabAndScroll() { 441 const hash = window.location.hash; 442 if (!hash) { 443 return; 444 } 445 446 const targetId = hash.substring(1); 447 const targetElement = document.getElementById(targetId); 448 449 if (targetElement) { 450 const tabHeader = targetElement.closest('span.real-plans-tab'); 451 const tabContent = targetElement.closest('.su-tabs-pane'); 452 453 // Check if the target element is already an active tab 454 // This happens if you click one anchor, then another, then return. 455 // The hash change event won't fire, so we'll go the other way. 456 457 if (tabHeader && tabHeader.classList.contains('su-tabs-current')) { 458 // If the tab is already active, just scroll to it 459 window.scrollTo(0, 0); 460 return; 461 } 462 463 if (tabHeader && tabHeader.classList.contains('real-plans-tab')) { 464 tabHeader.click(); 465 setTimeout(() => { 466 window.scrollTo(0, 0); 467 }, 0); 468 469 // Synchronization with pseudo-select 470 // after the tab has been activated 471 472 const allPseudoOptions = document.querySelectorAll('.pseudoselect-option'); 473 const realTabButtons = document.querySelectorAll('.real-plans-tab'); 474 475 // Find the index of the activated tab 476 let activatedTabIndex = -1; 477 if (tabHeader) { 478 activatedTabIndex = Array.from(realTabButtons).indexOf(tabHeader); 479 } else if (tabContent) { 480 const tabId = tabContent.id; 481 const correspondingTabButton = Array.from(realTabButtons).find(btn => btn.id === tabId); 482 if (correspondingTabButton) { 483 activatedTabIndex = Array.from(realTabButtons).indexOf(correspondingTabButton); 484 } 485 } 486 487 if (activatedTabIndex !== -1 && allPseudoOptions.length > activatedTabIndex) { 488 allPseudoOptions.forEach(option => { 489 option.classList.remove('selected'); 490 }); 491 492 const correspondingPseudoOption = allPseudoOptions[activatedTabIndex]; 493 correspondingPseudoOption.classList.add('selected'); 494 495 // Updating the text in the "label" 496 const pseudoselectLabel = document.querySelector('.price-plans-pseudoselect .pseudoselect-label'); 497 if (pseudoselectLabel) { 498 pseudoselectLabel.innerHTML = correspondingPseudoOption.innerHTML; 499 } 500 } 501 } else if (tabContent) { 502 const tabId = tabContent.id; 503 const correspondingTabButton = realTabButtons.find(btn => btn.id === tabId); 504 505 if (correspondingTabButton) { 506 correspondingTabButton.click(); 507 setTimeout(() => { 508 window.scrollTo(0, 0); 509 }, 0); 510 } else { 511 console.warn(`Could not find a clickable tab button for element #${targetId}. Scrolling directly.`); 512 window.scrollTo(0, 0); 513 } 514 } else { 515 console.warn(`Element with ID "${targetId}" not found in a tab. Scrolling directly.`); 516 window.scrollTo(0, 0); 517 } 518 } else { 519 console.warn(`Element with ID "${targetId}" not found.`); 520 } 521 } 522 523 // main function - handler for transition to anchor 524 function handleHashAnchor() { 525 const tabsContainer = document.querySelector('.su-tabs'); 526 527 if (!tabsContainer) { 528 console.warn('Tabs container .su-tabs not found on the page.'); 529 return; 530 } 531 532 // If the tabs are already ready, we launch the logic for switching to the desired tab. 533 if (document.querySelector('.su-tabs-current')) { 534 activateTabAndScroll(); 535 } else { 536 // If the tabs are not ready yet, use MutationObserver 537 const observer = new MutationObserver((mutations, obs) => { 538 for (const mutation of mutations) { 539 if (mutation.type === 'attributes' && mutation.attributeName === 'class' || mutation.addedNodes.length > 0) { 540 if (document.querySelector('.su-tabs-current')) { 541 activateTabAndScroll(); 542 obs.disconnect(); 543 return; 544 } 545 } 546 } 547 }); 548 observer.observe(tabsContainer, { 549 childList: true, 550 subtree: true, 551 attributes: true 552 }); 553 } 554 } 555 556 handleHashAnchor(); // must call it, because this is the first init when following a link to a page. 557 window.addEventListener('hashchange', handleHashAnchor); 558 } 559 560 // Auxiliary function for switching the duration and synchronization of the accordion 561 function handleDurationSwitch(parentCard, newDurationType) { 562 const cardPlanName = parentCard.dataset.planGroup; 563 const monthCardInPair = dataCardPairs[cardPlanName].month; 564 const yearCardInPair = dataCardPairs[cardPlanName].year; 565 566 // Important: We take the current active duration from the PREVIOUS parent card, 567 // to know which card was visible and what its accordion state was. 568 const currentActiveDurationForPair = parentCard.dataset.activeDuration || 'month'; 569 570 if (newDurationType === currentActiveDurationForPair) { 571 return; 572 } 573 574 const currentVisibleCard = (currentActiveDurationForPair === 'month') ? monthCardInPair : yearCardInPair; 575 // Save the accordion state of the CARD THAT IS CURRENTLY HI
575DING. 576 // This is important to apply it to the new card. 577 const accordionState = currentVisibleCard ? (currentVisibleCard.dataset.accordionIsOpen || 'false') : 'false'; 578 //console.log('Switching cards. Previous card:', currentVisibleCard ? currentVisibleCard.dataset.planGroup + ' ' + currentVisibleCard.dataset.duration : 'N/A', 'Accordion state saved:', accordionState); 579 580 // Update dataset.activeDuration on BOTH cards in the pair 581 // Get an array of all cards in the pair, filtering out null/undefined 582 const cardsInPair = [monthCardInPair, yearCardInPair].filter(card => card); 583 584 cardsInPair.forEach(card => { 585 card.dataset.activeDuration = newDurationType; 586 card.style.display = 'none'; 587 }); 588 589 // Defining a new visible card 590 let newVisibleCard = (newDurationType === 'month') ? monthCardInPair : yearCardInPair; 591 592 if (newVisibleCard) { 593 newVisibleCard.style.display = 'flex'; 594 updateCardSwitches(newVisibleCard, newDurationType); 595 applyAccordionStateToCard(newVisibleCard, accordionState); 596 } 597 } 598 599 // Helper function for applying accordion state to a card 600 function applyAccordionStateToCard(cardElement, state) { 601 if (!cardElement) return; 602 603 const accordionBody = cardElement.querySelector(ACCORDION_BODY_CLASS); 604 const accordionSwitch = cardElement.querySelector(ACCORDION_SWITCH_CLASS); 605 606 if (!accordionBody || !accordionSwitch) return; 607 608 // here we must synchronize the data attributes and classes of the OPENING card. 609 // it takes its states from the CLOSING one, but if we don't synchronize, then switching again 610 // will close the open one or open the closed one. Even though the initial state may seem correct. 611 if (window.innerWidth < breakpointMobile) { 612 // Updating the data attribute of the new card 613 cardElement.dataset.accordionIsOpen = state; 614 accordionSwitch.setAttribute('aria-expanded', state); 615 616 if (state === 'true') { 617 accordionBody.style.display = 'block'; 618 accordionSwitch.classList.add('open'); 619 accordionBody.style.height = 'auto'; // "Just in case," it's now fully visible. 620 } else { 621 accordionBody.style.display = 'none'; 622 accordionSwitch.classList.remove('open'); 623 accordionBody.style.height = '0'; // Also "just in case", so that she would definitely hide 624 } 625 accordionBody.style.transition = ''; 626 accordionBody.style.overflow = ''; 627 //console.log('Applied accordion state to new card:', cardElement.dataset.planGroup, cardElement.dataset.duration, 'State:', state, 'Display:', accordionBody.style.display, 'Accordion switch has class open:', accordionSwitch.classList.contains('open')); 628 } else { 629 // On desktop or above BreakpointMobile, the accordion is always open. 630 accordionBody.style.display = 'block'; 631 accordionBody.style.height = 'auto'; 632 accordionBody.style.transition = ''; 633 accordionBody.style.overflow = ''; 634 accordionSwitch.classList.remove('open'); 635 delete cardElement.dataset.accordionIsOpen; 636 accordionSwitch.setAttribute('aria-expanded', 'false'); 637 } 638 } 639 640 // function for handling clicks on cardList 641 function handleCardListClick(event) { 642 const target = event.target; 643 const clickedSwitcher = target.closest(`${SWITCH_MONTH_CLASS}, ${SWITCH_YEAR_CLASS}`); 644 645 if (clickedSwitcher) { 646 event.stopPropagation(); 647 event.preventDefault(); 648 649 const parentCard = clickedSwitcher.closest(CARD_ITEM_CLASS); 650 651 if (parentCard && parentCard.dataset.plan === 'data') { 652 const newDurationType = clickedSwitcher.classList.contains('swich-month') ? 'month' : 'year'; 653 handleDurationSwitch(parentCard, newDurationType); 654 } 655 } 656 } 657 658 // function for processing clicks on plan switches (SQL/DATA) 659 function handlePlanSwitcherClick(event) { 660 // We use currentTarget since the listener is already bound to the switch directly. 661 const swich = event.currentTarget; 662 663 const newPlanType = swich.dataset.plan; 664 if (newPlanType !== activeMainPlanType) { 665 activeMainPlanType = newPlanType; 666 planSwichs.forEach(s => s.classList.remove('active')); 667 swich.classList.add('active'); 668 updateCardVisibility(); 669 } 670 } 671 672 // --- 5. Event Handlers --- 673 // 5.1. SQL/DATA Main Switcher 674 planSwichs.forEach(swich => { 675 swich.addEventListener('click', handlePlanSwitcherClick); 676 }); 677 678 // 5.2. Internal Monthly/Yearly Switcher 679 // Event delegation for span.swich-month/swich-year 680 cardList.addEventListener('click', handleCardListClick); 681 682 // --- 6. Initialization (calling all functions when the DOM is loaded) --- 683 // Initialization of default states of DATA cards and their switches
684 Object.values(dataCardPairs).forEach(pair => { 685 const defaultDuration = 'month'; 686 const initialActiveDuration = (pair.month && pair.month.dataset.activeDuration) || 687 (pair.year && pair.year.dataset.activeDuration) || 688 defaultDuration; 689 const cardsInThisPair = [pair.month, pair.year].filter(card => card); 690 691 cardsInThisPair.forEach(card => { 692 card.dataset.activeDuration = initialActiveDuration; 693 updateCardSwitches(card, initialActiveDuration); 694 }); 695 }); 696 697 // Initializing the main SQL/DATA switcher 698 const initialPlanSwich = plansSwitcher.querySelector(`.plan-swich[data-plan="${activeMainPlanType}"]`); 699 if (initialPlanSwich) { 700 initialPlanSwich.classList.add('active'); 701 } 702 updateCardVisibility(); 703 704 // Initializing a pseudo-select 705 //initializePseudoselect(); -- The last quest without dropdowns is no longer needed. 706 // But I left it in case they decide to go back to dropdowns. 707 708 // Call the accordion here for initial setup. 709 // It still won't work on the desktop; this is already in the function itself. 710 initializeAccordion(); 711 712 // Initializing anchor navigation functions 713 initializeNavigationHandlers(); 714 715 // Resize event listener for an accordion with debounce for optimization 716 // for those who like to stretch the screen or for QA 717 const debouncedInitializeAccordion = debounce(initializeAccordion, 250); 718 window.addEventListener('resize', debouncedInitializeAccordion); 719});
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.