(() => { const OWNER = "nulldrm"; const REPO = "Null-DRM-Official"; const API = "/git-api/api/v1/repos/" + OWNER + "/" + REPO; const FORGEJO = "https://git.nulldrm.com/" + OWNER + "/" + REPO; const TG_URL = "https://t.me/+GZwQ7d_HD2w2ZTM8"; function setText(id, value) { const el = document.getElementById(id); if (el) el.textContent = value; } function shortDate(iso) { if (!iso) return "—"; const d = new Date(iso); if (Number.isNaN(d.getTime())) return "—"; return d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }); } function escapeHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } async function loadRepo() { if (!document.getElementById("git-stats")) return; try { const res = await fetch(API); if (!res.ok) throw new Error("HTTP " + res.status); const data = await res.json(); setText("stat-stars", String(data.stars_count ?? data.stars ?? 0)); setText("stat-forks", String(data.forks_count ?? data.forks ?? 0)); setText("stat-issues", String(data.open_issues_count ?? data.open_issues ?? 0)); setText("stat-updated", shortDate(data.updated_at || data.pushed_at)); } catch { ["stat-stars", "stat-forks", "stat-issues", "stat-updated"].forEach((id) => setText(id, "—")); } } async function loadReleases() { const status = document.getElementById("releases-status"); const list = document.getElementById("releases-list"); if (!status || !list) return; try { const res = await fetch(API + "/releases?limit=5"); if (!res.ok) throw new Error("HTTP " + res.status); const releases = await res.json(); if (!Array.isArray(releases) || releases.length === 0) { status.textContent = "No releases published yet — clone the repo or watch it on Forgejo."; status.hidden = false; list.hidden = true; return; } list.innerHTML = releases.map((r) => { const title = escapeHtml(r.name || r.tag_name || "Release"); const tag = escapeHtml(r.tag_name || ""); const when = shortDate(r.published_at || r.created_at); const url = escapeHtml(r.html_url || (FORGEJO + "/releases/tag/" + encodeURIComponent(r.tag_name || ""))); const body = (r.body || "").trim(); const preview = body ? '

' + escapeHtml(body.slice(0, 180)) + (body.length > 180 ? "…" : "") + "

" : ""; const assets = Array.isArray(r.assets) ? r.assets : []; const assetLinks = assets.slice(0, 4).map((a) => { const href = escapeHtml(a.browser_download_url || a.html_url || "#"); const name = escapeHtml(a.name || "asset"); return '' + name + ""; }).join(" · "); return ( "
  • " + '

    ' + title + "

    " + '

    ' + tag + (tag ? " · " : "") + when + (assetLinks ? " · " + assetLinks : "") + "

    " + preview + "
  • " ); }).join(""); status.hidden = true; list.hidden = false; } catch { status.innerHTML = 'Could not load releases. Open Forgejo releases directly.'; status.hidden = false; list.hidden = true; } } // Telegram invite: shown once per browser. function initTelegramInvite() { const KEY = "nulldrm_tg_seen"; const modal = document.getElementById("tg-modal"); if (!modal) return; let seen = false; try { seen = localStorage.getItem(KEY) === "1"; } catch { seen = true; // private mode: do not nag } const markSeen = () => { try { localStorage.setItem(KEY, "1"); } catch { /* private mode */ } }; // Belt and braces: the hidden attribute plus an inline display, so the // overlay cannot survive a stylesheet that sets display on .tg-modal. const close = () => { markSeen(); modal.hidden = true; modal.style.display = "none"; document.body.style.overflow = ""; }; const open = () => { modal.hidden = false; modal.style.display = "grid"; document.body.style.overflow = "hidden"; const join = document.getElementById("tg-join"); if (join) join.focus(); }; // Wire every dismiss control directly rather than relying on delegation. modal.querySelectorAll("[data-tg-dismiss]").forEach((el) => { el.addEventListener("click", (e) => { e.preventDefault(); close(); }); }); // Clicking anywhere outside the dialog also closes it. modal.addEventListener("click", (e) => { const t = e.target; if (t instanceof Element && !t.closest(".tg-dialog")) close(); }); const join = document.getElementById("tg-join"); if (join) join.addEventListener("click", close); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !modal.hidden) close(); }); if (seen) { modal.hidden = true; modal.style.display = "none"; } else { open(); } } // Highlight the section currently in view. function initNavHighlight() { const links = Array.from(document.querySelectorAll('.site-nav a[href^="#"]')); if (!links.length || !("IntersectionObserver" in window)) return; const byId = new Map(links.map((a) => [a.getAttribute("href").slice(1), a])); const sections = Array.from(byId.keys()) .map((id) => document.getElementById(id)) .filter(Boolean); if (!sections.length) return; const obs = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const link = byId.get(entry.target.id); if (link) link.classList.toggle("active", entry.isIntersecting); }); }, { rootMargin: "-45% 0px -50% 0px" } ); sections.forEach((s) => obs.observe(s)); } loadRepo(); loadReleases(); initTelegramInvite(); initNavHighlight(); // Any Telegram link left without an href falls back to the canonical invite. document.querySelectorAll('a[href^="https://t.me/"]').forEach((a) => { if (!a.getAttribute("href")) a.setAttribute("href", TG_URL); }); })();