PageSourceSearch

https://www.ispirer.com/templates/ispirer/js/ispirer/articles/pricing/price-uxui.js?v=2.8.2

js ispirer.com collected 2026-09-24 09:21:29 UTC 29,208 bytes, 719 lines download raw bytes

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.