PageSourceSearch

https://hicharlieocean.com/js/main.js

js hicharlieocean.com collected 2026-09-29 07:25:53 UTC 4,444 bytes, 145 lines download raw bytes

1/* Universal site script: injects shared header/footer partials, then wires up
2   nav, FAQ accordion, scroll reveals, and the footer year — on every page. */
3   (function () {
4    "use strict";
5  
6    function injectPartials() {
7      var slots = Array.prototype.slice.call(document.querySelectorAll("[data-include]"));
8  
9      return Promise.all(
10        slots.map(function (el) {
11          return fetch(el.getAttribute("data-include"))
12            .then(function (r) { return r.text(); })
13            .then(function (html) {
14              el.innerHTML = html;
15            })
16            .catch(function (err) {
17              console.error("Partial failed to load:", err);
18            });
19        })
20      );
21    }
22  
23    function initNav() {
24      var header = document.querySelector(".site-header");
25  
26      // FIX: header may not exist (injected partial not ready yet or missing page)
27      if (header) {
28        var onScroll = function () {
29          header.classList.toggle("is-scrolled", window.scrollY > 24);
30        };
31  
32        window.addEventListener("scroll", onScroll, { passive: true });
33        onScroll();
34      }
35  
36      var toggle = document.querySelector(".nav__toggle");
37      var menu = document.getElementById("nav-menu");
38  
39      if (toggle && menu) {
40        var setMenu = function (open) {
41          toggle.setAttribute("aria-expanded", String(open));
42          toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
43          menu.classList.toggle("is-open", open);
44          document.body.classList.toggle("menu-open", open);
45        };
46  
47        toggle.addEventListener("click", function () {
48          setMenu(toggle.getAttribute("aria-expanded") !== "true");
49        });
50  
51        menu.addEventListener("click", function (e) {
52          if (e.target.closest("a")) setMenu(false);
53        });
54  
55        document.addEventListener("keydown", function (e) {
56          if (e.key === "Escape" && menu.classList.contains("is-open")) {
57            setMenu(false);
58            toggle.focus();
59          }
60        });
61      }
62  
63      var year = document.getElementById("year");
64      if (year) year.textContent = String(new Date().getFullYear());
65    }
66  
67    function initFAQ() {
68      var items = document.querySelectorAll(".faq__item");
69  
70      items.forEach(function (item) {
71        item.addEventListener("toggle", function () {
72          if (!item.open) return;
73  
74          items.forEach(function (other) {
75            if (other !== item) other.open = false;
76          });
77        });
78      });
79    }
80  
81    function initReveals() {
82      var reveals = document.querySelectorAll(".reveal");
83  
84      if (!("IntersectionObserver" in window)) {
85        reveals.forEach(function (el) {
86          el.classList.add("is-visible");
87        });
88        return;
89      }
90  
91      var io = new IntersectionObserver(function (entries) {
92        entries.forEach(function (entry) {
93          if (entry.isIntersecting) {
94            entry.target.classList.add("is-visible");
95            io.unobserve(entry.target);
96          }
97        });
98      }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
99  
100      reveals.forEach(function (el) {
101        io.observe(el);
102      });
103    }
104  
105    function initMarquee() {
106      var marquees = document.querySelectorAll(".marquee");
107      if (!marquees.length) return;
108
109      marquees.forEach(function (marquee) {
110        var tracks = marquee.querySelectorAll(".marquee__track");
111
112        tracks.forEach(function (track) {
113          var list = track.querySelector(".marquee__list");
114          if (!list) return;
115
116          // Match perceived speed across rows regardless of list length.
117          var pxPerSec = 36;
118          var duration = Math.max(30, list.scrollWidth / pxPerSec);
119          track.style.setProperty("--speed", duration.toFixed(1) + "s");
120
121          // Duplicate the list so the loop is seamless.
122          var clone = list.cloneNode(true);
123          clone.setAttribute("aria-hidden", "true");
124          track.appendChild(clone);
125        });
126
127        marquee.classList.add("is-ready");
128      });
129    }
130
131    // Run page features that exist immediately
132    initFAQ();
133    initReveals();
134    initMarquee();
135  
136    // Inject header/footer, then init nav AFTER DOM exists
137    injectPartials().then(function () {
138      requestAnimationFrame(function () {
139        setTimeout(function () {
140          initNav();
141        }, 0);
142      });
143    });
144  
145  })();

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.