1// Gallery: thumbnails swap the stage image. 2const stage = document.querySelector(".pdp-stage img"); 3const stageIndex = document.querySelector(".pdp-stage-index"); 4const thumbs = [...document.querySelectorAll(".pdp-thumb")]; 5 6thumbs.forEach((thumb, i) => { 7 thumb.addEventListener("click", () => { 8 const img = thumb.querySelector("img"); 9 if (!stage || !img) return; 10 stage.src = img.dataset.full || img.src; 11 stage.srcset = ""; 12 stage.alt = img.alt; 13 if (stageIndex) { 14 stageIndex.hidden = false; 15 stageIndex.textContent = `${String(i + 1).padStart(2, "0")} / ${String(thumbs.length).padStart(2, "0")}`; 16 } 17 thumbs.forEach((t) => t.setAttribute("aria-current", String(t === thumb))); 18 }); 19}); 20 21// Quantity stepper, driving WooCommerce's own quantity input. 22// Price, variations, and add-to-cart are handled by WooCommerce. 23const qtyInput = document.querySelector(".pdp-form input.qty"); 24 25document.querySelectorAll(".pdp-qty button, .neomag-qty-step").forEach((button) => { 26 button.addEventListener("click", () => { 27 if (!qtyInput) return; 28 const step = Number(button.dataset.step) || 1; 29 const min = Number(qtyInput.min) || 1; 30 const max = Number(qtyInput.max) || Infinity; 31 const next = (Number(qtyInput.value) || min) + step; 32 qtyInput.value = Math.min(max, Math.max(min, next)); 33 qtyInput.dispatchEvent(new Event("change", { bubbles: true })); 34 }); 35}); 36 37// Reviews: collapse the long tail behind a toggle. 38document.querySelectorAll(".reviews-toggle").forEach((button) => { 39 const extras = [...document.querySelectorAll(".review-card.is-extra")]; 40 const label = button.textContent.trim(); 41 button.addEventListener("click", () => { 42 const expanded = button.getAttribute("aria-expanded") === "true"; 43 extras.forEach((card) => { card.hidden = expanded; }); 44 button.setAttribute("aria-expanded", String(!expanded)); 45 button.innerHTML = expanded ? label : 'SHOW FEWER REVIEWS <span aria-hidden="true">â</span>'; 46 }); 47}); 48 49// Collapse the review list (Customer Reviews for WooCommerce markup) behind a 50// toggle - products here carry up to 47 reviews, which buried the page. 51(() => { 52 const list = document.querySelector("#reviews ol.commentlist"); 53 if (!list) return; 54 55 const items = [...list.querySelectorAll(":scope > li.review")]; 56 const VISIBLE = 6; 57 if (items.length <= VISIBLE) return; 58 59 const extras = items.slice(VISIBLE); 60 extras.forEach((li) => { li.hidden = true; }); 61 62 const button = document.createElement("button"); 63 button.type = "button"; 64 button.className = "neomag-reviews-toggle"; 65 button.setAttribute("aria-expanded", "false"); 66 button.setAttribute("aria-controls", "reviews"); 67 const showLabel = `SHOW ALL ${items.length} REVIEWS`; 68 button.innerHTML = `${showLabel} <span aria-hidden="true">â</span>`; 69 list.insertAdjacentElement("afterend", button); 70 71 button.addEventListener("click", () => { 72 const expanded = button.getAttribute("aria-expanded") === "true"; 73 extras.forEach((li) => { li.hidden = expanded; }); 74 button.setAttribute("aria-expanded", String(!expanded)); 75 button.innerHTML = expanded 76 ? `${showLabel} <span aria-hidden="true">â</span>` 77 : 'SHOW FEWER REVIEWS <span aria-hidden="true">â</span>'; 78 if (expanded) button.scrollIntoView({ block: "center", behavior: "smooth" }); 79 }); 80})(); 81 82// Enhance known color attributes while retaining Woo's submitted selects and 83// variation events. Unknown finishes keep their original dropdown. 84(() => { 85 const $ = window.jQuery; 86 if (!$) return; 87 const colors = { 88 black: '#202020', 'black anodized': '#202020', 'black leather': '#202020', 89 white: '#f5f5f2', gray: '#808388', grey: '#808388', 'tri-vintage grey': '#808388', 90 greyman: '#777e80', 'wolf grey': '#777e80', tungsten: '#575b60', 91 navy: '#23364d', 'navy blue': '#23364d', midwatch: '#23364d', 92 olive: '#606345', 'od green': '#48513a', ranger: '#68715a', 'ranger green': '#68715a', 93 fde: '#ae9572', 'flat dark earth': '#ae9572', coyote: '#a18455', tan: '#c4aa7d', 94 brown: '#69452f', 'brown leather': '#69452f', chocolate: '#50382d', 95 'russet brown': '#95572f', 'antique brown': '#624232', 'crazy horse': '#826148', 96 'burnt bronze': '#826540', red: '#b52e30', 'rescue red': '#b52e30', 97 asphalt: '#48494b', tumbled: '#a6a7a3', 'raw titanium': '#a6a7a3', 'raw ti': '#a6a7a3', 98 multicam: 'conic-gradient(#776948, #a89f7c, #4f5940, #c4b18a, #776948)',
99 'black multicam': 'conic-gradient(#252b25, #42473a, #202420, #575749, #252b25)' 100 }; 101 const normalize = value => value.toLowerCase().trim() 102 .replace(/\s+(cerakote|cerekote|tray|leather)$/, ''); 103 const paint = text => colors[normalize(text)]; 104 const controls = new Map(); 105 function enhance() { 106 document.querySelectorAll('.pdp-form .variations select').forEach(select => { 107 if (controls.has(select) || !/(colou?r|^attribute_(?:leather-)?insert$)/i.test(select.name)) return; 108 const options = [...select.options].filter(o => o.value); 109 if (!options.length || !options.every(o => paint(o.textContent))) return; 110 const group = document.createElement('div'); 111 group.className = 'neomag-swatches'; 112 group.setAttribute('role', 'group'); 113 const label = select.labels && select.labels[0]; 114 group.setAttribute('aria-label', label ? label.textContent.trim() : 'Color'); 115 const buttons = options.map(option => { 116 const button = document.createElement('button'); 117 button.type = 'button'; 118 button.className = 'neomag-swatch'; 119 button.dataset.value = option.value; 120 const dot = document.createElement('span'); 121 dot.className = 'neomag-swatch-dot'; 122 dot.style.background = paint(option.textContent); 123 dot.setAttribute('aria-hidden', 'true'); 124 const name = document.createElement('span'); 125 name.textContent = option.textContent; 126 button.append(dot, name); 127 button.addEventListener('click', () => { 128 if (button.disabled) return; 129 select.value = option.value; 130 $(select).trigger('change'); 131 sync(); 132 }); 133 group.append(button); 134 return button; 135 }); 136 select.insertAdjacentElement('afterend', group); 137 select.classList.add('neomag-swatch-select'); 138 select.setAttribute('aria-hidden', 'true'); 139 select.tabIndex = -1; 140 // The visible label remains a useful click target after enhancement. 141 if (label) label.addEventListener('click', event => { 142 event.preventDefault(); 143 (buttons.find(b => b.getAttribute('aria-pressed') === 'true' && !b.disabled) || buttons.find(b => !b.disabled))?.focus(); 144 }); 145 controls.set(select, buttons); 146 }); 147 sync(); 148 } 149 function sync() { 150 controls.forEach((buttons, select) => { 151 buttons.forEach(button => { 152 const option = [...select.options].find(o => o.value === button.dataset.value); 153 button.disabled = select.disabled || !option || option.disabled; 154 button.setAttribute('aria-pressed', String(select.value === button.dataset.value)); 155 }); 156 }); 157 } 158 $(document).on('woocommerce_update_variation_values wc_variation_form', '.variations_form', enhance); 159 $(document).on('change reset_data', '.variations_form', sync); 160 $(enhance); 161 162 // Woo's default gallery selector does not target this theme's custom stage. 163 const original = stage ? { src: stage.src, srcset: stage.srcset, sizes: stage.sizes, alt: stage.alt } : null; 164 const firstIndex = stageIndex?.textContent; 165 $(document).on('found_variation', '.pdp-buy > .pdp-form > form.variations_form', function (event, variation) { 166 if (!stage || !variation.image?.src) return; 167 stage.src = variation.image.src; 168 stage.srcset = variation.image.srcset || ''; 169 stage.sizes = variation.image.sizes || ''; 170 stage.alt = variation.image.alt || original.alt; 171 const match = thumbs.find(t => t.querySelector('img')?.dataset.full === variation.image.src); 172 thumbs.forEach(t => t.setAttribute('aria-current', String(t === match))); 173 if (stageIndex) { 174 stageIndex.hidden = !match; 175 if (match) stageIndex.textContent = `${String(thumbs.indexOf(match) + 1).padStart(2, '0')} / ${String(thumbs.length).padStart(2, '0')}`; 176 } 177 }); 178 $(document).on('reset_image', '.pdp-buy > .pdp-form > form.variations_form', () => { 179 if (!stage || !original) return; 180 Object.assign(stage, original); 181 thumbs.forEach((t, i) => t.setAttribute('aria-current', String(i === 0))); 182 if (stageIndex) { stageIndex.hidden = false; stageIndex.textContent = firstIndex; } 183 }); 184})();
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.