PageSourceSearch

https://primo-color.jp/wp-content/themes/SumahoDeHoken/js/price-simulation.js?ver=1.0.8

js primo-color.jp collected 2026-10-04 03:33:38 UTC 11,920 bytes, 388 lines download raw bytes

1(function ($) {
2  if (window.__priceSimulationLoaded) return;
3  window.__priceSimulationLoaded = true;
4
5  let __priceSimTimer = null;
6  const priceSimulationConfig = window.PRICE_SIMULATION || {};
7  const profileSelectedCookie = 'sim_profile_selected';
8
9  /* ===============================
10   * URLパラメータ同期
11   * =============================== */
12  function changeParams(changedEl) {
13    const params = new URLSearchParams(window.location.search);
14
15    const age = getAgeValue();
16    const sex = normalizeSexValue(getSexValue());
17    const profileChanged = isProfileControl(changedEl);
18
19    if (profileChanged) {
20      setCookie(profileSelectedCookie, '1', 1);
21    }
22
23    if (sex) setSexValue(sex);
24
25    if (profileChanged || hasProfileSelection()) {
26      if (age) setCookie('sim_age', age, 1);
27      if (sex && !getForcedSex()) setCookie('sim_sex', sex, 1);
28    }
29
30    if (isUrlParamsSuppressed()) {
31      removeProfileParamsFromUrl(params);
32      return;
33    }
34
35    if (shouldExposeProfileParams(params)) {
36      age ? params.set('age', age) : params.delete('age');
37      sex ? params.set('sex', sex) : params.delete('sex');
38      replaceUrlParams(params);
39    } else {
40      removeProfileParamsFromUrl(params);
41    }
42  }
43
44  function updateParams() {
45    const params = new URLSearchParams(window.location.search);
46    const hasProfileParams = params.has('age') || params.has('sex');
47
48    const age = params.get('age') || getCookie('sim_age');
49    const sex = normalizeSexValue(params.get('sex') || getCookie('sim_sex'));
50
51    if (age) setAgeValue(age);
52    if (sex) setSexValue(sex);
53
54    if (hasProfileParams || hasProfileSelection()) {
55      if (age) setCookie('sim_age', age, 1);
56      if (sex && !getForcedSex()) setCookie('sim_sex', sex, 1);
57    }
58
59    if (isUrlParamsSuppressed()) {
60      removeProfileParamsFromUrl(params);
61      return;
62    }
63
64    if (shouldExposeProfileParams(params)) {
65      age ? params.set('age', age) : params.delete('age');
66      sex ? params.set('sex', sex) : params.delete('sex');
67      replaceUrlParams(params);
68    } else {
69      removeProfileParamsFromUrl(params);
70    }
71  }
72
73  /* ===============================
74   * 値の取得
75   * =============================== */
76  function getSexValue() {
77    const $sex = $('.sex').first();
78
79    if ($sex.is('select')) {
80      return $sex.val();
81    }
82
83    if ($sex.is(':radio')) {
84      return $('.sex:checked').val();
85    }
86
87    return null;
88  }
89
90  function getAgeValue() {
91    return $('.age').first().val();
92  }
93
94  /* ===============================
95   * 値のセット
96   * =============================== */
97  function setSexValue(value) {
98    $('.sex').each(function () {
99      if ($(this).is('select')) {
100        $(this).val(value);
101      }
102
103      if ($(this).is(':radio')) {
104        if ($(this).val() === value) {
105          $(this).prop('checked', true);
106        }
107      }
108    });
109  }
110
111  function setAgeValue(value) {
112    $('.age').val(value);
113  }
114
115  /* ===============================
116   * 強制条件・URL露出制御
117   * =============================== */
118  function isLadyCategoryPage() {
119    return window.location.pathname.includes('/lady/');
120  }
121
122  function getForcedSex() {
123    if (priceSimulationConfig.forceSex) return priceSimulationConfig.forceSex;
124    if (isLadyCategoryPage()) return 'woman';
125    return '';
126  }
127
128  function normalizeSexValue(value) {
129    const forcedSex = getForcedSex();
130    if (forcedSex) return forcedSex;
131    return value || null;
132  }
133
134  function isUrlParamsSuppressed() {
135    return priceSimulationConfig.suppressUrlParams === 1 ||
136      priceSimulationConfig.suppressUrlParams === '1' ||
137      priceSimulationConfig.suppressUrlParams === true;
138  }
139
140  function hasProfileSelection() {
141    return getCookie(profileSelectedCookie) === '1';
142  }
143
144  function isProfileControl(el) {
145    if (!el) return false;
146    const $el = $(el);
147    return $el.hasClass('age') || $el.hasClass('sex');
148  }
149
150  function shouldExposeProfileParams(params) {
151    return hasProfileSelection() || params.has('age') || params.has('sex');
152  }
153
154  function replaceUrlParams(params) {
155    const query = params.toString();
156    const nextUrl = window.location.pathname + (query ? '?' + query : '') + window.location.hash;
157    const currentUrl = window.location.pathname + window.location.search + window.location.hash;
158    if (nextUrl === currentUrl) return;
159    window.history.replaceState(null, '', nextUrl);
160  }
161
162  function removeProfileParamsFromUrl(params) {
163    const hadProfileParams = params.has('age') || params.has('sex');
164    params.delete('age');
165    params.delete('sex');
166    if (hadProfileParams) replaceUrlParams(params);
167  }
168
169  /* ===============================
170   * Cookie ユーティリティ
171   * =============================== */
172  function setCookie(name, value, days = 1) {
173    const d = new Date();
174    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
175    document.cookie =
176      name + '=' + encodeURIComponent(value) +
177      '; expires=' + d.toUTCString() +
178      '; path=/';
179  }
180
181  function getCookie(name) {
182    const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
183    return m ? decodeURIComponent(m[1]) : null;
184  }
185
186  /* ===============================
187   * 価格アニメーション
188   * =============================== */
189  function animateText($el, from, to, duration) {
190    const nFrom = parseInt(String(from).replace(/,/g, ''), 10) || 0;
191    const nTo = parseInt(String(to).replace(/,/g, ''), 10);
192
193    if (isNaN(nTo) || nTo <= 0) return;
194    if ($el.attr('data-animating-to') === String(nTo)) return;
195
196    $el.attr('data-animating-to', nTo);
197    $el.stop(true, true);
198
199    $({ n: nFrom }).animate({ n: nTo }, {
200      duration: duration,
201      step: function () {
202        $el.text(Math.floor(this.n).toLocaleString());
203      },
204      complete: function () {
205        $el.text(nTo.toLocaleString());
206        $el.removeAttr('data-animating-to');
207      }
208    });
209  }
210
211  /* ===============================
212   * 価格計算
213   * =============================== */
214  function changePrice() {
215    const sex = getSexValue();
216    const age = getAgeValue();
217    const key = sex + '-' + age;
218
219    $('.change-price').each(function () {
220      const $price = $(this);
221      const $box = $price.closest('.price');
222      const newPrice = $box.attr('data-' + key);
223      const $parentBox = $price.closest('.shadow-box');
224
225      if (newPrice !== undefined && newPrice !== '' && newPrice !== '0') {
226        $box.removeClass('none');
227        $parentBox.find('.orange').removeClass('none');
228        $price.attr('data-sort-val', newPrice);
229
230        const currentText = $price.text().replace(/,/g, '');
231        if (currentText === String(newPrice) && !$price.attr('data-animating-to')) return;
232
233        animateText($price, currentText, newPrice, 400);
234      } else {
235        $box.addClass('none');
236        $parentBox.find('.orange').addClass('none');
237        $price.attr('data-sort-val', '9999999'); // 条件外は大きな値を保持
238        $price.removeAttr('data-animating-to');
239        $price.text('---');
240      }
241    });
242  }
243
244  /* ===============================
245   * ソート共通ロジック
246   * =============================== */
247  // 価格が無効(9999999)なものを常に後ろにするための比較関数
248  function compareValues(valA, valB, order) {
249    const isInvalidA = (valA >= 9999999);
250    const isInvalidB = (valB >= 9999999);
251
252    // 両方無効なら順序維持
253    if (isInvalidA && isInvalidB) return 0;
254    // Aのみ無効ならAを後ろに (正の値を返す)
255    if (isInvalidA) return 1;
256    // Bのみ無効ならBを後ろに (負の値を返す)
257    if (isInvalidB) return -1;
258
259    // 両方有効な場合のみ、指定の順序で比較
260    if (order === 'lowest') return valA - valB;
261    if (order === 'highest') return valB - valA;
262    return 0;
263  }
264
265  /* ===============================
266   * 商品一覧のソート
267   * =============================== */
268  function sortProducts() {
269    const order = $('#order').val();
270    if (!$('.product-list').length) return;
271
272    $('.product-list').each(function () {
273      const $productList = $(this);
274
275      // この .product-list 自身の直下にある商品だけを対象にする
276      // → .insurance-ranking 内の .product-loop-full は拾わない
277      const items = $productList.children('.product-loop-full').toArray();
278
279      if (!items.length) return;
280
281      const pickup = items.filter(i => $(i).find('.rank.pickup').length);
282      const normal = items.filter(i => !$(i).find('.rank.pickup').length);
283
284      normal.sort((a, b) => {
285        if (order === 'popularity') {
286          const ra = parseInt($(a).find('.rank .number').text(), 10) || 9999;
287          const rb = parseInt($(b).find('.rank .number').text(), 10) || 9999;
288          return ra - rb;
289        }
290
291        const pa = parseInt($(a).find('.change-price').attr('data-sort-val'), 10) || 9999999;
292        const pb = parseInt($(b).find('.change-price').attr('data-sort-val'), 10) || 9999999;
293        return compareValues(pa, pb, order);
294      });
295
296      $productList.empty().append(pickup.concat(normal));
297    });
298  }
299
300  /* ===============================
301   * 比較表のソート
302   * =============================== */
303  function sortCompareColumns() {
304    if (!$('.compare-product-list').length) return;
305    const order = $('#order').val();
306    const $rows = $('.compare-product-item');
307    const $baseRow = $rows.filter((_, r) => $(r).find('.compare-product-column').length).first();
308    if (!$baseRow.length) return;
309
310    const getPriceNum = ($col) => {
311      const val = $col.find('.change-price').first().attr('data-sort-val');
312      return parseInt(val, 10) || 9999999;
313    };
314    const getRankNum = ($col) => parseInt($col.find('.rank .number').text(), 10) || 9999;
315    const getProductId = (el) => $(el).attr('data-product-id');
316
317    const cols = $baseRow.find('.compare-product-column').toArray();
318    const pickup = cols.filter(c => $(c).find('.rank.pickup').length);
319    const normal = cols.filter(c => !$(c).find('.rank.pickup').length);
320
321    normal.sort((a, b) => {
322      if (order === 'popularity') return getRankNum($(a)) - getRankNum($(b));
323      return compareValues(getPriceNum($(a)), getPriceNum($(b)), order);
324    });
325
326    const orderIds = pickup.concat(normal).map(c => getProductId(c)).filter(Boolean);
327
328    $rows.each(function () {
329      const $row = $(this);
330      const $cols = $row.find('.compare-product-column').detach();
331      const map = {};
332      $cols.each(function() { 
333        const id = getProductId(this);
334        if (id) map[id] = this; 
335      });
336      orderIds.forEach(id => { if (map[id]) $row.append(map[id]); });
337    });
338  }
339
340  /* ===============================
341   * 初期化・実行制御
342   * =============================== */
343  function initPriceSimulation() {
344    updateParams();
345    changePrice();
346    sortProducts();
347    sortCompareColumns();
348  }
349
350  function schedulePriceSimulationInit() {
351    if (__priceSimTimer) cancelAnimationFrame(__priceSimTimer);
352    __priceSimTimer = requestAnimationFrame(() => {
353      initPriceSimulation();
354      __priceSimTimer = null;
355    });
356  }
357
358  $(document)
359    .off('change.price', '.age, .sex, #order')
360    .on('change.price', '.age, .sex, #order', function (e) {
361
362      if ($(e.target).hasClass('age')) {
363        setAgeValue($(e.target).val());
364      }
365
366      if ($(e.target).hasClass('sex')) {
367        setSexValue($(e.target).val());
368      }
369
370      changeParams(e.target);
371
372      const isComparePage = $('.compare-product-list').length > 0;
373      const isSortChange = $(e.target).is('#order');
374
375      if (isComparePage && !isSortChange) return;
376
377      schedulePriceSimulationInit();
378    });
379
380  $(function() {
381    if (!$('.compare-product-list').length) {
382      schedulePriceSimulationInit();
383    }
384  });
385
386  window.addEventListener("compare:updated", schedulePriceSimulationInit);
387
388})(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.