PageSourceSearch

https://www.nitoms.com/assets/js/videoYoutube.js?_=1755144450

js nitoms.com collected 2026-10-02 17:01:48 UTC 32,366 bytes, 1,063 lines download raw bytes

1/**
2 * 統合クッキー同意管理システム
3 * クッキー同意後またはすでに同意済みの場合のみ指定されたアクションを実行
4 * - 外部スクリプトの読み込み
5 * - YouTube動画の読み込み
6 * - DOM要素の更新
7 * - GTM/Analytics等のトラッキングスクリプトのブロック強化
8 */
9(function() {
10  'use strict';
11
12  // デフォルト設定
13  const DEFAULT_CONFIG = {
14    cookieName: '_ul_cookie_consent',
15    allowValue: 'allow',
16    eventName: 'allow_cookie',
17    debug: false,
18
19    // スクリプトブロッキング設定(GTM対応強化)
20    blocking: {
21      enabled: true,
22      // ブロック対象のスクリプト属性
23      blockAttribute: 'data-cookieyes',
24      // ブロック対象のスクリプトsrc(部分一致)- GTMパターン強化
25      blockPatterns: [
26        'google-analytics.com/ga.js',
27        'google-analytics.com/analytics.js',
28        'googletagmanager.com/gtag/js',
29        'googletagmanager.com/gtm.js',
30        'www.googletagmanager.com/gtm.js', // 追加: www付きパターン
31        'googletagmanager.com/ns.html', // 追加: noscript用
32        'www.googletagmanager.com/ns.html', // 追加: www付きnoscript
33        'facebook.net/en_US/fbevents.js',
34        'connect.facebook.net',
35        'doubleclick.net',
36        'googlesyndication.com'
37      ],
38      // ブロック対象のinlineスクリプトパターン(GTM対応強化)
39      inlineBlockPatterns: [
40        'gtag(',
41        'ga(',
42        '_gaq',
43        'fbq(',
44        'GoogleAnalyticsObject',
45        'dataLayer', // 追加: GTM dataLayer
46        'gtm_id', // 追加: GTM ID
47        'GTM-' // 追加: GTM-で始まる文字列
48      ]
49    },
50
51    // スクリプト読み込み設定(条件付き読み込み対応)
52    scripts: [
53      {
54        src: 'https://form.nitoms.com/web/mrcform.js',
55        dataId: 'd9xzkh',
56        delay: 0, // 次のスクリプト読み込み前の待機時間(ms)
57        // パス条件設定
58        pathConditions: {
59          enabled: true,
60          // 許可するパスパターン(正規表現文字列)
61          allowedPaths: ['^/special/linetape/.*'],
62          // 手動で指定するパス(完全一致)
63          manualPaths: [],
64          // デバッグモード:条件チェック結果をログ出力
65          debug: true
66        }
67      },
68      {
69        src: '//ar.mrc-s.com/web/ar.js',
70        dataId: 'd9xzkh',
71        delay: 0,
72        // 同じ条件を適用
73        pathConditions: {
74          enabled: true,
75          allowedPaths: ['^/special/linetape/.*'],
76          manualPaths: [],
77          debug: true
78        }
79      }
80      // GTMを動的読み込みする場合は以下のコメントアウトを解除し、GTM IDを設定
81      // {
82      //   src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX',
83      //   delay: 0,
84      //   attributes: {
85      //     async: 'true'
86      //   }
87      // }
88    ],
89
90    // YouTube動画設定
91    youtube: {
92      enabled: true,
93      videoSelector: '.js-video-frame',
94      baseUrl: 'https://www.youtube.com/embed/',
95      defaultParams: '?rel=0&showinfo=0',
96      defaultAllow: 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
97      observeMutations: true
98    },
99
100    // DOM更新設定(GTM noscript対応追加)
101    domUpdates: {
102      enabled: true,
103      actions: [
104        {
105          selector: '.contact.-notice',
106          action: 'removeClass',
107          value: '-notice'
108        },
109        {
110          selector: '.ul_notice',
111          action: 'hide'
112        },
113        {
114          selector: 'a[data-mrc]',
115          action: 'enableLinks'
116        },
117        // GTM関連のnoscriptタグも非表示にする
118        {
119          selector: 'noscript iframe[src*="googletagmanager.com"]',
120          action: 'hide'
121        }
122      ]
123    }
124  };
125
126  // グローバル状態管理
127  let config = {};
128  let isTriggeredByEvent = false;
129  let scriptsLoaded = false;
130  let youtubeInitialized = false;
131  let isSystemInitialized = false;
132  let blockedScripts = [];
133  let originalScripts = [];
134
135  // グローバル状態をwindowオブジェクトに設定
136  window.__has_ul_consent = false;
137
138  /**
139   * 設定をマージ
140   * @param {Object} userConfig ユーザー設定
141   */
142  function mergeConfig(userConfig = {}) {
143    config = Object.assign({}, DEFAULT_CONFIG, userConfig);
144
145    // ネストされたオブジェクトもマージ
146    if (userConfig.blocking) {
147      config.blocking = Object.assign({}, DEFAULT_CONFIG.blocking, userConfig.blocking);
148    }
149    if (userConfig.youtube) {
150      config.youtube = Object.assign({}, DEFAULT_CONFIG.youtube, userConfig.youtube);
151    }
152    if (userConfig.domUpdates) {
153      config.domUpdates = Object.assign({}, DEFAULT_CONFIG.domUpdates, userConfig.domUpdates);
154    }
155
156    // スクリプト設定の詳細マージ
157    if (userConfig.scripts && Array.isArray(userConfig.scripts)) {
158      config.scripts = userConfig.scripts.map((userScript, index) => {
159        const defaultScript = DEFAULT_CONFIG.scripts[index] || {};
160        const mergedScript = Object.assign({}, defaultScript, userScript);
161
162        // pathConditions の詳細マージ
163        if (userScript.pathConditions && defaultScript.pathConditions) {
164          mergedScript.pathConditions = Object.assign({}, defaultScript.pathConditions, userScript.pathConditions);
165        }
166
167        return mergedScript;
168      });
169    }
170  }
171
172  /**
173   * デバッグログ出力
174   * @param {...any} args ログ引数
175   */
176  function debugLog(...args) {
177    if (config.debug) {
178      console.log('[CookieConsent]', ...args);
179    }
180  }
181
182  /**
183   * 現在のパスが条件に合致するかチェック
184   * @param {Object} pathConditions パス条件設定
185   * @returns {boolean} 合致する場合true
186   */
187  function checkPathConditions(pathConditions) {
188    if (!pathConditions || !pathConditions.enabled) {
189      return true; // 条件が無効な場合は常に許可
190    }
191
192    const currentPath = window.location.pathname;
193
194    if (pathConditions.debug) {
195      debugLog(`パス条件チェック - 現在のパス: ${currentPath}`);
196    }
197
198    // 手動指定パスの完全一致チェック
199    if (pathConditions.manualPaths && pathConditions.manualPaths.length > 0) {
200      const manualMatch = pathConditions.manualPaths.includes(currentPath);
201      if (pathConditions.debug) {
202        debugLog(`手動パス一致チェック: ${manualMatch}`, pathConditions.manualPaths);
203      }
204      if (manualMatch) {
205        return true;
206      }
207    }
208
209    // 正規表現パターンチェック
210    if (pathConditions.allowedPaths && pathConditions.allowedPaths.length > 0) {
211      for (const pattern of pathConditions.allowedPaths) {
212        try {
213          const regex = new RegExp(pattern);
214          const regexMatch = regex.test(currentPath);
215          if (pathConditions.debug) {
216            debugLog(`正規表現パターン "${pattern}" チェック: ${regexMatch}`);
217          }
218          if (regexMatch) {
219            return true;
220          }
221        } catch (error) {
222          console.error(`無効な正規表現パターン: ${pattern}`, error);
223        }
224      }
225    }
226
227    if (pathConditions.debug) {
228      debugLog(`パス条件チェック結果: 不一致`);
229    }
230    return false;
231  }
232
233  /**
234   * スクリプトが読み込み可能かチェック
235   * @param {Object} scriptConfig スクリプト設定
236   * @returns {boolean} 読み込み可能な場合true
237   */
238  function canLoadScript(scriptConfig) {
239    if (!scriptConfig.pathConditions) {
240      return true; // パス条件がない場合は常に許可
241    }
242
243    const canLoad = checkPathConditions(scriptConfig.pathConditions);
244
245    if (scriptConfig.pathConditions.debug || config.debug) {
246      debugLog(`スクリプト読み込み可否: ${scriptConfig.src} -> ${canLoad ? '許可' : '拒否'}`);
247    }
248
249    return canLoad;
250  }
251
252  /**
253   * クッキーの値を取得
254   * @param {string} name クッキー名
255   * @returns {string|null} クッキーの値、存在しない場合はnull
256   */
257  function getCookieValue(name) {
258    const value = `; ${document.cookie}`;
259    const parts = value.split(`; ${name}=`);
260    if (parts.length === 2) {
261      return parts
262        .pop()
263        .split(';')
264        .shift();
265    }
266    return null;
267  }
268
269  /**
270   * クッキー同意状態をチェック
271   * @returns {boolean} 同意済みの場合true
272   */
273  function isConsentGiven() {
274    const cookieConsent = getCookieValue(config.cookieName);
275    return cookieConsent === config.allowValue;
276  }
277
278  /**
279   * スクリプトがブロック対象かチェック
280   * @param {HTMLScriptElement} script チェック対象のスクリプト要素
281   * @returns {boolean} ブロック対象の場合true
282   */
283  function shouldBlockScript(script) {
284    if (!config.blocking.enabled) return false;
285
286    // data-cookieyes属性がある場合はブロック
287    if (script.hasAttribute(config.blocking.blockAttribute)) {
288      return true;
289    }
290
291    // src属性のチェック
292    if (script.src) {
293      for (const pattern of config.blocking.blockPatterns) {
294        if (script.src.includes(pattern)) {
295          return true;
296        }
297      }
298    }
299
300    // インラインスクリプトのチェック
301    if (!script.src && script.textContent) {
302      for (const pattern of config.blocking.inlineBlockPatterns) {
303        if (script.textContent.includes(pattern)) {
304          return true;
305        }
306      }
307    }
308
309    return false;
310  }
311
312  /**
313   * 既存のスクリプトをブロック
314   */
315  function blockExistingScripts() {
316    if (!config.blocking.enabled) return;
317
318    debugLog('既存スクリプトのブロック処理開始');
319
320    const scripts = document.querySelectorAll('script');
321    let blockedCount = 0;
322
323    scripts.forEach(script => {
324      if (shouldBlockScript(script)) {
325        // オリジナルの属性を保存
326        const originalData = {
327          element: script,
328          src: script.src,
329          textContent: script.textContent,
330          attributes: {}
331        };
332
333        // 全属性を保存
334        Array.from(script.attributes).forEach(attr => {
335          originalData.attributes[attr.name] = attr.value;
336        });
337
338        originalScripts.push(originalData);
339
340        // スクリプトを無効化
341        if (script.src) {
342          script.removeAttribute('src');
343        }
344        script.textContent = '';
345        script.type = 'text/plain';
346
347        blockedScripts.push(script);
348        blockedCount++;
349
350        debugLog(`スクリプトをブロックしました: ${originalData.src || 'inline script'}`);
351      }
352    });
353
354    debugLog(`${blockedCount}個のスクリプトをブロックしました`);
355  }
356
357  /**
358   * ブロックされたスクリプトを復元・実行
359   */
360  function restoreBlockedScripts() {
361    if (!config.blocking.enabled || originalScripts.length === 0) return;
362
363    debugLog('ブロックされたスクリプトの復元開始');
364
365    originalScripts.forEach((originalData, index) => {
366      try {
367        // 新しいスクリプト要素を作成
368        const newScript = document.createElement('script');
369
370        // 属性を復元
371        Object.entries(originalData.attributes).forEach(([name, value]) => {
372          if (name !== 'type') {
373            // typeは最後に設定
374            newScript.setAttribute(name, value);
375          }
376        });
377
378        // src または textContent を復元
379        if (originalData.src) {
380          newScript.src = originalData.src;
381        } else {
382          newScript.textContent = originalData.textContent;
383        }
384
385        // typeを復元(実行可能にする)
386        newScript.type = originalData.attributes.type || 'text/javascript';
387
388        // 元の要素を新しい要素で置き換え
389        const oldScript = originalData.element;
390        if (oldScript.parentNode) {
391          oldScript.parentNode.insertBefore(newScript, oldScript);
392          oldScript.parentNode.removeChild(oldScript);
393        }
394
395        debugLog(`スクリプトを復元しました: ${originalData.src || 'inline script'}`);
396      } catch (error) {
397        console.error('スクリプト復元中にエラーが発生しました:', error, originalData);
398      }
399    });
400
401    // 復元完了後にクリア
402    originalScripts = [];
403    blockedScripts = [];
404
405    debugLog('ブロックされたスクリプトの復元完了');
406  }
407
408  /**
409   * 新しいスクリプトの追加を監視・ブロック
410   */
411  function observeAndBlockNewScripts() {
412    if (!config.blocking.enabled || !window.MutationObserver) return;
413
414    const observer = new MutationObserver(mutations => {
415      if (isConsentGiven()) {
416        observer.disconnect();
417        return;
418      }
419
420      mutations.forEach(mutation => {
421        mutation.addedNodes.forEach(node => {
422          if (node.tagName === 'SCRIPT' && shouldBlockScript(node)) {
423            debugLog('新しいスクリプトをブロック:', node.src || 'inline');
424
425            // オリジナルデータを保存
426            const originalData = {
427              element: node,
428              src: node.src,
429              textContent: node.textContent,
430              attributes: {}
431            };
432
433            Array.from(node.attributes).forEach(attr => {
434              originalData.attributes[attr.name] = attr.value;
435            });
436
437            originalScripts.push(originalData);
438
439            // スクリプトを無効化
440            if (node.src) {
441              node.removeAttribute('src');
442            }
443            node.textContent = '';
444            node.type = 'text/plain';
445
446            blockedScripts.push(node);
447          }
448        });
449      });
450    });
451
452    observer.observe(document, {
453      childList: true,
454      subtree: true
455    });
456
457    debugLog('新しいスクリプトの監視を開始');
458  }
459
460  /**
461   * DOM要素を更新
462   */
463  function updateDOM() {
464    if (!config.domUpdates.enabled) return;
465
466    try {
467      debugLog('DOM更新開始');
468
469      config.domUpdates.actions.forEach(actionConfig => {
470        const elements = document.querySelectorAll(actionConfig.selector);
471
472        elements.forEach(element => {
473          switch (actionConfig.action) {
474            case 'removeClass':
475              element.classList.remove(actionConfig.value);
476              break;
477
478            case 'addClass':
479              element.classList.add(actionConfig.value);
480              break;
481
482            case 'hide':
483              element.style.display = 'none';
484              break;
485
486            case 'show':
487              element.style.display = '';
488              break;
489
490            case 'enableLinks':
491              const url = element.getAttribute('data-mrc');
492              if (url) {
493                element.href = url;
494                element.removeAttribute('style');
495              }
496              break;
497
498            case 'setAttribute':
499              if (actionConfig.attribute && actionConfig.value !== undefined) {
500                element.setAttribute(actionConfig.attribute, actionConfig.value);
501              }
502              break;
503
504            case 'removeAttribute':
505              if (actionConfig.attribute) {
506                element.removeAttribute(actionConfig.attribute);
507              }
508              break;
509
510            default:
511              console.warn('未知のDOMアクション:', actionConfig.action);
512          }
513        });
514      });
515
516      debugLog('DOM更新完了');
517    } catch (error) {
518      console.warn('DOM更新中にエラーが発生しました:', error);
519    }
520  }
521
522  /**
523   * スクリプトを動的に読み込み
524   * @param {Object} scriptConfig スクリプト設定
525   * @returns {Promise} スクリプト読み込み完了のPromise
526   */
527  function loadScript(scriptConfig) {
528    return new Promise((resolve, reject) => {
529      const script = document.createElement('script');
530      script.src = scriptConfig.src;
531
532      // 追加属性を設定
533      if (scriptConfig.dataId) {
534        script.setAttribute('data-id', scriptConfig.dataId);
535      }
536
537      if (scriptConfig.attributes) {
538        Object.entries(scriptConfig.attributes).forEach(([key, value]) => {
539          script.setAttribute(key, value);
540        });
541      }
542
543      script.onload = () => {
544        debugLog(`スクリプト読み込み完了: ${scriptConfig.src}`);
545        resolve();
546      };
547
548      script.onerror = () => {
549        const error = new Error(`Failed to load script: ${scriptConfig.src}`);
550        console.error(error);
551        reject(error);
552      };
553
554      document.body.appendChild(script);
555    });
556  }
557
558  /**
559   * 全スクリプトを順次読み込み(直列実行、パス条件付き)
560   */
561  async function loadAllScripts() {
562    if (scriptsLoaded || config.scripts.length === 0) {
563      if (scriptsLoaded) {
564        debugLog('スクリプトは既に読み込まれています');
565      }
566      return;
567    }
568
569    try {
570      debugLog('外部スクリプト読み込み開始(順次実行、パス条件チェック付き)');
571
572      let loadedCount = 0;
573      let skippedCount = 0;
574
575      // スクリプトを順次読み込み(前のスクリプトが完了してから次を開始)
576      for (let i = 0; i < config.scripts.length; i++) {
577        const scriptConfig = config.scripts[i];
578
579        // パス条件チェック
580        if (!canLoadScript(scriptConfig)) {
581          debugLog(`スクリプトをスキップ (パス条件不一致): ${scriptConfig.src}`);
582          skippedCount++;
583          continue;
584        }
585
586        debugLog(
587          `スクリプト ${loadedCount + 1}/${config.scripts.length - skippedCount} 読み込み開始: ${scriptConfig.src}`
588        );
589
590        await loadScript(scriptConfig);
591        loadedCount++;
592
593        debugLog(`スクリプト ${loadedCount}/${config.scripts.length - skippedCount} 読み込み完了: ${scriptConfig.src}`);
594
595        // 次のスクリプト読み込み前に少し待機(必要に応じて)
596        if (scriptConfig.delay && i < config.scripts.length - 1) {
597          debugLog(`${scriptConfig.delay}ms 待機中...`);
598          await new Promise(resolve => setTimeout(resolve, scriptConfig.delay));
599        }
600      }
601
602      scriptsLoaded = true;
603      debugLog(`スクリプト読み込み完了: ${loadedCount}個読み込み、${skippedCount}個スキップ`);
604    } catch (error) {
605      console.error('スクリプト読み込み中にエラーが発生しました:', error);
606    }
607  }
608
609  /**
610   * YouTube iframeを作成
611   * @param {HTMLElement} element 対象要素
612   * @returns {HTMLIFrameElement|null} 作成されたiframe、失敗時はnull
613   */
614  function createSingleYoutubeFrame(element) {
615    try {
616      const id = element.getAttribute('data-video-id');
617      if (!id) {
618        console.warn('data-video-id属性が見つかりません:', element);
619        return null;
620      }
621
622      // 既にiframeが存在するかチェック
623      if (element.querySelector('iframe')) {
624        debugLog('既にiframeが存在します:', id);
625        return null;
626      }
627
628      const frameId = element.getAttribute('data-frame-id');
629      const style = element.getAttribute('data-style');
630      const doClick = element.getAttribute('data-doclick');
631      const allowed = element.getAttribute('data-allow');
632
633      // iframe要素を作成
634      const iframe = document.createElement('iframe');
635      iframe.src = `${config.youtube.baseUrl}${id}${config.youtube.defaultParams}`;
636      iframe.allow = allowed || config.youtube.defaultAllow;
637      iframe.allowFullscreen = true;
638
639      // 追加属性の設定
640      if (frameId && frameId.trim()) {
641        iframe.setAttribute('id', frameId);
642      }
643
644      if (style && style.trim()) {
645        iframe.setAttribute('style', style);
646      }
647
648      if (doClick && doClick.trim()) {
649        iframe.setAttribute('onclick', doClick);
650      }
651
652      element.appendChild(iframe);
653      debugLog(`YouTube動画を読み込みました: ${id}`);
654      return iframe;
655    } catch (error) {
656      console.error('YouTube iframe作成中にエラーが発生しました:', error, element);
657      return null;
658    }
659  }
660
661  /**
662   * 全てのYouTube動画フレームを作成
663   */
664  function createYoutubeFrames() {
665    if (!config.youtube.enabled) return;
666
667    const videos = document.querySelectorAll(config.youtube.videoSelector);
668
669    if (videos.length === 0) {
670      debugLog('YouTube動画要素が見つかりませんでした');
671      return;
672    }
673
674    debugLog(`${videos.length}個のYouTube動画要素を処理中...`);
675
676    let successCount = 0;
677    videos.forEach(video => {
678      if (video.nodeType === Node.ELEMENT_NODE) {
679        const iframe = createSingleYoutubeFrame(video);
680        if (iframe) {
681          successCount++;
682        }
683      }
684    });
685
686    debugLog(`YouTube動画読み込み完了: ${successCount}/${videos.length}`);
687  }
688
689  /**
690   * 新しく追加された動画要素の監視
691   */
692  function observeNewVideos() {
693    if (!window.MutationObserver || !window.__has_ul_consent || !config.youtube.observeMutations) {
694      return;
695    }
696
697    if (!document.body) {
698      setTimeout(observeNewVideos, 100);
699      return;
700    }
701
702    const observer = new MutationObserver(mutations => {
703      mutations.forEach(mutation => {
704        mutation.addedNodes.forEach(node => {
705          if (node.nodeType === Node.ELEMENT_NODE) {
706            const newVideos =
707              node.matches && node.matches(config.youtube.videoSelector)
708                ? [node]
709                : node.querySelectorAll
710                ? node.querySelectorAll(config.youtube.videoSelector)
711                : [];
712
713            if (newVideos.length > 0) {
714              debugLog(`新しいYouTube動画要素を検出: ${newVideos.length}個`);
715              newVideos.forEach(video => {
716                createSingleYoutubeFrame(video);
717              });
718            }
719          }
720        });
721      });
722    });
723
724    observer.observe(document.body, {
725      childList: true,
726      subtree: true
727    });
728
729    debugLog('YouTube動画の動的追加監視を開始');
730  }
731
732  /**
733   * 同意後の全ての処理を実行
734   */
735  async function executeConsentActions() {
736    if (isSystemInitialized) {
737      debugLog('既に初期化済みです');
738      return;
739    }
740
741    debugLog('クッキー同意確認済み - 処理開始');
742
743    try {
744      // DOM更新
745      updateDOM();
746
747      // ブロックされたスクリプトを復元・実行
748      restoreBlockedScripts();
749
750      // 外部スクリプト読み込み(パス条件チェック付き)
751      await loadAllScripts();
752
753      // YouTube動画初期化
754      if (config.youtube.enabled) {
755        createYoutubeFrames();
756        window.__UL_F_CONSENT = createYoutubeFrames;
757
758        // 動的追加の監視を開始
759        if (config.youtube.observeMutations) {
760          setTimeout(observeNewVideos, 1000);
761        }
762      }
763
764      // グローバル状態を更新
765      window.__has_ul_consent = true;
766      isSystemInitialized = true;
767
768      debugLog('全ての処理が完了しました');
769
770      // イベント経由で実行された場合、window loadイベントを発火
771      if (isTriggeredByEvent) {
772        debugLog('window loadイベントを発火');
773        window.dispatchEvent(new Event('load'));
774      }
775    } catch (error) {
776      console.error('同意処理中にエラーが発生しました:', error);
777    }
778  }
779
780  /**
781   * 同意イベントのハンドラー
782   */
783  function handleConsentEvent() {
784    debugLog('クッキー同意イベントを受信');
785    isTriggeredByEvent = true;
786
787    // イベント後にクッキーが正しく設定されているか再確認
788    setTimeout(() => {
789      if (isConsentGiven()) {
790        executeConsentActions();
791      } else {
792        console.warn('同意イベント後もクッキーが設定されていません');
793      }
794    }, 100);
795  }
796
797  /**
798   * イベントリスナーをセットアップ
799   */
800  function setupEventListener() {
801    function waitForBody() {
802      if (document.body) {
803        debugLog('クッキー同意イベントリスナーを設定');
804        document.body.addEventListener(config.eventName, handleConsentEvent, {
805          once: true
806        });
807      } else {
808        setTimeout(waitForBody, 10);
809      }
810    }
811    waitForBody();
812  }
813
814  /**
815   * DOM準備完了を確実に待つ
816   * @param {Function} callback 実行するコールバック関数
817   */
818  function ensureDOMReady(callback) {
819    if (document.readyState === 'loading') {
820      document.addEventListener('DOMContentLoaded', callback, {once: true});
821    } else {
822      callback();
823    }
824  }
825
826  /**
827   * 初期化処理
828   */
829  function initialize() {
830    debugLog('統合クッキー同意管理システム初期化');
831
832    // スクリプトブロッキングを開始
833    if (config.blocking.enabled && !isConsentGiven()) {
834      blockExistingScripts();
835      observeAndBlockNewScripts();
836    }
837
838    if (isConsentGiven()) {
839      debugLog('クッキー同意済み - 即座に処理実行');
840      ensureDOMReady(executeConsentActions);
841    } else {
842      debugLog('クッキー未同意 - 同意イベント待機中');
843      setupEventListener();
844    }
845  }
846
847  /**
848   * システム開始
849   * @param {Object} userConfig ユーザー設定
850   */
851  function start(userConfig = {}) {
852    mergeConfig(userConfig);
853
854    if (document.readyState === 'loading') {
855      document.addEventListener('DOMContentLoaded', initialize, {once: true});
856    } else {
857      initialize();
858    }
859  }
860
861  /**
862   * GTMブロック状況を確認するデバッグ関数
863   */
864  function checkGTMBlocking() {
865    console.log('=== GTM ブロッキング状況 ===');
866    console.log('同意状況:', isConsentGiven());
867    console.log('初期化状況:', isSystemInitialized);
868    console.log('ブロック済みスクリプト数:', blockedScripts.length);
869    console.log('復元対象スクリプト数:', originalScripts.length);
870
871    // GTM関連スクリプトの確認
872    const gtmScripts = document.querySelectorAll('script[src*="googletagmanager.com"]');
873    console.log('GTMスクリプト要素数:', gtmScripts.length);
874
875    gtmScripts.forEach((script, index) => {
876      console.log(`GTMスクリプト${index + 1}:`, {
877        src: script.src,
878        type: script.type,
879        blocked: script.type === 'text/plain'
880      });
881    });
882
883    // noscript内のGTMも確認
884    const noscriptGTM = document.querySelectorAll('noscript iframe[src*="googletagmanager.com"]');
885    console.log('GTM noscript要素数:', noscriptGTM.length);
886  }
887
888  // グローバルAPIとして公開
889  window.CookieConsentManager = {
890    start: start,
891
892    // 設定用のヘルパー
893    createConfig: function(options = {}) {
894      return {
895        debug: options.debug || false,
896        scripts: options.scripts || [],
897        youtube: {
898          enabled: options.youtube !== false,
899          videoSelector: options.youtubeSelector || '.js-video-frame',
900          ...options.youtubeConfig
901        },
902        blocking: {
903          enabled: options.blocking !== false,
904          ...options.blockingConfig
905        },
906        domUpdates: {
907          enabled: options.domUpdates !== false,
908          actions: options.domActions || DEFAULT_CONFIG.domUpdates.actions
909        },
910        ...options
911      };
912    },
913
914    // パス条件のヘルパー関数
915    createPathCondition: function(allowedPaths = [], manualPaths = [], debug = false) {
916      return {
917        enabled: true,
918        allowedPaths: Array.isArray(allowedPaths) ? allowedPaths : [allowedPaths],
919        manualPaths: Array.isArray(manualPaths) ? manualPaths : [manualPaths],
920        debug: debug
921      };
922    },
923
924    // 状態確認用(デバッグ用)
925    debug: {
926      isConsentGiven: () => isConsentGiven(),
927      isInitialized: () => isSystemInitialized,
928      scriptsLoaded: () => scriptsLoaded,
929      youtubeCount: () => document.querySelectorAll(config.youtube?.videoSelector || '.js-video-frame').length,
930      loadedYoutubeCount: () =>
931        document.querySelectorAll(`${config.youtube?.videoSelector || '.js-video-frame'} iframe`).length,
932      blockedScriptsCount: () => blockedScripts.length,
933      originalScriptsCount: () => originalScripts.length,
934      forceExecute: executeConsentActions,
935      config: () => config,
936      currentPath: () => window.location.pathname,
937      checkPathForScript: scriptIndex => {
938        const script = config.scripts[scriptIndex];
939        return script ? canLoadScript(script) : false;
940      },
941      testPathCondition: checkPathConditions,
942      // GTMブロッキング状況確認関数を追加
943      checkGTMBlocking: checkGTMBlocking
944    }
945  };
946
947  // デフォルトで自動開始(後方互換性のため)
948  start();
949})();
950
951// =============================================================================
952// GTMブロッキング強化設定(使用例)
953// 以下の設定は必要に応じてコメントアウトを解除して使用してください
954// =============================================================================
955
956/*
957// カスタム設定でGTMブロッキングを強化する場合の例
958const customGTMConfig = {
959  debug: true,
960  
961  // 既存のscripts設定に加えて、GTMを動的読み込みしたい場合
962  scripts: [
963    // 既存のスクリプト設定
964    {
965      src: 'https://form.nitoms.com/web/mrcform.js',
966      dataId: 'd9xzkh',
967      delay: 0,
968      pathConditions: {
969        enabled: true,
970        allowedPaths: ['^/special/linetape/.*'],
971        manualPaths: [],
972        debug: true
973      }
974    },
975    {
976      src: '//ar.mrc-s.com/web/ar.js',
977      dataId: 'd9xzkh',
978      delay: 0,
979      pathConditions: {
980        enabled: true,
981        allowedPaths: ['^/special/linetape/.*'],
982        manualPaths: [],
983        debug: true
984      }
985    },
986    // GTMを同意後に動的読み込みする場合(GTM IDを実際の値に変更)
987    {
988      src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX',
989      delay: 100,
990      attributes: {
991        async: 'true'
992      }
993    }
994  ],
995
996  // 追加のDOM操作が必要な場合
997  domUpdates: {
998    enabled: true,
999    actions: [
1000      {
1001        selector: '.contact.-notice',
1002        action: 'removeClass',
1003        value: '-notice'
1004      },
1005      {
1006        selector: '.ul_notice',
1007        action: 'hide'
1008      },
1009      {
1010        selector: 'a[data-mrc]',
1011        action: 'enableLinks'
1012      },
1013      // GTM noscriptを非表示
1014      {
1015        selector: 'noscript iframe[src*="googletagmanager.com"]',
1016        action: 'hide'
1017      },
1018      // 必要に応じて他のGTM関連要素も非表示
1019      {
1020        selector: '.gtm-blocked-notice',
1021        action: 'show'
1022      }
1023    ]
1024  }
1025};
1026
1027// カスタム設定で再起動したい場合
1028// window.CookieConsentManager.start(customGTMConfig);
1029*/
1030
1031// =============================================================================
1032// デバッグ用関数(開発時のみ使用)
1033// =============================================================================
1034
1035// コンソールでGTMブロック状況を確認: window.CookieConsentManager.debug.checkGTMBlocking()
1036// 同意状況を確認: window.CookieConsentManager.debug.isConsentGiven()
1037// 強制実行(テスト用): window.CookieConsentManager.debug.forceExecute()
1038// 設定確認: window.CookieConsentManager.debug.config()
1039
1040// =============================================================================
1041// 実装のポイント
1042// =============================================================================
1043/*
10441. GTMブロッキング強化:
1045   - www.googletagmanager.com パターンを追加
1046   - ns.html (noscript) パターンを追加
1047   - dataLayer, GTM- などのインラインパターンを追加
1048
10492. noscript対応:
1050   - GTMのnoscript内iframeも非表示にするDOM操作を追加
1051
10523. デバッグ機能強化:
1053   - checkGTMBlocking() 関数でGTMブロック状況を詳細確認可能
1054
10554. 動的読み込み対応:
1056   - 必要に応じてscripts配列にGTMを追加して同意後に読み込み可能
1057
10585. 既存機能の維持:
1059   - YouTube動画読み込み
1060   - パス条件付きスクリプト読み込み
1061   - DOM操作
1062   - その他の既存機能はすべて維持
1063*/

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.