1/** 2 * Parfumbella Theme JavaScript 3 * 4 * @package Parfumbella 5 */ 6 7(function($) { 8 'use strict'; 9 10 /** 11 * Theme Toggle 12 */ 13 function initThemeToggle() { 14 const toggleButtons = document.querySelectorAll('.theme-toggle'); 15 16 toggleButtons.forEach(function(button) { 17 button.addEventListener('click', function() { 18 const currentTheme = document.documentElement.getAttribute('data-theme'); 19 const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; 20 21 document.documentElement.setAttribute('data-theme', newTheme); 22 localStorage.setItem('theme', newTheme); 23 }); 24 }); 25 } 26 27 /** 28 * Mobile Menu 29 */ 30 function initMobileMenu() { 31 const mobileMenu = document.getElementById('mobile-menu'); 32 const openButton = document.querySelector('.mobile-menu-toggle'); 33 const closeButton = document.querySelector('.mobile-menu-close'); 34 const backdrop = document.querySelector('.mobile-menu-backdrop'); 35 36 if (!mobileMenu || !openButton) return; 37 38 function openMenu() { 39 mobileMenu.setAttribute('aria-hidden', 'false'); 40 openButton.setAttribute('aria-expanded', 'true'); 41 document.body.style.overflow = 'hidden'; 42 } 43 44 function closeMenu() { 45 mobileMenu.setAttribute('aria-hidden', 'true'); 46 openButton.setAttribute('aria-expanded', 'false'); 47 document.body.style.overflow = ''; 48 } 49 50 openButton.addEventListener('click', openMenu); 51 52 if (closeButton) { 53 closeButton.addEventListener('click', closeMenu); 54 } 55 56 if (backdrop) { 57 backdrop.addEventListener('click', closeMenu); 58 } 59 60 // Close on Escape key 61 document.addEventListener('keydown', function(e) { 62 if (e.key === 'Escape' && mobileMenu.getAttribute('aria-hidden') === 'false') { 63 closeMenu(); 64 } 65 }); 66 67 // Handle submenu toggles in mobile menu 68 const menuItemsWithChildren = document.querySelectorAll('.mobile-nav-menu .menu-item-has-children > a'); 69 70 menuItemsWithChildren.forEach(function(link) { 71 link.addEventListener('click', function(e) { 72 e.preventDefault(); 73 const parent = this.parentElement; 74 const subMenu = parent.querySelector('.sub-menu'); 75 76 if (subMenu) { 77 parent.classList.toggle('is-open'); 78 subMenu.style.display = parent.classList.contains('is-open') ? 'block' : 'none'; 79 } 80 }); 81 }); 82 } 83 84 /** 85 * Footer Accordion (Mobile) 86 */ 87 function initFooterAccordion() { 88 if (window.innerWidth > 767) return; 89 90 const footerTitles = document.querySelectorAll('.footer-widget-title'); 91
92 footerTitles.forEach(function(title) { 93 title.addEventListener('click', function() { 94 const content = this.nextElementSibling; 95 96 // Close other open sections 97 footerTitles.forEach(function(otherTitle) { 98 if (otherTitle !== title) { 99 otherTitle.classList.remove('active'); 100 const otherContent = otherTitle.nextElementSibling; 101 if (otherContent) { 102 otherContent.style.display = 'none'; 103 } 104 } 105 }); 106 107 // Toggle current section 108 this.classList.toggle('active'); 109 if (content) { 110 content.style.display = this.classList.contains('active') ? 'block' : 'none'; 111 } 112 }); 113 }); 114 } 115 116 /** 117 * FAQ Accordion (Smooth Animation) 118 */ 119 function initFaqAccordion() { 120 const faqItems = document.querySelectorAll('.faq-item'); 121 122 faqItems.forEach(function(item) { 123 const summary = item.querySelector('.faq-question'); 124 const answer = item.querySelector('.faq-answer'); 125 126 if (!summary || !answer) return; 127 128 // Set initial height for closed state 129 answer.style.maxHeight = '0px'; 130 answer.style.overflow = 'hidden'; 131 answer.style.transition = 'max-height 0.3s ease, padding 0.3s ease'; 132 answer.style.paddingTop = '0'; 133 answer.style.paddingBottom = '0'; 134 135 summary.addEventListener('click', function(e) { 136 e.preventDefault(); 137 138 const isOpen = item.hasAttribute('open'); 139 140 if (isOpen) { 141 // Closing 142 answer.style.maxHeight = '0px'; 143 answer.style.paddingTop = '0'; 144 answer.style.paddingBottom = '0'; 145 146 setTimeout(function() { 147 item.removeAttribute('open'); 148 }, 300); 149 } else { 150 // Opening 151 item.setAttribute('open', ''); 152 answer.style.maxHeight = answer.scrollHeight + 32 + 'px'; 153 answer.style.paddingTop = ''; 154 answer.style.paddingBottom = ''; 155 } 156 }); 157 }); 158 } 159 160 /** 161 * Product Tabs Accordion (Mobile) 162 */ 163 function initProductTabsAccordion() { 164 if (window.innerWidth > 767) return; 165 166 const tabPanels = document.querySelectorAll('.woocommerce-tabs .woocommerce-Tabs-panel'); 167 168 if (!tabPanels.length) return; 169 170 // Show first tab by default 171 if (tabPanels[0]) { 172 tabPanels[0].classList.add('active'); 173 } 174 175 tabPanels.forEach(function(panel) { 176 // Create clickable header from tab label 177 const tabId = panel.getAttribute('id'); 178 const tabLink = document.querySelector('.woocommerce-tabs .tabs a[href="#' + tabId + '"]'); 179 180 if (!tabLink) return; 181 182 const header = document.createElement('div'); 183 header.className = 'accordion-tab-header'; 184 header.textContent = tabLink.textContent; 185 header.style.cssText = 'padding: var(--spacing-md); background: var(--color-surface); border: 1px solid var(--color-border); font-weight: var(--font-weight-semibold); cursor: pointer; margin-bottom: 0;'; 186 187 panel.parentNode.insertBefore(header, panel); 188 189 // Update active state styling 190 if (panel.classList.contains('active')) { 191 header.style.background = 'var(--color-primary)'; 192 header.style.color = 'var(--color-white)'; 193 } 194 195 header.addEventListener('click', function() { 196 const isActive = panel.classList.contains('active'); 197 198 // Close all panels 199 tabPanels.forEach(function(p) { 200 p.classList.remove('active'); 201 const h = p.previousElementSibling; 202 if (h && h.classList.contains('accordion-tab-header')) { 203 h.style.background = 'var(--color-surface)'; 204 h.style.color = ''; 205 } 206 }); 207 208 // Toggle current panel 209 if (!isActive) { 210 panel.classList.add('active'); 211 header.style.background = 'var(--color-primary)'; 212 header.style.color = 'var(--color-white)'; 213 } 214 }); 215 }); 216 } 217 218 /** 219 * Product Thumbnail Slider Navigation 220 */ 221 function initThumbSlider() { 222 const thumbContainer = document.querySelector('.woocommerce-product-gallery ol.flex-control-thumbs'); 223 224 if (!thumbContainer || window.innerWidth <= 767) return; 225 226 const gallery = document.querySelector('.woocommerce-product-gallery'); 227 if (!gallery) return; 228 229 // Create navigation arrows 230 const prevArrow = document.createElement('button'); 231 prevArrow.className = 'thumb-nav-arrow thumb-nav-prev'; 232 prevArrow.innerHTML = '←'; 233 prevArrow.setAttribute('aria-label', 'Previous thumbnails'); 234 235 const nextArrow = document.createElement('button'); 236 nextArrow.className = 'thumb-nav-arrow thumb-nav-next'; 237 nextArrow.innerHTML = '→'; 238 nextArrow.setAttribute('aria-label', 'Next thumbnails'); 239 240 gallery.appendChild(prevArrow); 241 gallery.appendChild(nextArrow); 242 243 // Scroll amount (one thumbnail width + gap) 244 const scrollAmount = 116; // 100px thumb + 16px gap 245 246 function updateArrows() { 247 const scrollLeft = thumbContainer.scrollLeft; 248 const maxScroll = thumbContainer.scrollWidth - thumbContainer.clientWidth; 249 250 prevArrow.classList.toggle('disabled', scrollLeft <= 0); 251 nextArrow.classList.toggle('disabled', scrollLeft >= maxScroll); 252 } 253 254 prevArrow.addEventListener('click', function() { 255 thumbContainer.scrollBy({ left: -scrollAmount, behavior: 'smooth' }); 256 }); 257 258 nextArrow.addEventListener('click', function() { 259 thumbContainer.scrollBy({ left: scrollAmount, behavior: 'smooth' }); 260 }); 261 262 thumbContainer.addEventListener('scroll', updateArrows); 263 updateArrows(); 264 } 265 266 /** 267 * Sticky Header Shadow 268 */ 269 function initStickyHeader() { 270 const header = document.querySelector('.site-header'); 271 if (!header) return; 272 273 window.addEventListener('scroll', function() { 274 if (window.scrollY > 10) { 275 header.classList.add('is-scrolled'); 276 } else { 277 header.classList.remove('is-scrolled'); 278 } 279 }); 280 } 281 282 /** 283 * Variable Product Radio Selector 284 */ 285 function initVariationRadios() { 286 var $form = $('.variations_form'); 287 if (!$form.length) return; 288 289 var $rows = $form.find('.variation-radio-row'); 290 if (!$rows.length) return; 291 292 // Select first available variation on load 293 var $firstAvailable = $rows.not('.out-of-stock').first(); 294 if ($firstAvailable.length) { 295 selectVariation($firstAvailable); 296 } 297 298 // Handle radio row click 299 $rows.on('click', function() { 300 if ($(this).hasClass('out-of-stock')) return; 301 selectVariation($(this)); 302 }); 303 304 function selectVariation($row) { 305 var $radio = $row.find('input[type="radio"]'); 306 var attrName = $row.closest('.variation-radio-group').data('attribute'); 307 var value = $radio.val(); 308 309 // Update UI 310 $rows.removeClass('is-selected'); 311 $row.addClass('is-selected'); 312 $radio.prop('checked', true); 313 314 // Sync with hidden WooCommerce dropdown 315 var $select = $form.find('select[name="' + $radio.attr('name') + '"]'); 316 if ($select.length) { 317 $select.val(value).trigger('change'); 318 } 319 } 320 } 321 322 /** 323 * Sidebar Filter Search 324 */ 325 function initFilterSearch() { 326 var inputs = document.querySelectorAll('.filter-search-input');
327 inputs.forEach(function(input) { 328 var listId = input.getAttribute('data-list'); 329 var list = document.getElementById(listId); 330 if (!list) return; 331 332 input.addEventListener('input', function() { 333 var query = this.value.toLowerCase(); 334 var items = list.querySelectorAll('li'); 335 items.forEach(function(item) { 336 var text = item.textContent.toLowerCase(); 337 item.style.display = text.indexOf(query) !== -1 ? '' : 'none'; 338 }); 339 }); 340 }); 341 } 342 343 /** 344 * Initialize on DOM Ready 345 */ 346 document.addEventListener('DOMContentLoaded', function() { 347 initThemeToggle(); 348 initMobileMenu(); 349 initFooterAccordion(); 350 initFaqAccordion(); 351 initProductTabsAccordion(); 352 initThumbSlider(); 353 initStickyHeader(); 354 initVariationRadios(); 355 initFilterSearch(); 356 }); 357 358 /** 359 * Reinitialize on window resize 360 */ 361 let resizeTimer; 362 window.addEventListener('resize', function() { 363 clearTimeout(resizeTimer); 364 resizeTimer = setTimeout(function() { 365 // Reinit footer accordion based on screen size 366 const footerContents = document.querySelectorAll('.footer-content'); 367 if (window.innerWidth > 767) { 368 footerContents.forEach(function(content) { 369 content.style.display = ''; 370 }); 371 } 372 }, 250); 373 }); 374 375})(jQuery);
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.