PageSourceSearch

https://nagareyamaaries.com/yomosora/assets/site-084a928935.js

js nagareyamaaries.com collected 2026-10-08 16:56:30 UTC 8,404 bytes, 191 lines download raw bytes

1(() => {
2  'use strict';
3  document.documentElement.classList.add('js-enabled');
4
5  const header = document.querySelector('.site-header');
6  const menu = document.querySelector('details.mobile-menu');
7  const menuSummary = menu?.querySelector('summary');
8  const sticky = document.querySelector('.mobile-cta');
9  const heroCta = document.getElementById('hero-primary-cta');
10  const inlineCtas = [...document.querySelectorAll('main .button[data-cv]')];
11  let layoutQueued = false;
12
13  function setSticky(visible) {
14    if (!sticky) return;
15    sticky.classList.toggle('is-visible', visible);
16    sticky.toggleAttribute('inert', !visible);
17    sticky.setAttribute('aria-hidden', String(!visible));
18  }
19
20  function updateLayout() {
21    layoutQueued = false;
22    const scrolled = window.scrollY > 28;
23    let showSticky = false;
24    if (sticky && heroCta && !menu?.open && getComputedStyle(sticky).display !== 'none') {
25      const viewportHeight = window.innerHeight;
26      const passedHero = heroCta.getBoundingClientRect().bottom <= 0;
27      const visibleInlineCta = inlineCtas.some(link => {
28        const box = link.getBoundingClientRect();
29        return box.width > 0 && box.height > 0 && box.top < viewportHeight && box.bottom > 0;
30      });
31      showSticky = passedHero && !visibleInlineCta;
32    }
33    header?.classList.toggle('is-scrolled', scrolled);
34    setSticky(showSticky);
35  }
36
37  function queueLayout() {
38    if (layoutQueued) return;
39    layoutQueued = true;
40    window.requestAnimationFrame(updateLayout);
41  }
42
43  function syncMenu() {
44    if (!menu) return;
45    menuSummary?.setAttribute('aria-expanded', String(menu.open));
46    menuSummary?.setAttribute('aria-label', menu.open ? 'メニューを閉じる' : 'メニューを開く');
47    // 開いた瞬間から、メニューと追従ボタンを重ねません。
48    if (menu.open) setSticky(false);
49    queueLayout();
50  }
51
52  function closeMenu(returnFocus = false) {
53    if (!menu?.open) return;
54    menu.open = false;
55    if (returnFocus) menuSummary?.focus({ preventScroll: true });
56    syncMenu();
57  }
58
59  menu?.addEventListener('toggle', syncMenu);
60  menu?.querySelectorAll('a').forEach(link => link.addEventListener('click', () => closeMenu()));
61  document.addEventListener('keydown', event => {
62    if (event.key === 'Escape' && menu?.open) {
63      event.preventDefault();
64      closeMenu(true);
65    }
66  });
67  document.addEventListener('click', event => {
68    if (menu?.open && event.target instanceof Node && !menu.contains(event.target)) closeMenu();
69  });
70  syncMenu();
71
72  // 既存の計測イベントと data-cv を引き継ぎます。
73  document.addEventListener('click', event => {
74    const cta = event.target instanceof Element ? event.target.closest('[data-cv]') : null;
75    if (!cta) return;
76    window.dispatchEvent(new CustomEvent('yomosora:cta', { detail: { id: cta.dataset.cv } }));
77  });
78
79  const faqList = document.querySelector('.faq-list');
80  const faqMore = document.querySelector('.faq-more');
81  if (faqList && faqMore) {
82    const extraQuestions = [...faqList.querySelectorAll(':scope > details')].slice(6);
83    const label = faqMore.querySelector('span:first-child');
84    const icon = faqMore.querySelector('.faq-more-icon');
85    if (!faqList.id) faqList.id = 'faq-questions';
86    faqMore.setAttribute('aria-controls', faqList.id);
87    function renderFaq(expanded) {
88      faqList.classList.toggle('is-expanded', expanded);
89      faqMore.setAttribute('aria-expanded', String(expanded));
90      extraQuestions.forEach(item => {
91        item.hidden = !expanded;
92        if (!expanded) item.open = false;
93      });
94      if (label) label.textContent = expanded ? '質問を閉じる' : 'ほかの質問も見る';
95      if (icon) icon.textContent = expanded ? '−' : '+';
96      queueLayout();
97    }
98    faqMore.hidden = extraQuestions.length === 0;
99    renderFaq(false);
100    faqMore.addEventListener('click', () => renderFaq(!faqList.classList.contains('is-expanded')));
101  }
102
103  // 2026-09-24 専用ブログ(/yomosora/blog/)の最新3件。取得できた時だけ一覧を作り、
104  // 失敗・0件・不正なデータなら何も足さない(説明とボタンだけが残る)。
105  const blogInner = document.querySelector('#blog .blog-inner');
106  const BLOG_ROOT = 'https://nagareyamaaries.com/yomosora/blog/';
107  const BLOG_FEED = '/yomosora/blog/wp-json/wp/v2/posts?per_page=3&_fields=title,link,date';
108  function blogPostLink(value) {
109    // 記事リンクは https://nagareyamaaries.com/yomosora/blog/ 配下だけを採用します。
110    if (typeof value !== 'string') return null;
111    let url;
112    try { url = new URL(value); } catch (error) { return null; }
113    if (url.protocol !== 'https:' || url.hostname !== 'nagareyamaaries.com' || url.port || url.username || url.password) return null;
114    return url.href.startsWith(BLOG_ROOT) ? url.href : null;
115  }
116  function blogPostTitle(rendered) {
117    // WordPress の title.rendered は文字参照を含むため、実行・読み込みをしない文書で文字だけ取り出します。
118    if (typeof rendered !== 'string') return '';
119    const text = new DOMParser().parseFromString(rendered, 'text/html').body.textContent || '';
120    return text.replace(/\s+/g, ' ').trim().slice(0, 120);
121  }
122  function renderBlogPosts(posts) {
123    if (!blogInner || !Array.isArray(posts)) return;
124    const list = document.createElement('ol');
125    list.className = 'blog-latest';
126    list.setAttribute('aria-label', 'ヨモソラ ブログの最新記事');
127    for (const post of posts.slice(0, 3)) {
128      const href = blogPostLink(post?.link);
129      const title = blogPostTitle(post?.title?.rendered);
130      if (!href || !title) continue;
131      const item = document.createElement('li');
132      const link = document.createElement('a');
133      link.href = href;
134      const date = typeof post.date === 'string' ? post.date.match(/^(\d{4})-(\d{2})-(\d{2})/) : null;
135      if (date) {
136        const time = document.createElement('time');
137        time.dateTime = `${date[1]}-${date[2]}-${date[3]}`;
138        time.textContent = `${date[1]}.${date[2]}.${date[3]}`;
139        link.append(time);
140      }
141      const label = document.createElement('span');
142      label.textContent = title;
143      link.append(label);
144      item.append(link);
145      list.append(item);
146    }
147    if (!list.children.length) return;
148    blogInner.append(list);
149    blogInner.classList.add('has-posts');
150    queueLayout();
151  }
152  if (blogInner && 'fetch' in window && 'DOMParser' in window) {
153    const controller = 'AbortController' in window ? new AbortController() : null;
154    const timer = controller ? window.setTimeout(() => controller.abort(), 8000) : 0;
155    fetch(BLOG_FEED, { credentials: 'omit', headers: { Accept: 'application/json' }, signal: controller ? controller.signal : undefined })
156      .then(response => (response.ok ? response.json() : null))
157      .then(renderBlogPosts)
158      .catch(() => {})
159      .finally(() => window.clearTimeout(timer));
160  }
161
162  // 2026-09-24 表示速度対応: 最下部 .invitation の背景画像(約278KB)は、画面に近づいてから読み込みます。
163  // perf-20260924.css が .is-bg-ready の無い間だけ背景を外します(読み込み後の見た目は従来と同じ)。
164  const invitation = document.querySelector('.invitation');
165  if (invitation) {
166    const showInvitationBackground = () => invitation.classList.add('is-bg-ready');
167    if ('IntersectionObserver' in window) {
168      const invitationObserver = new IntersectionObserver(entries => {
169        if (!entries.some(entry => entry.isIntersecting)) return;
170        invitationObserver.disconnect();
171        showInvitationBackground();
172      }, { rootMargin: '1200px 0px' });
173      invitationObserver.observe(invitation);
174    } else {
175      showInvitationBackground();
176    }
177  }
178
179  window.addEventListener('scroll', queueLayout, { passive: true });
180  window.addEventListener('resize', queueLayout, { passive: true });
181  window.addEventListener('pageshow', queueLayout);
182  window.addEventListener('load', queueLayout, { once: true });
183  document.fonts?.ready.then(queueLayout);
184  if ('ResizeObserver' in window) {
185    const layoutObserver = new ResizeObserver(queueLayout);
186    const main = document.querySelector('main');
187    if (main) layoutObserver.observe(main);
188    if (heroCta) layoutObserver.observe(heroCta);
189  }
190  updateLayout();
191})();

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.