PageSourceSearch

https://www.spik-group.co.jp/wp/wp-content/themes/spik-vanstar/assets/js/script.js?ver=1790797720

js spik-group.co.jp collected 2026-10-04 15:43:32 UTC 17,679 bytes, 409 lines download raw bytes

1(function () {
2  'use strict';
3
4  document.documentElement.classList.add('is-fade-ready');
5
6  // ページ表示時のイントロアニメーション。CSSの初期状態を初回描画前に確定させるため、
7  // クラスの付与はこのファイル(head で同期読み込み)の先頭で行う。
8  var introMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
9  var introEnabled = !introMotionQuery.matches;
10
11  // 初期状態と再生をまとめて解除する(片方だけ残すとマスクが外れて途中で表示が飛ぶ)。
12  function clearIntroClasses() {
13    var root = document.documentElement;
14    root.classList.remove('is-intro-ready');
15    root.classList.remove('is-intro-header');
16    root.classList.remove('is-intro-playing');
17    root.classList.remove('is-intro-first');
18    // オープニング後だけ延ばしているKVカーテンの開始時刻も戻す。
19    root.style.removeProperty('--intro-curtain-delay');
20  }
21
22  var introFailsafeTimer = null;
23
24  if (introEnabled) {
25    document.documentElement.classList.add('is-intro-ready');
26
27    // ヘッダーのリビールとTOPのオープニングはセッション初回だけ。
28    // sessionStorage が使えない環境では毎回再生する。
29    var introSessionKey = 'spik-intro-session';
30    var introSessionDone = false;
31
32    try {
33      introSessionDone = !!window.sessionStorage.getItem(introSessionKey);
34      if (!introSessionDone) window.sessionStorage.setItem(introSessionKey, '1');
35    } catch (error) {
36      introSessionDone = false;
37    }
38
39    if (!introSessionDone) {
40      document.documentElement.classList.add('is-intro-header');
41      // オープニングのマークアップは front-page.php にしかないため、TOP以外では何も起きない。
42      document.documentElement.classList.add('is-intro-first');
43    }
44
45    // 後続処理が止まってもヘッダーが隠れたままにならないようにする保険。
46    introFailsafeTimer = window.setTimeout(clearIntroClasses, 4000);
47  }
48
49  // PCはデザイン幅1360pxを基準に、1920pxまで等倍で拡大し、それ以上は固定する。
50  var designWidth = 1360;
51  var maxScaleWidth = 1920;
52
53  function updateClientWidth() {
54    var clientWidth = document.documentElement.clientWidth;
55    if (!clientWidth) return;
56
57    var fluidRemSize = window.innerWidth <= 767
58      ? (clientWidth / 375) * 100
59      : (clientWidth / designWidth) * 100;
60    var remSize = window.innerWidth <= 767
61      ? fluidRemSize
62      : Math.min(fluidRemSize, (maxScaleWidth / designWidth) * 100);
63    document.documentElement.style.setProperty('--rem-size', remSize + 'px');
64    // 1920pxを超えても拡大を続けたい写真用の基準(上限なし)。
65    // レイアウト全体は --rem-size(1920pxで頭打ち)のままで、
66    // 使うのは「両サイドに下地が見えてしまう写真」だけ。
67    document.documentElement.style.setProperty('--rem-fluid', fluidRemSize + 'px');
68  }
69
70  updateClientWidth();
71  requestAnimationFrame(updateClientWidth);
72  window.addEventListener('load', updateClientWidth);
73  window.addEventListener('resize', updateClientWidth);
74
75  document.addEventListener('DOMContentLoaded', function () {
76    // イントロのカーテンを組み立てる。FVのシルエット(左右の帯+ノッチ付きの画像枠)に
77    // 合わせるため、下層FVは3ピース、TOPのKVは1ピースで作る。
78    if (introEnabled) {
79      var introCurtains = [];
80
81      document.querySelectorAll('.c-subpage-hero, #index.root .animation').forEach(function (host) {
82        var curtain = document.createElement('div');
83        curtain.className = 'c-intro-curtain';
84        curtain.setAttribute('aria-hidden', 'true');
85
86        if (host.classList.contains('c-subpage-hero')) {
87          ['left', 'right'].forEach(function (side) {
88            var rail = document.createElement('span');
89            rail.className = 'c-intro-curtain__rail c-intro-curtain__rail--' + side;
90            curtain.appendChild(rail);
91          });
92        }
93
94        var shape = document.createElement('span');
95        shape.className = 'c-intro-curtain__shape';
96        curtain.appendChild(shape);
97
98        host.appendChild(curtain);
99        introCurtains.push(curtain);
100      });
101
102      var introStarted = false;
103
104      // CSSの遅延+再生時間の合計(760ms + 640ms)より後に後始末する。
105      // オープニング後はKVカーテンの終わり(下記)に合わせて延ばす。
106      var startIntro = function (cleanupDelay) {
107        if (introStarted) return;
108        introStarted = true;
109        document.documentElement.classList.add('is-intro-playing');
110
111        window.setTimeout(function () {
112          clearIntroClasses();
113          introCurtains.forEach(function (curtain) {
114            if (curtain.parentNode) curtain.parentNode.removeChild(curtain);
115          });
116        }, typeof cleanupDelay === 'number' ? cleanupDelay : 1500);
117      };
118
119      var firstIntro = document.querySelector('.c-first-intro');
120      var firstIntroPlaying = !!firstIntro && document.documentElement.classList.contains('is-intro-first');
121
122      // 再生しない読み込み(2ページ目以降)ではCSSで非表示だが、DOMも残さない。
123      if (firstIntro && !firstIntroPlaying) {
124        firstIntro.parentNode.removeChild(firstIntro);
125        firstIntro = null;
126      }
127
128      // 幕が開いた先で写真が未読み込みだと空白が見えるため、読み込み完了か
129      // 一定時間の経過まで開始を待つ。
130      // COMPANYは枠(div.c-subpage-hero__image)の中に img がある(横スクロール対応)ため、img を直接探す。
131      var introImage = document.querySelector('.c-subpage-hero img');
132
133      if (firstIntroPlaying) {
134        // オープニング中はスクロールさせない。
135        document.body.style.overflow = 'hidden';
136
137        // オープニングの分だけ保険を後ろへずらす。
138        window.clearTimeout(introFailsafeTimer);
139        introFailsafeTimer = window.setTimeout(clearIntroClasses, 8000);
140
141        // 青い箱は74%地点(4000ms中2960ms)から下へ抜けはじめ、4000msで画面外へ出る(制作会社の調整後の値)。
142        // ヘッダーと文字のリビールは開始から760ms遅れて始まるため、上端が露出する
143        // 2960msにリビールが重なるよう、その760ms前の2200msにページ側のイントロを始める。
144        // KVのカーテン(青)は箱の裏で埋め終えておき(2280〜2880ms)、箱がKVから抜けた
145        // 4000msから上へ抜けさせる(箱の裏で抜けてしまうとシャッターが見えない)。
146        // 後始末はカーテンの終わり(4000 + 720ms)より後に行う。
147        var openingIntroStart = 2200;
148        var openingCurtainStart = 4000;
149        var openingCurtainDuration = 720;
150        document.documentElement.style.setProperty('--intro-curtain-delay', (openingCurtainStart - openingIntroStart) + 'ms');
151        window.setTimeout(function () {
152          startIntro(openingCurtainStart + openingCurtainDuration - openingIntroStart + 200);
153        }, openingIntroStart);
154
155        // 再生後は要素ごと取り除く。
156        window.setTimeout(function () {
157          document.documentElement.classList.remove('is-intro-first');
158          document.body.style.overflow = '';
159          if (firstIntro.parentNode) firstIntro.parentNode.removeChild(firstIntro);
160        }, 4100);
161      } else if (introImage && !introImage.complete) {
162        introImage.addEventListener('load', startIntro);
163        introImage.addEventListener('error', startIntro);
164        window.setTimeout(startIntro, 1200);
165      } else {
166        startIntro();
167      }
168    }
169
170    var fadeTargets = document.querySelectorAll('.js-fade-in');
171    var reduceMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
172
173    fadeTargets.forEach(function (target) {
174      var delay = parseInt(target.getAttribute('data-fade-delay'), 10);
175      var duration = parseInt(target.getAttribute('data-fade-duration'), 10);
176      if (Number.isFinite(delay)) target.style.setProperty('--fade-delay', delay + 'ms');
177      if (Number.isFinite(duration)) target.style.setProperty('--fade-duration', duration + 'ms');
178    });
179
180    // フェードイン完了後は is-fade-done を付け、通常の要素と同じ短いトランジションに戻す
181    // (リンクの a:hover の透過が .is-visible の opacity: 1 に負けないようにするため。CSS側参照)。
182    function markFadeDone(target) {
183      target.classList.add('is-fade-done');
184    }
185
186    if (reduceMotionQuery.matches || !('IntersectionObserver' in window)) {
187      fadeTargets.forEach(function (target) {
188        target.classList.add('is-visible');
189        markFadeDone(target);
190      });
191    } else {
192      var fadeObserver = new IntersectionObserver(function (entries, observer) {
193        entries.forEach(function (entry) {
194          if (!entry.isIntersecting) return;
195          var target = entry.target;
196          target.classList.add('is-visible');
197          observer.unobserve(target);
198
199          // transitionend が来ない環境(transition 無効など)のための保険を兼ねて、
200          // 遅延+再生時間の経過後にも付ける。
201          var delay = parseFloat(target.style.getPropertyValue('--fade-delay')) || 0;
202          var duration = parseFloat(target.style.getPropertyValue('--fade-duration')) || 800;
203          var fadeDoneTimer = window.setTimeout(function () { markFadeDone(target); }, delay + duration + 50);
204          target.addEventListener('transitionend', function onEnd(event) {
205            if (event.target !== target || event.propertyName !== 'opacity') return;
206            target.removeEventListener('transitionend', onEnd);
207            window.clearTimeout(fadeDoneTimer);
208            markFadeDone(target);
209          });
210        });
211      }, {
212        rootMargin: '0px 0px -10% 0px',
213        threshold: 0.08
214      });
215
216      fadeTargets.forEach(function (target) {
217        fadeObserver.observe(target);
218      });
219    }
220
221    // 背景動画(.js-autoplay-video の中の video)は、その範囲が画面に入っている間だけ再生する。
222    // Chrome は画面外の muted 動画の自動再生を始めないことがあるため、見えたタイミングで play() を呼ぶ。
223    // 画面外では止めて負荷を抑える。
224    var autoplayVideoHosts = document.querySelectorAll('.js-autoplay-video');
225    if (autoplayVideoHosts.length && 'IntersectionObserver' in window) {
226      var autoplayVideoObserver = new IntersectionObserver(function (entries) {
227        entries.forEach(function (entry) {
228          var video = entry.target.querySelector('video');
229          if (!video) return;
230          if (entry.isIntersecting) {
231            var playPromise = video.play();
232            if (playPromise && playPromise.catch) playPromise.catch(function () {});
233          } else {
234            video.pause();
235          }
236        });
237      }, { threshold: 0 });
238      autoplayVideoHosts.forEach(function (host) {
239        autoplayVideoObserver.observe(host);
240      });
241    }
242
243    var mobileQuery = window.matchMedia('(max-width: 767px)');
244    var focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
245    var header = document.querySelector('.c-site-header');
246    var menuButton = header && header.querySelector('.c-site-header__hamburger');
247    var menuPanel = header && header.querySelector('.c-site-header__menu-panel');
248    var lastModalTrigger = null;
249
250    function setExpanded(button, expanded) {
251      button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
252    }
253
254    function setAccordionExpanded(button, expanded) {
255      setExpanded(button, expanded);
256      var contentId = button.getAttribute('aria-controls');
257      var content = contentId && document.getElementById(contentId);
258      if (!content) return;
259      content.inert = !expanded;
260      content.setAttribute('aria-hidden', expanded ? 'false' : 'true');
261    }
262
263    function closeMenu(restoreFocus) {
264      if (!header || !menuButton) return;
265      header.classList.remove('is-menu-open');
266      setExpanded(menuButton, false);
267      menuButton.setAttribute('aria-label', 'メニューを開く');
268      document.body.classList.remove('is-menu-open');
269      header.querySelectorAll('.c-site-header__accordion-button').forEach(function (button) {
270        setAccordionExpanded(button, false);
271      });
272      if (restoreFocus) menuButton.focus();
273    }
274
275    function openMenu() {
276      if (!header || !menuButton || !menuPanel) return;
277      header.classList.add('is-menu-open');
278      setExpanded(menuButton, true);
279      menuButton.setAttribute('aria-label', 'メニューを閉じる');
280      document.body.classList.add('is-menu-open');
281    }
282
283    if (menuButton) {
284      menuButton.addEventListener('click', function () {
285        if (header.classList.contains('is-menu-open')) closeMenu(true);
286        else openMenu();
287      });
288    }
289
290    document.querySelectorAll('.c-site-header__accordion-button').forEach(function (button) {
291      button.addEventListener('click', function () {
292        if (!mobileQuery.matches) return;
293        setAccordionExpanded(button, button.getAttribute('aria-expanded') !== 'true');
294      });
295    });
296
297    if (menuPanel) {
298      menuPanel.querySelectorAll('a[href]').forEach(function (link) {
299        link.addEventListener('click', function () {
300          if (mobileQuery.matches) closeMenu(false);
301        });
302      });
303    }
304
305    var footerButtons = document.querySelectorAll('.c-site-footer__accordion-button');
306    function syncFooterAccordions() {
307      footerButtons.forEach(function (button) {
308        button.disabled = !mobileQuery.matches;
309        setAccordionExpanded(button, !mobileQuery.matches);
310      });
311    }
312    footerButtons.forEach(function (button) {
313      button.addEventListener('click', function () {
314        if (!mobileQuery.matches) return;
315        setAccordionExpanded(button, button.getAttribute('aria-expanded') !== 'true');
316      });
317    });
318    syncFooterAccordions();
319
320    function syncResponsiveState() {
321      if (!mobileQuery.matches) closeMenu(false);
322      syncFooterAccordions();
323    }
324    if (mobileQuery.addEventListener) mobileQuery.addEventListener('change', syncResponsiveState);
325    else mobileQuery.addListener(syncResponsiveState);
326
327    var modalSelector = '.officer-modal, .c-modal';
328    var modalDialogSelector = '.officer-modal__dialog, .c-modal__dialog';
329    var modalDismissSelector = '.officer-modal__close, .officer-modal__overlay, .c-modal__close, .c-modal__overlay';
330
331    document.querySelectorAll('.officer-card, .c-modal-trigger').forEach(function (trigger) {
332      trigger.addEventListener('click', function () {
333        lastModalTrigger = trigger;
334      });
335    });
336
337    function activeModal() {
338      if (!/^#[\w-]+$/.test(window.location.hash)) return null;
339      var target = document.querySelector(window.location.hash);
340      return target && target.matches(modalSelector) ? target : null;
341    }
342
343    function syncModal() {
344      document.querySelectorAll(modalSelector).forEach(function (modal) {
345        if (modal.getAttribute('aria-hidden') === 'false') modal.setAttribute('aria-hidden', 'true');
346      });
347      var modal = activeModal();
348      document.body.classList.toggle('is-modal-open', Boolean(modal));
349      if (modal) {
350        modal.setAttribute('aria-hidden', 'false');
351        var dialog = modal.querySelector(modalDialogSelector);
352        if (dialog) requestAnimationFrame(function () { dialog.focus(); });
353      } else if (lastModalTrigger) {
354        lastModalTrigger.focus();
355      }
356    }
357
358    function closeModal() {
359      if (!activeModal()) return;
360      window.location.hash = 'modal-closed';
361    }
362
363    document.querySelectorAll(modalDismissSelector).forEach(function (button) {
364      button.addEventListener('click', function (event) {
365        event.preventDefault();
366        closeModal();
367      });
368    });
369    window.addEventListener('hashchange', syncModal);
370    syncModal();
371
372    document.addEventListener('keydown', function (event) {
373      var modal = activeModal();
374      if (event.key === 'Escape') {
375        if (modal) {
376          event.preventDefault();
377          closeModal();
378        } else if (header && header.classList.contains('is-menu-open')) {
379          event.preventDefault();
380          closeMenu(true);
381        }
382        return;
383      }
384
385      if (event.key !== 'Tab') return;
386      var container = modal || (header && header.classList.contains('is-menu-open') ? header : null);
387      if (!container) return;
388      var items = Array.prototype.filter.call(container.querySelectorAll(focusableSelector), function (item) {
389        var style = window.getComputedStyle(item);
390        return item.tabIndex >= 0
391          && !item.hasAttribute('disabled')
392          && item.getAttribute('aria-hidden') !== 'true'
393          && style.display !== 'none'
394          && style.visibility !== 'hidden'
395          && item.getClientRects().length > 0;
396      });
397      if (!items.length) return;
398      var first = items[0];
399      var last = items[items.length - 1];
400      if (event.shiftKey && document.activeElement === first) {
401        event.preventDefault();
402        last.focus();
403      } else if (!event.shiftKey && document.activeElement === last) {
404        event.preventDefault();
405        first.focus();
406      }
407    });
408  });
409})();

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.