PageSourceSearch

https://gs-hojo-web-fspoc.jp/assets/js/functions.js?260615

js gs-hojo-web-fspoc.jp collected 2026-09-29 16:42:04 UTC 26,843 bytes, 781 lines download raw bytes

1/* =============================
2   共通ユーティリティ
3============================= */
4// ハッシュから安全にID要素を取得(英数, _, -, : のみ許可)
5function getBySafeIdFromHash(hash) {
6  if (!hash || hash === '#' || hash.indexOf('#') !== 0) return null;
7  const id = decodeURIComponent(hash.slice(1));
8  if (!/^[A-Za-z0-9_\-:]+$/.test(id)) return null;
9  return document.getElementById(id);
10}
11// jQuery未定義対策(万一のガード)
12var $jq = (typeof window.jQuery === 'function') ? window.jQuery : null;
13
14/* =============================
15   スムーススクロール(セレクタ注入防止)
16============================= */
17$(function(){
18  $('a[href^="#"]').on('click', function(e){
19    e.preventDefault();
20    const targetEl = getBySafeIdFromHash($(this).attr('href'));
21    const adjust = (($('header').outerHeight() || 0) + 30);
22    const speed = 400;
23    const pos = targetEl ? $(targetEl).offset().top - adjust : 0;
24    $('html,body').animate({scrollTop: pos}, speed, 'swing');
25  });
26});
27
28/* =============================
29   アンカーリンクでヘッダー分ずらす(onload)
30============================= */
31$(window).on('load', function() {
32  const targetEl = getBySafeIdFromHash(location.hash);
33  if (!targetEl) return;
34  const headerHeight = (($('header').outerHeight() || 0) + 30);
35  const position = Math.floor($(targetEl).offset().top) - headerHeight;
36  $('html, body').animate({scrollTop: position}, 400);
37});
38
39/* =============================
40   タブ(#link指定時の動作)
41============================= */
42$(function(){
43  if (window.location.hash === '#link') {
44    $('#tab01').prop('checked', true);
45    const qa = document.getElementById('qa');
46    if (qa) {
47      $('html,body').animate({ scrollTop: $(qa).offset().top }, 'fast');
48    }
49  }
50});
51
52/* =============================
53   FAQ検索(動的検索 + 遷移両対応)統合版
54============================= */
55$(document).ready(function() {
56  /* -------- ユーティリティ -------- */
57  const INVALID_CHARS_RE = /[!"#$%&'()*+,\-./:;<=>?@[\\\]\^_`{|}~]/g;
58
59  function isOnFaqPage() {
60    // 例: /faq.html, /faq, /help/faq などを想定
61    const p = window.location.pathname.toLowerCase();
62    return p.includes('/faq') || /faq(?:\.html)?$/i.test(p);
63  }
64
65  function cleanKeyword(raw) {
66    return (raw || '').trim().replace(INVALID_CHARS_RE, '');
67  }
68
69  function showMessage(msg) {
70    $('#message').text(msg || '');
71  }
72
73  function makeQAListItem(question, answer) {
74    const li = document.createElement('li');
75    const dl = document.createElement('dl');
76    const dt = document.createElement('dt');
77    const dd = document.createElement('dd');
78    dt.textContent = question || '';
79    dd.textContent = answer || '';
80    dl.appendChild(dt); dl.appendChild(dd);
81    li.appendChild(dl);
82    return li;
83  }
84
85  function extractQA($el) {
86    // 1) dl構造を優先
87    const $dl = $el.is('dl') ? $el : $el.find('dl').first();
88    if ($dl.length) {
89      const q = ($dl.find('dt').first().text() || '').trim();
90      const a = ($dl.find('dd').first().text() || '').trim();
91      if (q || a) return { q, a };
92    }
93    // 2) よくあるクラス名
94    const qSel = ['.q', '.question', '.faq-question', '.title', '.faq-q', '.accordion-data_title'];
95    const aSel = ['.a', '.answer', '.faq-answer', '.content', '.desc', '.text', '.js-accordion_inner', '.accordion-data_content'];
96    let q = '', a = '';
97    for (const s of qSel) { const t = $el.find(s).first().text().trim(); if (t) { q = t; break; } }
98    for (const s of aSel) { const t = $el.find(s).first().text().trim(); if (t) { a = t; break; } }
99    // 3) フォールバック
100    if (!q && !a) {
101      const all = ($el.text() || '').trim();
102      if (all) {
103        const mid = Math.min(60, Math.floor(all.length / 2));
104        q = all.slice(0, mid).trim();
105        a = all.slice(mid).trim();
106      }
107    }
108    return { q, a };
109  }
110
111  /* -------- 入力値の制限 -------- */
112  $('#search-text').on('input', function () {
113    const before = $(this).val() || '';
114    const invalidChar = before.split('').find(c => INVALID_CHARS_RE.test(c));
115    if (invalidChar) {
116      showMessage(`"${invalidChar}" の文字は使えません、入力が制限されています。`);
117      const cleaned = before.replace(INVALID_CHARS_RE, '');
118      $(this).val(cleaned);
119    } else {
120      showMessage('');
121    }
122  });
123
124  /* -------- よくあるキーワードボタン -------- */
125  $('#keyword button').on('click', function() {
126    $('#search-text').val($(this).val()).trigger('input');
127  });
128
129  /* -------- 検索実行(FAQページ内での描画) -------- */
130  function performSearch() {
131    $('#output').text('検索結果');
132    const $list = $('#search-result__list ul');
133    const $scroll = $('#result-scroll');
134    const keyword = cleanKeyword($('#search-text').val());
135    $list.empty();
136    $('.no-result').remove();
137
138    let hit = 0;
139    if (keyword !== '') {
140      $('.search-target').each(function() {
141        const $t = $(this);
142        const haystack = ($t.text() || '');
143        if (haystack.indexOf(keyword) !== -1) {
144          const { q, a } = extractQA($t);
145          const li = makeQAListItem(q || haystack.slice(0, 60), a || '');
146          $list[0].appendChild(li);
147          hit++;
148        }
149      });
150      $('.faq #target-area').hide();
151    }
152
153    if (hit === 0) {
154      const li = document.createElement('li');
155      li.className = 'no-result';
156      li.textContent = '該当のキーワードを含んだFAQはありません。';
157      $list[0].appendChild(li);
158    }
159
160    if ($scroll.length) $scroll.scrollTop(0);
161
162    if (typeof window.gtag === 'function') {
163      window.gtag('event', 'search', { 'search_term': keyword });
164    }
165  }
166
167  /* -------- クリック&Enterの挙動(ページ判定付き) -------- */
168  // クリック: FAQならその場で検索、その他はfaq.htmlに遷移
169  $(document).on('click', '#searchBtn', function(e){
170    e.preventDefault();
171    const keyword = cleanKeyword($('#search-text').val());
172    if (!keyword) {
173      showMessage('検索キーワードを入力してください。');
174      return;
175    }
176
177    if (isOnFaqPage()) {
178      performSearch();
179    } else {
180      // faq.html への相対/絶対パスは環境に合わせて調整
181      const faqUrl = new URL('faq.html', window.location.href);
182      faqUrl.searchParams.set('search', keyword);
183      window.location.href = faqUrl.toString();
184    }
185  });
186
187  // Enter: FAQならその場で検索、その他は遷移
188  $('#search-text').on('keypress', function(e){
189    if (e.which === 13) {
190      e.preventDefault();
191      const keyword = cleanKeyword($('#search-text').val());
192      if (!keyword) {
193        showMessage('検索キーワードを入力してください。');
194        return;
195      }
196      if (isOnFaqPage()) {
197        performSearch();
198      } else {
199        const faqUrl = new URL('faq.html', window.location.href);
200        faqUrl.searchParams.set('search', keyword);
201        window.location.href = faqUrl.toString();
202      }
203    }
204  });
205
206  /* -------- ?search= の初期実行(FAQページのみ) -------- */
207  (function bootFromQuery(){
208    const params = new URLSearchParams(window.location.search);
209    const q = params.get('search') || '';
210    if (!q) return;
211    const cleaned = cleanKeyword(q);
212    if (cleaned !== q) {
213      showMessage('一部の記号は入力できないため、検索語から除外しました。');
214    }
215    $('#search-text').val(cleaned);
216    if (isOnFaqPage()) {
217      performSearch();
218    }
219  })();
220});
221
222
223
224/* =============================
225   メール(GA4イベント)
226============================= */
227$(document).ready(function() {
228  $('#link-mail').on('click', function() {
229    if (typeof window.gtag === 'function') {
230      window.gtag('event', 'email_click', { 'mail_type': 'データ送受信' });
231    }
232  });
233});
234
235/* =============================
236   ページ先頭ボタン
237============================= */
238$(function(){
239  const $btn = $('.pagetop');
240  $btn.hide();
241
242  $('.pagetop').on('click', function (e) {
243    e.preventDefault();
244    $('html, body').animate({ scrollTop: 0 }, 500);
245  });
246
247  let ticking = false;
248  $(window).on('scroll resize', function () {
249    if (ticking) return;
250    ticking = true;
251    requestAnimationFrame(function(){
252      if ($(window).scrollTop() > $('header').innerHeight()) {
253        $btn.fadeIn(500);
254      } else {
255        $btn.fadeOut(250);
256      }
257      ticking = false;
258    });
259  });
260});
261
262/* =============================
263   スマホメニュー開閉(toggle + body固定)
264============================= */
265$(function () {
266  var state = false;
267  var pos = 0;
268
269  $('.sp_nav').on('click', function () {
270    $('nav').toggleClass('open');
271
272    if (state === false) {
273      pos = $(window).scrollTop();
274      $('body').addClass('fixed').css({ top: -pos });
275      state = true;
276    } else {
277      $('body').removeClass('fixed').css({ top: 0 });
278      window.scrollTo(0, pos);
279      state = false;
280    }
281  });
282});
283
284/* =============================
285   レポート一覧 プルダウンフィルター
286============================= */
287$(document).ready(function () {
288  function updateFilteredList() {
289    var selectedYear = $('#yearFilter').val();
290    var selectedGenre = $('#genreFilter').val();
291
292    $('.filtered-item').hide().fadeIn();
293
294    if (selectedYear !== 'all') {
295      $('.filtered-item')
296        .filter(function () { return $(this).find('.year').text() !== selectedYear; })
297        .hide();
298    }
299
300    if (selectedGenre !== 'all') {
301      $('.filtered-item')
302        .filter(function () { return $(this).find('.genre').text() !== selectedGenre; })
303        .hide();
304    }
305
306    if (selectedGenre === 'all' && selectedYear === 'all') {
307      $('.order').fadeIn();
308    } else {
309      $('.order').hide();
310    }
311
312    $('.filtered-item:visible').fadeIn();
313  }
314
315  updateFilteredList();
316  $('#yearFilter, #genreFilter').on('change', updateFilteredList);
317});
318
319/* =============================
320   他言語切り替えボタン(スクロールで表示)
321============================= */
322$(function () {
323  var $btn = $('#scroll-lang');
324  $btn.hide();
325  $(window).on('scroll', function () {
326    if ($(this).scrollTop() > 150) { $btn.fadeIn(); } else { $btn.fadeOut(); }
327  });
328});
329
330/* =============================
331   アコーディオン
332============================= */
333$(function(){
334  // 動的生成にも対応
335  $(document).on('click', '.js-accordion_title', function() {
336    $(this).next('.js-accordion_inner').slideToggle();
337    $(this).toggleClass('open');
338  });
339});
340
341// ハッシュで対象アコーディオンを初期展開
342$(function () {
343  const urlHash = location.hash;
344  if (!urlHash) return;
345  const $target = $(urlHash);
346  if (!$target.length) return;
347
348  $target.find('.accordion-data_title').addClass('open')
349         .next('.js-accordion_inner').slideToggle();
350});
351
352/* =============================
353   SPメニュー(MicroModal利用部分の安全化)
354============================= */
355window.addEventListener('DOMContentLoaded', () => {
356  const anchorLinks = document.querySelectorAll('a[href^="#"]');
357  const anchorLinksArr = Array.from(anchorLinks);
358  const btn = document.querySelector('#js-buttonHamburger');
359  const overlay = document.querySelector('#js-overlay');
360
361  // クリック時にモーダルを閉じつつスクロール
362  anchorLinksArr.forEach((link) => {
363    link.addEventListener('click', (e) => {
364      e.preventDefault();
365      const targetEl = getBySafeIdFromHash(link.hash);
366      if (!targetEl) return;
367
368      const targetoffsetTop = window.pageYOffset + targetEl.getBoundingClientRect().top;
369
370      if (btn && overlay && btn.ariaExpanded === 'true') {
371        btn.ariaExpanded = 'false';
372        overlay.classList.remove('active');
373      }
374      anchorLinksArr.forEach((l) => { l.ariaExpanded = 'false'; });
375
376      if (window.MicroModal) {
377        window.MicroModal.close('modal-1');
378      }
379
380      window.scrollTo({ top: targetoffsetTop, behavior: 'smooth' });
381    });
382  });
383
384  if (btn && overlay) {
385    btn.addEventListener('click', function () {
386      if (btn.ariaExpanded === 'false') {
387        btn.ariaExpanded = 'true';
388        if (window.MicroModal) {
389          window.MicroModal.show('modal-1', { disableScroll: true, awaitOpenAnimation: true });
390        }
391        overlay.classList.add('active');
392      } else {
393        btn.ariaExpanded = 'false';
394        if (window.MicroModal) {
395          window.MicroModal.close('modal-1', { awaitCloseAnimation: true });
396        }
397        overlay.classList.remove('active');
398      }
399    });
400
401    overlay.addEventListener('click', function () {
402      btn.ariaExpanded = 'false';
403      if (window.MicroModal) {
404        window.MicroModal.close('modal-1', { awaitCloseAnimation: true });
405      }
406      overlay.classList.remove('active');
407    });
408
409    document.addEventListener('keydown', function (e) {
410      if (e.key === 'Escape') {
411        btn.ariaExpanded = 'false';
412        if (window.MicroModal) {
413          window.MicroModal.close('modal-1', { awaitCloseAnimation: true });
414        }
415        overlay.classList.remove('active');
416      }
417    });
418  }
419});
420
421/* =============================
422   採択情報一覧 プルダウンフィルター(Promise対応 + ガード)
423============================= */
424$(document).ready(function () {
425  // 初期化時に実行
426  saitakuFilteredList().then(checkHiddenItems);
427
428  // フィルター変更(ページにより存在しない select は jQuery が自動スキップ)
429  $('#countryFilter, #jigyouFilter, #taisyoFilter, #fieldFilter, #kouboFilter')
430    .on('change', function () {
431      saitakuFilteredList().then(checkHiddenItems);
432    });
433
434  // Promise対応のフィルタ関数
435  function saitakuFilteredList() {
436    $('#noitem').hide();
437    return new Promise((resolve) => {
438      // 要素が存在しない場合 .val() は null を返すため 'all' にフォールバック
439      var selectedKoubo   = $('#kouboFilter').val()   || 'all';
440      var selectedCountry = $('#countryFilter').val() || 'all';
441      var selectedJigyou  = $('#jigyouFilter').val()  || 'all';
442      var selectedTaisyo  = $('#taisyoFilter').val()  || 'all';
443      var selectedField   = $('#fieldFilter').val()   || 'all';
444
445      $('.filtered-item').hide().fadeIn();
446
447      if (selectedKoubo !== 'all') {
448      $('.filtered-item').filter(function () {
449        return $(this).find('.koubo').text().trim() !== selectedKoubo;
450      }).hide();
451      }
452
453      if (selectedCountry !== 'all') {
454        // <option> から国名リストを取得
455        const countryList = $('#countryFilter option').map(function () {
456          return $(this).val().trim();
457        }).get();
458
459        $('.filtered-item').filter(function () {
460          const countryText = $(this).find('.country').text().trim();
461          const matchedCountries = countryList.filter(country => {
462            const regex = new RegExp(`(^|・|、|(|))${country}($|・|、|(|))`);
463            return regex.test(countryText);
464          });
465          return !matchedCountries.includes(selectedCountry);
466        }).hide();
467      }
468
469      if (selectedJigyou !== 'all') {
470        $('.filtered-item').filter(function () {
471          return $(this).find('.jigyou').text() !== selectedJigyou;
472        }).hide();
473      }
474
475      if (selectedTaisyo !== 'all') {
476        $('.filtered-item').filter(function () {
477          return $(this).find('.taisyo').text() !== selectedTaisyo;
478        }).hide();
479      }
480
481      if (selectedField !== 'all') {
482        $('.filtered-item').filter(function () {
483          return !$(this).find('.field').text().includes(selectedField);
484        }).hide();
485      }
486
487      const $visible = $('.filtered-item:visible');
488      if ($visible.length === 0) {
489        resolve();
490      } else {
491        $visible.fadeIn(() => resolve());
492      }
493    });
494  }
495
496	function checkHiddenItems() {
497
498	  const selectedKoubo   = $('#kouboFilter').val()   || 'all';
499	  const selectedCountry = $('#countryFilter').val() || 'all';
500	  const selectedJigyou  = $('#jigyouFilter').val()  || 'all';
501	  const selectedTaisyo  = $('#taisyoFilter').val()  || 'all';
502	  const selectedField   = $('#fieldFilter').val()   || 'all';
503
504	  const allDefault =
505		selectedKoubo === 'all' &&
506		selectedCountry === 'all' &&
507		selectedJigyou === 'all' &&
508		selectedTaisyo === 'all' &&
509		selectedField === 'all';
510
511	  const visibleCount = $('.filtered-item:visible').length;
512
513	  if (!allDefault && visibleCount === 0) {
514		$('#noitem').text('該当の採択情報はありません。').fadeIn();
515	  } else {
516		$('#noitem').hide();
517	  }
518	}
519});
520
521/* =============================
522   タブ切り替え(URLパラメータをホワイトリスト化)
523============================= */
524$(document).ready(function () {
525  const validTabIds = $('.tab-content').map(function(){ return this.id; }).get();
526
527  function safeTabId(raw, fallback) {
528    const id = (raw || '').trim();
529    return validTabIds.includes(id) ? id : fallback;
530  }
531
532  function activateTab(tabId) {
533    $('.tab').removeClass('active');
534    $(`.tab[data-tab="${tabId}"]`).addClass('active');
535    $('.tab-content').removeClass('active');
536    document.getElementById(tabId)?.classList.add('active');
537  }
538
539  const params = new URLSearchParams(window.location.search);
540  const defaultTab = validTabIds[0] || 'tab-fs';
541  const activeTab = safeTabId(params.get('tab'), defaultTab);
542  activateTab(activeTab);
543
544  $('[data-tab]').on('click', function () {
545    const tabId = safeTabId($(this).data('tab'), activeTab);
546    activateTab(tabId);
547    const newUrl = new URL(window.location.href);
548    newUrl.searchParams.set('tab', tabId);
549    history.replaceState(null, '', newUrl);
550  });
551});
552
553/* =============================
554   FAQタブ切り替え(URLパラメータをホワイトリスト化)
555============================= */
556$(document).ready(function () {
557  const validFaqIds = $('.faq-tab-content').map(function(){ return this.id; }).get();
558
559  function safeFaqId(raw, fallback) {
560    const id = (raw || '').trim();
561    return validFaqIds.includes(id) ? id : fallback;
562  }
563
564  function activateFaqTab(tabId) {
565    $('.faq-tab').removeClass('active');
566    $(`.faq-tab[data-tab-faq="${tabId}"]`).addClass('active');
567    $('.faq-tab-content').removeClass('active');
568    document.getElementById(tabId)?.classList.add('active');
569  }
570
571  const faqParams = new URLSearchParams(window.location.search);
572  const defaultFaq = validFaqIds[0] || 'faq-tab1';
573  const activeFaqTab = safeFaqId(faqParams.get('faq-tab'), defaultFaq);
574  activateFaqTab(activeFaqTab);
575
576  $('[data-tab-faq]').on('click', function () {
577    const tabId = safeFaqId($(this).data('tab-faq'), activeFaqTab);
578    activateFaqTab(tabId);
579
580    const newUrl = new URL(window.location.href);
581    newUrl.searchParams.set('faq-tab', tabId);
582    history.replaceState(null, '', newUrl);
583  });
584});
585
586/* =============================
587   事業全体の流れのフェードイン(外部SVGは同一オリジンのみ)
588============================= */
589document.addEventListener('DOMContentLoaded', function () {
590  const flowElement = document.getElementById('flow');
591  const objectElement = document.getElementById('flow-image');
592  if (!flowElement || !objectElement) return;
593
594  function applyFadeInClassSafe(objectEl) {
595    try {
596      const svgDoc = objectEl.contentDocument;
597      if (!svgDoc) return; // 未ロード
598      const target = svgDoc.getElementById('etc-group');
599      if (target) target.classList.add('fadeIn');
600    } catch (e) {
601      // 別オリジン等でアクセス不可。黙って無視
602    }
603  }
604
605  const observer = new IntersectionObserver((entries, obs) => {
606    entries.forEach(entry => {
607      if (entry.isIntersecting) {
608        if (objectElement.contentDocument) {
609          applyFadeInClassSafe(objectElement);
610        } else {
611          objectElement.addEventListener('load', () => applyFadeInClassSafe(objectElement), { once: true });
612        }
613        obs.unobserve(entry.target);
614      }
615    });
616  }, { root: null, rootMargin: '0% 0%', threshold: 0.5 });
617
618  observer.observe(flowElement);
619});
620
621
622/* =============================
623   ヒーローセクションバナー
624============================= */
625document.addEventListener('DOMContentLoaded', function() {
626    const tabButtons = document.querySelectorAll('.tab-button');
627
628    tabButtons.forEach(button => {
629        button.addEventListener('click', function() {
630            const targetTab = this.getAttribute('data-tab');
631
632            // すべてのタブボタンから active クラスを削除
633            tabButtons.forEach(btn => btn.classList.remove('active'));
634
635            // クリックされたタブボタンに active クラスを追加
636            this.classList.add('active');
637
638            // すべてのタブコンテンツから active クラスを削除
639            document.querySelectorAll('.banner-tab-content').forEach(content => {
640                content.classList.remove('active');
641            });
642
643            // 対応するタブコンテンツに active クラスを追加
644            document.getElementById(targetTab).classList.add('active');
645        });
646    });
647});
648
649/* =============================
650   スクロールフェードイン
651============================= */
652document.addEventListener('DOMContentLoaded', function () {
653  var targets = document.querySelectorAll('.fade-in');
654  if (!targets.length) return;
655  var observer = new IntersectionObserver(function (entries) {
656    entries.forEach(function (entry) {
657      if (entry.isIntersecting) {
658        entry.target.classList.add('is-visible');
659        observer.unobserve(entry.target);
660      }
661    });
662  }, { threshold: 0.25 });
663  targets.forEach(function (el) { observer.observe(el); });
664});
665
666/* =============================
667   ページャー(ファイル名ベース動的生成)
668============================= */
669document.addEventListener('DOMContentLoaded', function () {
670  const pager = document.querySelector('ul.pager');
671  if (!pager) return;
672
673  const currentFile = location.pathname.split('/').pop() || '01.html';
674  const match = currentFile.match(/(\d+)\.html/);
675  if (!match) return;
676
677  const current = parseInt(match[1], 10);
678  const totalPages = 5;
679  let html = '';
680
681  // prev
682  if (current > 1) {
683    const prev = String(current - 1).padStart(2, '0');
684    html += `<li><a href="${prev}.html" class="prev"><i class="fa-sharp fa-solid fa-angle-left"></i></a></li>`;
685  } else {
686    html += `<li><span class="prev disabled"><i class="fa-sharp fa-solid fa-angle-left"></i></span></li>`;
687  }
688
689  // 各ページ番号
690  for (let i = 1; i <= totalPages; i++) {
691    const file = String(i).padStart(2, '0') + '.html';
692    if (i === current) {
693      html += `<li><span class="current">${i}</span></li>`;
694    } else {
695      html += `<li><a href="${file}">${i}</a></li>`;
696    }
697  }
698
699  // next
700  if (current < totalPages) {
701    const next = String(current + 1).padStart(2, '0');
702    html += `<li><a href="${next}.html" class="next"><i class="fa-sharp fa-solid fa-angle-right"></i></a></li>`;
703  } else {
704    html += `<li><span class="next disabled"><i class="fa-sharp fa-solid fa-angle-right"></i></span></li>`;
705  }
706
707  pager.innerHTML = html;
708});
709
710/* =============================
711   過年度レポート一覧 プルダウンフィルター(.report-card 対象)
712   index.html 専用:.report-grid + #jigyouFilter + #countryFilter
713   ※ r5h/r6h-saitaku の .filtered-item フィルターとは完全分離
714============================= */
715document.addEventListener("DOMContentLoaded", function () {
716  var reportGrid    = document.querySelector(".report-grid");
717  var jigyouFilter  = document.getElementById("jigyouFilter");
718  var countryFilter = document.getElementById("countryFilter"); // 存在しないページでは null
719
720  // .report-grid がなければレポートページではないので終了
721  if (!reportGrid || !jigyouFilter) return;
722
723  // 「該当なし」メッセージ要素を生成して追加
724  var noResultsMsg = document.createElement("div");
725  noResultsMsg.id = "no-results-msg";
726  noResultsMsg.textContent = "該当のレポートはありません。  There are no corresponding reports.";
727  noResultsMsg.style.display = "none";
728  reportGrid.appendChild(noResultsMsg);
729
730  function filterReportCards() {
731    var selectedJigyou  = jigyouFilter.value;
732    var selectedCountry = countryFilter ? countryFilter.value : "all";
733    var cards = reportGrid.querySelectorAll(".report-card");
734    var visibleCount = 0;
735
736    cards.forEach(function (card) {
737      var jigyouEl  = card.querySelector(".jigyou");
738      var countryEl = card.querySelector(".country");
739      var jigyouText  = jigyouEl  ? jigyouEl.textContent.trim()  : "";
740      var countryText = countryEl ? countryEl.textContent.trim() : "";
741
742      var matchJigyou  = (selectedJigyou  === "all" || jigyouText === selectedJigyou);
743      var matchCountry = (selectedCountry === "all" || countryText === selectedCountry);
744
745      if (matchJigyou && matchCountry) {
746        visibleCount++;
747        card.style.display = "";
748      } else {
749        card.style.display = "none";
750      }
751    });
752
753    noResultsMsg.style.display = visibleCount === 0 ? "block" : "none";
754  }
755
756  jigyouFilter.addEventListener("change", filterReportCards);
757  if (countryFilter) countryFilter.addEventListener("change", filterReportCards);
758});
759
760/* =============================
761   レポートカード高さ統一
762============================= */
763function equalizeTitleHeight() {
764  const titles = document.querySelectorAll('.report-card__title');
765  if (!titles.length) return;
766
767  // 高さリセット
768  titles.forEach(el => { el.style.height = 'auto'; });
769
770  // 最大高さ取得
771  let maxHeight = 0;
772  titles.forEach(el => {
773    if (el.offsetHeight > maxHeight) maxHeight = el.offsetHeight;
774  });
775
776  // 全てに適用
777  titles.forEach(el => { el.style.height = maxHeight + 'px'; });
778}
779
780window.addEventListener('load', equalizeTitleHeight);
781window.addEventListener('resize', equalizeTitleHeight);

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.