(() => {
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);
});
})();