PageSourceSearch

https://theneomag.com/wp-content/themes/Divi-child/assets/js/product.js?ver=1789413540

js theneomag.com collected 2026-10-04 05:27:09 UTC 8,598 bytes, 184 lines download raw bytes

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.