PageSourceSearch

https://www.jesusfreakhideout.com/nav2026.js?v=20260723n

js jesusfreakhideout.com collected 2026-10-02 02:27:58 UTC 13,547 bytes, 344 lines download raw bytes

1const mainToggle = document.querySelector("#drop");
2const menuLabel = document.querySelector('label[for="drop"]');
3const submenuInputs = [...document.querySelectorAll('.jfh-menu input[id^="drop-"]:not(#drop)')];
4const nav = document.querySelector(".jfh-menu");
5const menu = document.querySelector(".jfh-menu > .menu");
6const backdrop = document.querySelector(".menu-backdrop");
7const mobileQuery = window.matchMedia("(max-width: 760px)");
8const revealButton = document.querySelector(".menu-reveal");
9const desktopSearchToggle = document.querySelector(".desktop-search-toggle");
10const desktopSearchPanel = document.querySelector(".desktop-search-panel");
11const desktopSearchForm = document.querySelector(".desktop-search-panel");
12const mobileOpenContexts = new WeakMap();
13
14function syncState(input) {
15  const label = document.querySelector(`label[for="${input.id}"]`);
16  if (label) label.setAttribute("aria-expanded", String(input.checked));
17  if (input === mainToggle) {
18    nav.classList.toggle("menu-is-open", input.checked);
19    menuLabel.setAttribute("aria-label", input.checked ? "Close main menu" : "Open main menu");
20  }
21}
22
23menuLabel.setAttribute("role", "button");
24menuLabel.setAttribute("tabindex", "0");
25menuLabel.setAttribute("aria-expanded", "false");
26menuLabel.setAttribute("aria-controls", "jfh-menu-list");
27document.querySelector(".jfh-menu > .menu").id = "jfh-menu-list";
28
29function directChild(element, selector) {
30  return [...element.children].find((child) => child.matches(selector));
31}
32
33const mediumMoreSections = ["Ministry", "Podcast", "Videos", "Features", "Extras"];
34
35function cloneDesktopMenuList(sourceList) {
36  const clonedList = document.createElement("ul");
37  [...sourceList.children].forEach((sourceItem) => {
38    const sourceLink = directChild(sourceItem, "a");
39    if (!sourceLink) return;
40    const item = document.createElement("li");
41    item.appendChild(sourceLink.cloneNode(true));
42    const nestedList = directChild(sourceItem, "ul");
43    if (nestedList) item.appendChild(cloneDesktopMenuList(nestedList));
44    clonedList.appendChild(item);
45  });
46  return clonedList;
47}
48
49const mediumMoreItem = document.createElement("li");
50const mediumMoreLink = document.createElement("a");
51const mediumMoreMenu = document.createElement("ul");
52mediumMoreItem.className = "mid-more-item";
53mediumMoreLink.href = "#";
54mediumMoreLink.textContent = "More";
55mediumMoreLink.setAttribute("aria-expanded", "false");
56mediumMoreSections.forEach((label) => {
57  const sourceItem = [...menu.children].find((item) => {
58    const link = directChild(item, "a");
59    return link && link.textContent.trim() === label;
60  });
61  if (!sourceItem) return;
62  const item = document.createElement("li");
63  item.appendChild(directChild(sourceItem, "a").cloneNode(true));
64  const sourceList = directChild(sourceItem, "ul");
65  if (sourceList) item.appendChild(cloneDesktopMenuList(sourceList));
66  mediumMoreMenu.appendChild(item);
67});
68mediumMoreItem.append(mediumMoreLink, mediumMoreMenu);
69menu.insertBefore(mediumMoreItem, document.querySelector(".desktop-search-item"));
70mediumMoreLink.addEventListener("click", (event) => {
71  event.preventDefault();
72  const opening = !mediumMoreItem.classList.contains("is-hover-open");
73  mediumMoreItem.classList.toggle("is-hover-open", opening);
74  mediumMoreLink.setAttribute("aria-expanded", String(opening));
75});
76document.addEventListener("click", (event) => {
77  if (mediumMoreItem.contains(event.target)) return;
78  mediumMoreItem.classList.remove("is-hover-open");
79  mediumMoreLink.setAttribute("aria-expanded", "false");
80});
81
82function rememberMobileSectionPosition(input, label) {
83  if (!mobileQuery.matches || input.checked) return;
84  mobileOpenContexts.set(input, {
85    scrollTop: menu.scrollTop,
86    labelTop: label.getBoundingClientRect().top
87  });
88}
89
90function revealOpenedMobileSection(input) {
91  if (!mobileQuery.matches || !input.checked) return;
92  requestAnimationFrame(() => requestAnimationFrame(() => {
93    const item = input.parentElement;
94    const label = directChild(item, `label[for="${input.id}"]`);
95    const submenu = directChild(item, "ul");
96    if (!label || !submenu) return;
97
98    const context = mobileOpenContexts.get(input);
99    const maximumScroll = Math.max(0, menu.scrollHeight - menu.clientHeight);
100    if (context) {
101      const positionShift = label.getBoundingClientRect().top - context.labelTop;
102      menu.scrollTop = Math.min(maximumScroll, Math.max(0, context.scrollTop + positionShift));
103      mobileOpenContexts.delete(input);
104    }
105
106    requestAnimationFrame(() => {
107      const menuRect = menu.getBoundingClientRect();
108      const labelRect = label.getBoundingClientRect();
109      const submenuRect = submenu.getBoundingClientRect();
110      const viewportBottom = window.visualViewport ? window.visualViewport.height : window.innerHeight;
111      const visibleBottom = Math.min(menuRect.bottom, viewportBottom) - 24;
112      const desiredBottom = Math.min(submenuRect.bottom, labelRect.bottom + 132);
113      const neededNudge = Math.max(0, desiredBottom - visibleBottom);
114      const safeNudge = Math.min(neededNudge, Math.max(0, labelRect.top - menuRect.top - 6));
115      if (safeNudge < 2) return;
116      const currentMaximum = Math.max(0, menu.scrollHeight - menu.clientHeight);
117      menu.scrollTop = Math.min(currentMaximum, menu.scrollTop + safeNudge);
118    });
119  }));
120}
121
122submenuInputs.forEach((input) => {
123  const item = input.parentElement;
124  const label = directChild(item, `label[for="${input.id}"]`);
125  const link = directChild(item, "a");
126  const submenu = directChild(item, "ul");
127  if (!label || !link || !submenu) return;
128  const categoryName = link.textContent.trim();
129  const homeItem = document.createElement("li");
130  const homeLink = document.createElement("a");
131  homeItem.className = "mobile-category-home";
132  homeLink.href = link.getAttribute("href");
133  homeLink.textContent = `${categoryName} Home`;
134  homeItem.appendChild(homeLink);
135  submenu.insertBefore(homeItem, submenu.firstChild);
136  item.classList.add("has-mobile-submenu");
137  link.classList.add("mobile-category-link");
138  link.setAttribute("aria-label", `Visit ${categoryName}`);
139  label.textContent = categoryName;
140  label.setAttribute("role", "button");
141  label.setAttribute("tabindex", "0");
142  label.addEventListener("click", () => rememberMobileSectionPosition(input, label));
143  label.addEventListener("keydown", (event) => {
144    if (event.key === "Enter" || event.key === " ") {
145      event.preventDefault();
146      rememberMobileSectionPosition(input, label);
147      input.checked = !input.checked;
148      input.dispatchEvent(new Event("change"));
149    }
150  });
151});
152
153[mainToggle, ...submenuInputs].forEach((input) => {
154  syncState(input);
155  input.addEventListener("change", () => {
156    syncState(input);
157    if (input === mainToggle) {
158      backdrop.hidden = !input.checked;
159      backdrop.classList.toggle("is-open", input.checked);
160      document.body.style.overflow = input.checked && mobileQuery.matches ? "hidden" : "";
161    }
162    if (input !== mainToggle && input.checked) {
163      const siblings = [...input.closest("ul").querySelectorAll(":scope > li > input[id^='drop-']")];
164      siblings.forEach((sibling) => {
165        if (sibling !== input) {
166          sibling.checked = false;
167          syncState(sibling);
168        }
169      });
170      revealOpenedMobileSection(input);
171    }
172  });
173});
174
175menuLabel.addEventListener("click", () => {
176  requestAnimationFrame(() => {
177    syncState(mainToggle);
178    backdrop.hidden = !mainToggle.checked;
179    backdrop.classList.toggle("is-open", mainToggle.checked);
180    document.body.style.overflow = mainToggle.checked && mobileQuery.matches ? "hidden" : "";
181  });
182});
183
184function closeMobileMenu(returnFocus = false) {
185  mainToggle.checked = false;
186  syncState(mainToggle);
187  submenuInputs.forEach((input) => {
188    input.checked = false;
189    syncState(input);
190  });
191  backdrop.hidden = true;
192  backdrop.classList.remove("is-open");
193  document.body.style.overflow = "";
194  if (returnFocus) menuLabel.focus();
195}
196
197backdrop.addEventListener("click", () => closeMobileMenu(true));
198menu.addEventListener("click", (event) => {
199  if (mobileQuery.matches && event.target.closest("a")) closeMobileMenu(false);
200});
201
202const searchForm = document.querySelector(".mobile-menu-search");
203
204function openSearchResults(query) {
205  const resultsUrl = new URL(window.location.href);
206  resultsUrl.searchParams.set("q", query);
207  resultsUrl.hash = "";
208  window.location.href = resultsUrl.toString();
209}
210
211document.addEventListener("click", (event) => {
212  const resultLink = event.target.closest(".gsc-results a[target='_blank']");
213  if (resultLink) resultLink.removeAttribute("target");
214}, true);
215
216searchForm.addEventListener("submit", (event) => {
217  event.preventDefault();
218  const query = searchForm.querySelector("input").value.trim();
219  if (query) openSearchResults(query);
220});
221
222desktopSearchToggle.addEventListener("click", () => {
223  const opening = !desktopSearchPanel.classList.contains("is-open");
224  desktopSearchPanel.classList.toggle("is-open", opening);
225  desktopSearchToggle.setAttribute("aria-expanded", String(opening));
226  if (opening) desktopSearchPanel.querySelector("input").focus();
227});
228desktopSearchForm.addEventListener("submit", (event) => {
229  event.preventDefault();
230  const query = desktopSearchForm.querySelector("input").value.trim();
231  if (query) openSearchResults(query);
232});
233menuLabel.addEventListener("keydown", (event) => {
234  if (event.key === "Enter" || event.key === " ") {
235    event.preventDefault();
236    mainToggle.checked = !mainToggle.checked;
237    mainToggle.dispatchEvent(new Event("change"));
238  }
239});
240
241document.addEventListener("keydown", (event) => {
242  if (event.key !== "Escape") return;
243  mediumMoreItem.classList.remove("is-hover-open");
244  mediumMoreLink.setAttribute("aria-expanded", "false");
245  desktopSearchPanel.classList.remove("is-open");
246  desktopSearchToggle.setAttribute("aria-expanded", "false");
247  closeMobileMenu(true);
248});
249
250window.matchMedia("(min-width: 761px)").addEventListener("change", (event) => {
251  if (event.matches) {
252    mainToggle.checked = false;
253    syncState(mainToggle);
254    backdrop.hidden = true;
255    backdrop.classList.remove("is-open");
256    document.body.style.overflow = "";
257  }
258});
259
260const sectionRules = [
261  ["Reviews", /\/cdreviews\//],
262  ["Indies", /\/(indiemusic|indies)\//],
263  ["Interviews", /\/interviews\//],
264  ["Lyrics", /\/lyrics\//],
265  ["Ministry", /\/(ministry|devotionals|prayers)/],
266  ["Movies", /\/movies\//],
267  ["Podcast", /\/podcasts\//],
268  ["Videos", /\/videoclips\//],
269  ["Features", /\/features\//],
270  ["Extras", /\/(extras|newsletter|contests|downloads|about|contactus|donate)/],
271  ["Artists", /\/artists\//],
272  ["Music", /\/(music|releases|news|concerts|listeningparty|jfhblog)/]
273];
274const livePath = location.protocol === "file:" ? "/default.asp" : location.pathname.toLowerCase();
275let currentLabel = "Home";
276for (const [label, pattern] of sectionRules) {
277  if (pattern.test(livePath)) {
278    currentLabel = label;
279    break;
280  }
281}
282if (mediumMoreSections.includes(currentLabel)) {
283  mediumMoreItem.classList.add("is-current");
284}
285const topItems = [...document.querySelectorAll(".jfh-menu > .menu > li:not(.mobile-search-item)")];
286topItems.forEach((item) => {
287  const link = directChild(item, "a");
288  const label = directChild(item, "label");
289  const text = (link?.textContent || label?.textContent || "").trim();
290  if (text.startsWith(currentLabel)) {
291    item.classList.add("is-current");
292    if (link) link.setAttribute("aria-current", "page");
293  }
294});
295
296const hoverTimers = new WeakMap();
297document.querySelectorAll(".jfh-menu li").forEach((item) => {
298  if (!directChild(item, "ul")) return;
299  item.addEventListener("mouseenter", () => {
300    if (mobileQuery.matches) return;
301    [...item.parentElement.children].forEach((sibling) => {
302      if (sibling === item) return;
303      clearTimeout(hoverTimers.get(sibling));
304      sibling.classList.remove("is-hover-open");
305    });
306    clearTimeout(hoverTimers.get(item));
307    item.classList.add("is-hover-open");
308  });
309  item.addEventListener("mouseleave", () => {
310    if (mobileQuery.matches) return;
311    hoverTimers.set(item, setTimeout(() => item.classList.remove("is-hover-open"), 240));
312  });
313});
314
315document.querySelectorAll(".jfh-menu > .menu > li > ul").forEach((dropdown) => {
316  const hasSideFlyout = [...dropdown.children].some((item) => directChild(item, "ul"));
317  if (!hasSideFlyout) dropdown.classList.add("can-scroll");
318});
319
320let previousScroll = window.scrollY;
321let accumulatedDelta = 0;
322let revealGraceUntil = 0;
323revealButton.addEventListener("click", () => {
324  nav.classList.remove("is-hidden");
325  revealButton.classList.remove("is-visible");
326  revealGraceUntil = Date.now() + 1800;
327  menuLabel.focus();
328});
329window.addEventListener("scroll", () => {
330  const currentScroll = window.scrollY;
331  const delta = currentScroll - previousScroll;
332  accumulatedDelta = Math.sign(delta) === Math.sign(accumulatedDelta) ? accumulatedDelta + delta : delta;
333  const menuIsOpen = mainToggle.checked || desktopSearchPanel.classList.contains("is-open") || document.querySelector(".jfh-menu .is-hover-open");
334  if (!menuIsOpen && Date.now() > revealGraceUntil && currentScroll > 180 && accumulatedDelta > 24) {
335    nav.classList.add("is-hidden");
336    revealButton.classList.add("is-visible");
337    accumulatedDelta = 0;
338  } else if (accumulatedDelta < -14 || currentScroll < 80) {
339    nav.classList.remove("is-hidden");
340    revealButton.classList.remove("is-visible");
341    accumulatedDelta = 0;
342  }
343  previousScroll = currentScroll;
344}, { passive: true });

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.