PageSourceSearch

https://whop.com/blog/assets/built/main.js?v=NeVtb7RhhZxvu9xg

js whop.com collected 2026-09-24 08:34:26 UTC 19,135 bytes, 593 lines download raw bytes

1var html=document.documentElement,body=document.body;const toggleLight=document.querySelector(".toggle-light"),toggleDark=document.querySelector(".toggle-dark");toggleLight&&toggleLight.addEventListener("click",()=>{document.documentElement.classList.remove("dark"),localStorage.setItem("dark-mode",!1)}),toggleDark&&toggleDark.addEventListener("click",()=>{document.documentElement.classList.add("dark"),localStorage.setItem("dark-mode",!0)});
2
3// Enhanced Navigation JavaScript with Accessibility Features
4document.addEventListener('DOMContentLoaded', function() {
5  const liveRegion = document.getElementById('live-region');
6  
7  // Utility function to announce to screen readers
8  function announce(message) {
9    if (liveRegion) {
10      liveRegion.textContent = message;
11      setTimeout(() => {
12        liveRegion.textContent = '';
13      }, 1000);
14    }
15  }
16
17  // Set active states based on current URL
18  function setActiveNavigationState() {
19    const currentPath = window.location.pathname;
20    const navLinks = document.querySelectorAll('nav a[href*="/tag/"], nav a[href*="/t/"], nav a[href="/"]');
21    
22    navLinks.forEach(link => {
23      const linkPath = new URL(link.href).pathname;
24      
25      // Remove existing active states
26      link.classList.remove('nav-active');
27      link.removeAttribute('aria-current');
28      
29      if (currentPath === linkPath || 
30          (currentPath.includes('/t/') && linkPath.includes(currentPath.split('/t/')[1])) ||
31          (currentPath.includes('/tag/') && linkPath.includes(currentPath.split('/tag/')[1]))) {
32        
33        link.classList.add('nav-active');
34        link.setAttribute('aria-current', 'page');
35        
36        // If it's in a dropdown, also highlight the parent
37        const parentDropdown = link.closest('.dropdown-container');
38        if (parentDropdown) {
39          const parentTrigger = parentDropdown.querySelector('.dropdown-trigger');
40          if (parentTrigger) {
41            parentTrigger.classList.add('nav-active');
42            parentTrigger.setAttribute('aria-current', 'page');
43          }
44        }
45      }
46    });
47  }
48
49  // Enhanced Desktop Dropdown Functionality
50  function initializeAccessibleDropdowns() {
51    const dropdowns = document.querySelectorAll('.dropdown-container');
52    
53    dropdowns.forEach(dropdown => {
54      const trigger = dropdown.querySelector('.dropdown-trigger');
55      const menu = dropdown.querySelector('.dropdown-menu');
56      
57      if (!trigger || !menu) return;
58      
59      const menuItems = menu.querySelectorAll('a[role="menuitem"]');
60      let currentFocus = -1;
61      let closeTimeout;
62      
63      // Keyboard event handler for dropdown triggers
64      function handleKeyDown(e) {
65        switch(e.key) {
66          case 'Enter':
67          case ' ':
68            e.preventDefault();
69            toggleDropdown();
70            break;
71          case 'ArrowDown':
72            e.preventDefault();
73            if (isOpen()) {
74              focusFirstItem();
75            } else {
76              openDropdown();
77              focusFirstItem();
78            }
79            break;
80          case 'ArrowUp':
81            e.preventDefault();
82            if (isOpen()) {
83              focusLastItem();
84            } else {
85              openDropdown();
86              focusLastItem();
87            }
88            break;
89          case 'Escape':
90            if (isOpen()) {
91              closeDropdown();
92              trigger.focus();
93            }
94            break;
95        }
96      }
97      
98      // Menu item keyboard navigation
99      function handleMenuItemKeyDown(e, index) {
100        currentFocus = index;
101        
102        switch(e.key) {
103          case 'ArrowDown':
104            e.preventDefault();
105            focusNextItem();
106            break;
107          case 'ArrowUp':
108            e.preventDefault();
109            focusPreviousItem();
110            break;
111          case 'Home':
112            e.preventDefault();
113            focusFirstItem();
114            break;
115          case 'End':
116            e.preventDefault();
117            focusLastItem();
118            break;
119          case 'Escape':
120            closeDropdown();
121            trigger.focus();
122            break;
123          case 'Tab':
124            // Allow normal tab behavior to close menu
125            closeDropdown();
126            break;
127        }
128      }
129      
130      function isOpen() {
131        return trigger.getAttribute('aria-expanded') === 'true';
132      }
133      
134      function openDropdown() {
135        clearTimeout(closeTimeout);
136        trigger.setAttribute('aria-expanded', 'true');
137        menu.setAttribute('aria-expanded', 'true');
138        currentFocus = -1;
139        
140        const menuLabel = trigger.querySelector('span').textContent.trim();
141        announce(`${menuLabel} menu opened`);
142        
143        // Close other dropdowns
144        dropdowns.forEach(otherDropdown => {
145          if (otherDropdown !== dropdown) {
146            closeDropdownImmediate(otherDropdown);
147          }
148        });
149      }
150      
151      function closeDropdown() {
152        closeTimeout = setTimeout(() => {
153          trigger.setAttribute('aria-expanded', 'false');
154          menu.setAttribute('aria-expanded', 'false');
155          currentFocus = -1;
156          
157          const menuLabel = trigger.querySelector('span').textContent.trim();
158          announce(`${menuLabel} menu closed`);
159        }, 150);
160      }
161      
162      function closeDropdownImmediate(targetDropdown) {
163        const targetTrigger = targetDropdown.querySelector('.dropdown-trigger');
164        const targetMenu = targetDropdown.querySelector('.dropdown-menu');
165        if (targetTrigger && targetMenu) {
166          targetTrigger.setAttribute('aria-expanded', 'false');
167          targetMenu.setAttribute('aria-expanded', 'false');
168        }
169      }
170      
171      function toggleDropdown() {
172        if (isOpen()) {
173          closeDropdown();
174        } else {
175          openDropdown();
176        }
177      }
178      
179      function focusItem(index) {
180        if (index >= 0 && index < menuItems.length) {
181          currentFocus = index;
182          menuItems[index].focus();
183        }
184      }
185      
186      function focusFirstItem() {
187        focusItem(0);
188      }
189      
190      function focusLastItem() {
191        focusItem(menuItems.length - 1);
192      }
193      
194      function focusNextItem() {
195        currentFocus = (currentFocus + 1) % menuItems.length;
196        focusItem(currentFocus);
197      }
198      
199      function focusPreviousItem() {
200        currentFocus = currentFocus <= 0 ? menuItems.length - 1 : currentFocus - 1;
201        focusItem(currentFocus);
202      }
203      
204      // Event listeners for triggers
205      trigger.addEventListener('keydown', handleKeyDown);
206      trigger.addEventListener('click', (e) => {
207        e.preventDefault();
208        toggleDropdown();
209      });
210      
211      // Event listeners for menu items
212      menuItems.forEach((item, index) => {
213        item.addEventListener('keydown', (e) => {
214          handleMenuItemKeyDown(e, index);
215        });
216        
217        item.addEventListener('click', () => {
218          closeDropdown();
219        });
220        
221        // Keep menu open when focusing on items
222        item.addEventListener('mouseenter', () => {
223          clearTimeout(closeTimeout);
224          currentFocus = index;
225        });
226      });
227      
228      // Mouse events for desktop (hover to open/close)
229      if (window.innerWidth >= 768) {
230        dropdown.addEventListener('mouseenter', openDropdown);
231        dropdown.addEventListener('mouseleave', closeDropdown);
232        
233        // Keep dropdown open when focusing on menu items
234        menu.addEventListener('mouseenter', () => {
235          clearTimeout(closeTimeout);
236        });
237        
238        menu.addEventListener('mouseleave', closeDropdown);
239      }
240    });
241    
242    // Global event listeners
243    document.addEventListener('click', (e) => {
244      if (!e.target.closest('.dropdown-container')) {
245        dropdowns.forEach(dropdown => {
246          const trigger = dropdown.querySelector('.dropdown-trigger');
247          const menu = dropdown.querySelector('.dropdown-menu');
248          if (trigger && menu) {
249            trigger.setAttribute('aria-expanded', 'false');
250            menu.setAttribute('aria-expanded', 'false');
251          }
252        });
253      }
254    });
255    
256    document.addEventListener('keydown', (e) => {
257      if (e.key === 'Escape') {
258        dropdowns.forEach(dropdown => {
259          const trigger = dropdown.querySelector('.dropdown-trigger');
260          const menu = dropdown.querySelector('.dropdown-menu');
261          if (trigger && menu) {
262            trigger.setAttribute('aria-expanded', 'false');
263            menu.setAttribute('aria-expanded', 'false');
264          }
265        });
266      }
267    });
268  }
269
270  // Enhanced Mobile Menu Functionality
271  function initializeAccessibleMobileMenu() {
272    const mobileMenuToggles = document.querySelectorAll('.mobile-menu-toggle');
273    const mobileMenuClose = document.querySelector('.mobile-menu-close');
274    const mobileMenuOverlay = document.querySelector('.mobile-menu-overlay');
275    const mobileMenuContent = document.querySelector('.mobile-menu-content');
276    const submenuToggles = document.querySelectorAll('.mobile-submenu-toggle');
277    
278    // Store the last focused element before opening menu
279    let lastFocusedElement = null;
280    // Store scroll position before opening menu
281    let savedScrollPosition = 0;
282
283    function openMobileMenu() {
284      lastFocusedElement = document.activeElement;
285
286      // Save current scroll position before applying position: fixed
287      savedScrollPosition = window.scrollY;
288
289      if (mobileMenuOverlay) {
290        mobileMenuOverlay.style.display = 'block';
291        setTimeout(() => {
292          mobileMenuOverlay.classList.add('show');
293        }, 10);
294      }
295
296      document.body.classList.add('mobile-menu-open');
297      // Offset the body to maintain visual position
298      document.body.style.top = `-${savedScrollPosition}px`;
299
300      if (mobileMenuToggles.length > 0) {
301        mobileMenuToggles.forEach(toggle => {
302          toggle.setAttribute('aria-expanded', 'true');
303        });
304      }
305
306      // Focus the close button for better accessibility
307      if (mobileMenuClose) {
308        mobileMenuClose.focus();
309      }
310
311      announce('Main menu opened');
312
313      // Set focus trap
314      if (mobileMenuContent) {
315        trapFocus(mobileMenuContent);
316      }
317    }
318
319    function closeMobileMenu() {
320      if (mobileMenuOverlay) {
321        mobileMenuOverlay.classList.remove('show');
322        setTimeout(() => {
323          mobileMenuOverlay.style.display = 'none';
324        }, 300);
325      }
326
327      document.body.classList.remove('mobile-menu-open');
328      document.body.style.top = '';
329      // Restore scroll position after removing position: fixed
330      window.scrollTo(0, savedScrollPosition);
331
332      if (mobileMenuToggles.length > 0) {
333        mobileMenuToggles.forEach(toggle => {
334          toggle.setAttribute('aria-expanded', 'false');
335        });
336      }
337
338      // Return focus to the element that opened the menu
339      if (lastFocusedElement) {
340        lastFocusedElement.focus();
341      } else if (mobileMenuToggles.length > 0) {
342        mobileMenuToggles[0].focus();
343      }
344      
345      announce('Main menu closed');
346      
347      // Close all submenus
348      submenuToggles.forEach(toggle => {
349        const submenu = toggle.nextElementSibling;
350        const arrow = toggle.querySelector('.mobile-submenu-arrow');
351        
352        toggle.setAttribute('aria-expanded', 'false');
353        toggle.classList.remove('active');
354        
355        if (submenu) {
356          submenu.classList.remove('show');
357        }
358        if (arrow) {
359          arrow.style.transform = 'rotate(0deg)';
360        }
361      });
362      
363      // Remove focus trap
364      removeFocusTrap();
365    }
366    
367    // Focus trap functionality
368    function trapFocus(element) {
369      const focusableElements = element.querySelectorAll(
370        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
371      );
372      const firstFocusableElement = focusableElements[0];
373      const lastFocusableElement = focusableElements[focusableElements.length - 1];
374      
375      function handleTabKey(e) {
376        if (e.key === 'Tab') {
377          if (e.shiftKey) {
378            if (document.activeElement === firstFocusableElement) {
379              lastFocusableElement.focus();
380              e.preventDefault();
381            }
382          } else {
383            if (document.activeElement === lastFocusableElement) {
384              firstFocusableElement.focus();
385              e.preventDefault();
386            }
387          }
388        }
389      }
390      
391      element.addEventListener('keydown', handleTabKey);
392      element.setAttribute('data-focus-trapped', 'true');
393    }
394    
395    function removeFocusTrap() {
396      const trappedElement = document.querySelector('[data-focus-trapped]');
397      if (trappedElement) {
398        trappedElement.removeAttribute('data-focus-trapped');
399      }
400    }
401    
402    // Event listeners
403    if (mobileMenuToggles.length > 0) {
404      mobileMenuToggles.forEach(toggle => {
405        toggle.addEventListener('click', openMobileMenu);
406      });
407    }
408    
409    if (mobileMenuClose) {
410      mobileMenuClose.addEventListener('click', closeMobileMenu);
411    }
412    
413    // Close on overlay click
414    if (mobileMenuOverlay) {
415      mobileMenuOverlay.addEventListener('click', (e) => {
416        if (e.target === mobileMenuOverlay) {
417          closeMobileMenu();
418        }
419      });
420    }
421    
422    // Close on escape key
423    document.addEventListener('keydown', (e) => {
424      if (e.key === 'Escape' && mobileMenuOverlay && mobileMenuOverlay.classList.contains('show')) {
425        closeMobileMenu();
426      }
427    });
428    
429    // Submenu toggle functionality
430    submenuToggles.forEach(toggle => {
431      // Set initial ARIA attributes
432      toggle.setAttribute('aria-expanded', 'false');
433      toggle.setAttribute('aria-haspopup', 'true');
434      
435      toggle.addEventListener('click', function() {
436        const submenu = this.nextElementSibling;
437        const arrow = this.querySelector('.mobile-submenu-arrow');
438        const isOpen = this.getAttribute('aria-expanded') === 'true';
439        const menuLabel = this.querySelector('span').textContent.trim();
440        
441        // Close other submenus
442        submenuToggles.forEach(otherToggle => {
443          if (otherToggle !== toggle) {
444            const otherSubmenu = otherToggle.nextElementSibling;
445            const otherArrow = otherToggle.querySelector('.mobile-submenu-arrow');
446            
447            otherToggle.setAttribute('aria-expanded', 'false');
448            otherToggle.classList.remove('active');
449            
450            if (otherSubmenu) {
451              otherSubmenu.classList.remove('show');
452            }
453            if (otherArrow) {
454              otherArrow.style.transform = 'rotate(0deg)';
455            }
456          }
457        });
458        
459        // Toggle current submenu
460        if (isOpen) {
461          this.setAttribute('aria-expanded', 'false');
462          this.classList.remove('active');
463          if (submenu) {
464            submenu.classList.remove('show');
465          }
466          if (arrow) {
467            arrow.style.transform = 'rotate(0deg)';
468          }
469          announce(`${menuLabel} submenu closed`);
470        } else {
471          this.setAttribute('aria-expanded', 'true');
472          this.classList.add('active');
473          if (submenu) {
474            submenu.classList.add('show');
475          }
476          if (arrow) {
477            arrow.style.transform = 'rotate(180deg)';
478          }
479          announce(`${menuLabel} submenu opened`);
480        }
481      });
482      
483      // Keyboard support for submenu toggles
484      toggle.addEventListener('keydown', (e) => {
485        if (e.key === 'Enter' || e.key === ' ') {
486          e.preventDefault();
487          toggle.click();
488        }
489      });
490    });
491    
492    // Close mobile menu when navigation links are clicked
493    const mobileNavLinks = document.querySelectorAll('.mobile-menu-overlay a');
494    mobileNavLinks.forEach(link => {
495      link.addEventListener('click', () => {
496        setTimeout(closeMobileMenu, 100);
497      });
498    });
499  }
500
501  // Skip link functionality
502  function initializeSkipLink() {
503    const skipLink = document.querySelector('.skip-link');
504    const mainContent = document.getElementById('main-content');
505    
506    if (skipLink && mainContent) {
507      skipLink.addEventListener('click', (e) => {
508        e.preventDefault();
509        mainContent.focus();
510        announce('Skipped to main content');
511      });
512    }
513  }
514
515  // Smooth scroll for footer "back to top" link
516  function initializeBackToTop() {
517    const backToTopLink = document.querySelector('a[href="#"]');
518    if (backToTopLink) {
519      backToTopLink.addEventListener('click', function(e) {
520        e.preventDefault();
521        window.scrollTo({
522          top: 0,
523          behavior: 'smooth'
524        });
525      });
526    }
527  }
528
529  // Initialize all functionality
530  setActiveNavigationState();
531  initializeAccessibleDropdowns();
532  initializeAccessibleMobileMenu();
533  initializeSkipLink();
534  initializeBackToTop();
535
536  // Re-initialize on window resize for responsive behavior
537  let resizeTimeout;
538  window.addEventListener('resize', function() {
539    clearTimeout(resizeTimeout);
540    resizeTimeout = setTimeout(function() {
541      // Re-initialize dropdowns with new window size
542      initializeAccessibleDropdowns();
543      
544      // Close mobile menu if window becomes large
545      if (window.innerWidth >= 768) {
546        const mobileMenuOverlay = document.querySelector('.mobile-menu-overlay');
547        const mobileMenuToggles = document.querySelectorAll('.mobile-menu-toggle');
548
549        if (mobileMenuOverlay && mobileMenuOverlay.classList.contains('show')) {
550          mobileMenuOverlay.classList.remove('show');
551          document.body.classList.remove('mobile-menu-open');
552
553          if (mobileMenuToggles.length > 0) {
554            mobileMenuToggles.forEach(toggle => {
555              toggle.setAttribute('aria-expanded', 'false');
556            });
557          }
558          
559          // Close all submenus
560          const submenuToggles = document.querySelectorAll('.mobile-submenu-toggle');
561          submenuToggles.forEach(toggle => {
562            const submenu = toggle.nextElementSibling;
563            const arrow = toggle.querySelector('.mobile-submenu-arrow');
564            
565            toggle.setAttribute('aria-expanded', 'false');
566            toggle.classList.remove('active');
567            
568            if (submenu) {
569              submenu.classList.remove('show');
570            }
571            if (arrow) {
572              arrow.style.transform = 'rotate(0deg)';
573            }
574          });
575        }
576      }
577    }, 250);
578  });
579
580  // Announce page changes for SPA-like behavior
581  let lastUrl = location.href;
582  new MutationObserver(() => {
583    const url = location.href;
584    if (url !== lastUrl) {
585      lastUrl = url;
586      setTimeout(() => {
587        setActiveNavigationState();
588        const pageTitle = document.title;
589        announce(`Navigated to ${pageTitle}`);
590      }, 100);
591    }
592  }).observe(document, { subtree: true, childList: true });
593});

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.