PageSourceSearch

https://tritonalmusic.com/live/scripts/site.js

js tritonalmusic.com collected 2026-10-02 06:34:12 UTC 6,574 bytes, 247 lines download raw bytes

1const DEFAULT_SITE_DATA = {
2  releases: [
3    {
4      title: "Walk Away",
5      meta: "2026 release",
6      image: "assets/images/releases/walk-away.jpg",
7      alt: "Walk Away cover art",
8      links: {
9        Spotify: "https://open.spotify.com/album/7pfwOHCIY1nB8Oaa4AzyQB",
10        Beatport: "https://www.beatport.com/artist/tritonal/75268/releases"
11      }
12    },
13    {
14      title: "Watch Us Glow",
15      meta: "Tritonal feat. Angel Taylor",
16      image: "assets/images/releases/watch-us-glow.jpg",
17      alt: "Watch Us Glow cover art",
18      links: {
19        Spotify: "https://open.spotify.com/album/0sin3mAcwISmJjPNOpVQeF",
20        Beatport: "https://www.beatport.com/release/watch-us-glow/4907251"
21      }
22    },
23    {
24      title: "From The Inside",
25      meta: "Tritonal & Kuala",
26      links: {
27        spotify: "https://open.spotify.com/artist/521qvhdobR0GzhvU6TFw76",
28        beatport: "https://www.beatport.com/artist/tritonal/75268/releases"
29      }
30    },
31    {
32      title: "U & Me",
33      meta: "Artist album",
34      links: {
35        spotify: "https://open.spotify.com/artist/521qvhdobR0GzhvU6TFw76",
36        beatport: "https://www.beatport.com/artist/tritonal/75268/releases"
37      }
38    }
39  ],
40  videos: [
41    {
42      id: "5HM2WEgXNFk",
43      title: "Tritonal feat. Angel Taylor - Watch Us Glow"
44    },
45    {
46      id: "1Xx07D2Suuk",
47      title: "Tritonia 524"
48    },
49    {
50      id: "VhjYGtmhH90",
51      title: "Watch Us Glow - Hyperbits Remix"
52    },
53    {
54      id: "DtkW-o2En00",
55      title: "U Found Me - Official Music Video"
56    }
57  ],
58  instagramFallback: [
59    {
60      image: "assets/images/a7401425.jpg",
61      alt: "Tritonal silhouetted in red stage light",
62      url: "https://www.instagram.com/tritonal/"
63    },
64    {
65      image: "assets/images/a7401629.jpg",
66      alt: "Tritonal posing against an orange stage visual",
67      url: "https://www.instagram.com/tritonal/"
68    },
69    {
70      image: "assets/images/a7402072.jpg",
71      alt: "Tritonal in front of a triangular stage visual",
72      url: "https://www.instagram.com/tritonal/"
73    },
74    {
75      image: "assets/images/a7402238.jpg",
76      alt: "Tritonal silhouettes in front of a glowing triangle",
77      url: "https://www.instagram.com/tritonal/"
78    }
79  ]
80};
81
82const qs = (selector, scope = document) => scope.querySelector(selector);
83const qsa = (selector, scope = document) => [...scope.querySelectorAll(selector)];
84
85function escapeHtml(value = "") {
86  return value
87    .replaceAll("&", "&")
88    .replaceAll("<", "&lt;")
89    .replaceAll(">", "&gt;")
90    .replaceAll('"', "&quot;")
91    .replaceAll("'", "&#039;");
92}
93
94async function loadJson(path, fallback) {
95  try {
96    const response = await fetch(path, { cache: "no-store" });
97    if (!response.ok) {
98      throw new Error(`Unable to load ${path}`);
99    }
100    return await response.json();
101  } catch {
102    return fallback;
103  }
104}
105
106function initHeader() {
107  const header = qs("[data-header]");
108  const toggle = qs(".nav-toggle");
109  const navLinks = qsa(".primary-nav a");
110
111  const updateHeader = () => {
112    header?.classList.toggle("is-scrolled", window.scrollY > 18);
113  };
114
115  toggle?.addEventListener("click", () => {
116    const isOpen = document.body.classList.toggle("nav-open");
117    toggle.setAttribute("aria-expanded", String(isOpen));
118  });
119
120  navLinks.forEach((link) => {
121    link.addEventListener("click", () => {
122      document.body.classList.remove("nav-open");
123      toggle?.setAttribute("aria-expanded", "false");
124    });
125  });
126
127  updateHeader();
128  window.addEventListener("scroll", updateHeader, { passive: true });
129}
130
131function initActiveNavigation() {
132  const navLinks = qsa(".primary-nav a").filter((link) =>
133    link.getAttribute("href")?.startsWith("#")
134  );
135  const sections = navLinks
136    .map((link) => qs(link.getAttribute("href")))
137    .filter(Boolean);
138
139  if (!sections.length) {
140    return;
141  }
142
143  const observer = new IntersectionObserver(
144    (entries) => {
145      const visible = entries
146        .filter((entry) => entry.isIntersecting)
147        .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
148
149      if (!visible) {
150        return;
151      }
152
153      navLinks.forEach((link) => {
154        link.classList.toggle("is-active", link.getAttribute("href") === `#${visible.target.id}`);
155      });
156    },
157    {
158      rootMargin: "-35% 0px -55% 0px",
159      threshold: [0, 0.2, 0.6]
160    }
161  );
162
163  sections.forEach((section) => observer.observe(section));
164}
165
166function renderReleases(releases = []) {
167  const container = qs("[data-releases]");
168  if (!container) {
169    return;
170  }
171
172  container.innerHTML = releases
173    .map((release) => {
174      const links = Object.entries(release.links || {})
175        .map(([label, url]) => {
176          const slug = label.toLowerCase().includes("spotify") ? "spotify" : "beatport";
177          return `<a class="pill-link pill-link--${slug}" href="${escapeHtml(url)}" target="_blank" rel="noreferrer">${escapeHtml(label)}</a>`;
178        })
179        .join("");
180
181      return `
182        <article class="release-card">
183          <div>
184            <p class="release-card__meta">${escapeHtml(release.meta || "")}</p>
185            <h3>${escapeHtml(release.title || "")}</h3>
186          </div>
187          <div class="release-card__links">${links}</div>
188        </article>
189      `;
190    })
191    .join("");
192}
193
194function initContactForm() {
195  const form = qs("[data-contact-form]");
196  if (!form) {
197    return;
198  }
199
200  const message = qs("[data-form-message]", form);
201  const captcha = qs("[data-captcha-answer]", form);
202
203  captcha?.addEventListener("input", () => {
204    if (Number(captcha.value) === 12) {
205      captcha.removeAttribute("aria-invalid");
206      if (message) {
207        message.textContent = "";
208      }
209    }
210  });
211
212  form.addEventListener("submit", (event) => {
213    const honey = form.querySelector("[name='_honey']");
214
215    if (honey?.value) {
216      event.preventDefault();
217      return;
218    }
219
220    if (Number(captcha?.value) !== 12) {
221      event.preventDefault();
222      captcha?.setAttribute("aria-invalid", "true");
223      if (message) {
224        message.textContent = "Please answer the security check before sending.";
225      }
226      captcha?.focus();
227    }
228  });
229}
230
231async function initContent() {
232  const siteData = await loadJson("assets/data/site.json", DEFAULT_SITE_DATA);
233  renderReleases(siteData.releases || DEFAULT_SITE_DATA.releases);
234}
235
236function initFooterYear() {
237  const year = qs("[data-year]");
238  if (year) {
239    year.textContent = new Date().getFullYear();
240  }
241}
242
243initHeader();
244initActiveNavigation();
245initContent();
246initContactForm();
247initFooterYear();

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.