document.addEventListener("DOMContentLoaded", loadGames); let elements = []; let gameElements = []; let topElements = []; let isDev = []; let pageData; let starredGames = []; let recentGames = []; let globalPlays = null; let selectedTags = new Set(); function getHiddenGames() { try { const parsed = JSON.parse(localStorage.getItem("selenite.hiddenGames") || "[]"); return parsed; } catch { return []; } } function getRecentlyPlayed() { try { const parsed = JSON.parse(localStorage.getItem("selenite.recentlyPlayed") || "[]"); return parsed; } catch { return []; } } async function loadGames() { if (type == "g") pageData = { path: "/resources/games.json", prefix: "semag", type: "g" }; else if (type == "a") pageData = { path: "/resources/apps.json", prefix: "sppa", type: "a" }; else { sAlert("this is not valid"); return; } let data = await (await fetch(pageData.path)).json(); const topGamesDisabled = localStorage.getItem("selenite.disableTopGames") == "true"; data.forEach((g, i) => { g._key = g.name.toUpperCase(); g._index = i; }); data.sort((a, b) => a._key < b._key ? -1 : a._key > b._key ? 1 : 0); const gamesElement = document.getElementById("games"); const topGElement = document.getElementById("topGames"); const gamesFragment = document.createDocumentFragment(); const topFragment = document.createDocumentFragment(); const hiddenGames = pageData.type === "g" ? getHiddenGames() : []; const hiddenSet = new Set(hiddenGames); starredGames = JSON.parse(localStorage.getItem("selenite.starred") || "[]"); if (hiddenGames.length > 0) { starredGames = starredGames.filter((game) => !hiddenSet.has(game)); localStorage.setItem("selenite.starred", JSON.stringify(starredGames)); } const starredSet = new Set(starredGames); data.forEach((element) => { if (hiddenSet.has(element.directory)) return; const isStarred = starredSet.has(element.directory); const newElement = document.createElement("a"); newElement.dataset.target = element.directory; newElement.dataset.image = element.image; newElement.dataset.name = element._key; newElement.dataset.top = element.tags && element.tags.includes("top") ? "1" : ""; newElement.dataset.index = element._index; newElement.dataset.tags = element.tags ? element.tags.join(",") : ""; newElement.className = "game"; newElement.href = `/loader.html?title=${encodeURIComponent(element.name)}&dir=${element.directory}&img=${element.image}&type=${pageData.type}`; const image = document.createElement("img"); image.src = `/resources/${pageData.prefix}/${element.directory}/${element.image}`; image.loading = "lazy"; image.onerror = function() { this.src = "/img/missing.svg"; }; const holder = document.createElement("div"); holder.id = "holder"; const title = document.createElement("h1"); title.innerText = element.name; holder.appendChild(title); const star = document.createElement("img"); star.id = "star"; star.className = "star"; star.src = isStarred ? "/img/star-fill.svg" : "/img/star.svg"; const warnings = document.createElement("warnings"); newElement.appendChild(image); newElement.appendChild(holder); newElement.appendChild(warnings); holder.appendChild(star); if (element.tags) { [["18+", "thirteen", "18+"], ["13+", "thirteen", "13+"], ["horror", "horror", "😱"], ["gore", "gore", "🩸"]].forEach(([tag, cls, text]) => { if (element.tags.includes(tag)) { const w = document.createElement("warning"); w.innerText = text; w.className = cls; warnings.appendChild(w); } }); if (element.tags.includes("wip")) { isDev.push(element.directory); const w = document.createElement("warning"); w.innerText = "WIP"; w.className = "thirteen"; warnings.appendChild(w); } if (element.tags.includes("top") && !topGamesDisabled) { topFragment.appendChild(newElement); topElements.push(newElement); } else { gamesFragment.appendChild(newElement); gameElements.push(newElement); } } else { gamesFragment.appendChild(newElement); gameElements.push(newElement); } elements.push(newElement); star.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(e); }); }); gamesElement.appendChild(gamesFragment); if (topGElement) topGElement.appendChild(topFragment); const visibleGameCount = elements.length; const hiddenCount = hiddenGames.length; const itemLabel = pageData.type === "a" ? "apps" : "games"; document.getElementById("gameCount").innerText = hiddenCount > 0 ? `${visibleGameCount} ${itemLabel} loaded (${hiddenCount} hidden)` : `${visibleGameCount} ${itemLabel} loaded!`; document.getElementById("loadingMsg").style.display = "none"; document.getElementById("allHeader").style.display = "block"; const topHeader = document.getElementById("topHeader"); if (topHeader && topElements.length > 0) topHeader.style.display = "block"; if (isDev.length > 0) console.log(isDev.join(",")); starredGames = JSON.parse(localStorage.getItem("selenite.starred") || "[]"); if (starredGames.length > 0) { document.getElementById("starredHeader").style.display = "block"; _rebuildStarred(); document.querySelectorAll("#starredgames #star").forEach((e) => { e.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(e); }); }); } const recentHeader = document.getElementById("recentHeader"); const recentGamesEl = document.getElementById("recentgames"); if (recentHeader && recentGamesEl && localStorage.getItem("selenite.disableRecentlyPlayed") != "true") { const pageDirs = new Set(elements.map((el) => el.dataset.target)); recentGames = getRecentlyPlayed().filter((dir) => !hiddenSet.has(dir) && pageDirs.has(dir)); if (recentGames.length > 0) { recentHeader.style.display = "block"; _rebuildRecent(); } } const tagSet = new Set(); data.forEach((element) => { if (hiddenSet.has(element.directory)) return; (element.tags || []).forEach((t) => { if (t !== "top") tagSet.add(t); }); }); renderTagFilters([...tagSet].sort()); } function renderTagFilters(tags) { const chipsContainer = document.getElementById("tagChips"); const chipsLabel = document.getElementById("tagChipsLabel"); if (!chipsContainer) return; if (tags.length === 0) { chipsContainer.style.display = "none"; if (chipsLabel) chipsLabel.style.display = "none"; updateFiltersToggleLabel(); return; } chipsContainer.innerHTML = ""; const fragment = document.createDocumentFragment(); tags.forEach((tag) => { const chip = document.createElement("button"); chip.type = "button"; chip.className = "tagChip"; chip.innerText = tag; chip.dataset.tag = tag; chip.addEventListener("click", () => { if (selectedTags.has(tag)) { selectedTags.delete(tag); chip.classList.remove("active"); } else { selectedTags.add(tag); chip.classList.add("active"); } updateFiltersToggleLabel(); applyFilters(); }); fragment.appendChild(chip); }); chipsContainer.appendChild(fragment); updateFiltersToggleLabel(); } function updateFiltersToggleLabel() { const toggleBtn = document.getElementById("filtersToggle"); if (!toggleBtn) return; toggleBtn.innerText = selectedTags.size > 0 ? `filters (${selectedTags.size})` : "filters"; toggleBtn.classList.toggle("hasActive", selectedTags.size > 0); } document.addEventListener("DOMContentLoaded", () => { const toggleBtn = document.getElementById("filtersToggle"); const panel = document.getElementById("tagFilters"); if (!toggleBtn || !panel) return; toggleBtn.addEventListener("click", () => { const isOpen = panel.classList.toggle("open"); toggleBtn.classList.toggle("open", isOpen); }); }); function applyFilters() { const searchInput = document.getElementById("gamesearch"); const input = (searchInput ? searchInput.value : "").toUpperCase(); let hidden = 0; elements.forEach((el) => { const matchesSearch = el.dataset.name.includes(input); const elTags = el.dataset.tags ? el.dataset.tags.split(",") : []; const matchesTags = selectedTags.size === 0 || elTags.some((t) => selectedTags.has(t)); if (matchesSearch && matchesTags) { el.style.display = "flex"; } else { el.style.display = "none"; hidden++; } }); document.getElementById("noResults").style.display = hidden >= elements.length ? "flex" : "none"; } let searchRaf = null; document.addEventListener("DOMContentLoaded", () => { document.getElementById("gamesearch").addEventListener("input", () => { if (searchRaf) cancelAnimationFrame(searchRaf); searchRaf = requestAnimationFrame(() => { applyFilters(); searchRaf = null; }); }); }); function _rebuildStarred() { const container = document.getElementById("starredgames"); const gamesEl = document.getElementById("games"); const topGEl = document.getElementById("topGames"); container.innerHTML = ""; const fragment = document.createDocumentFragment(); starredGames.forEach((dir) => { const el = gamesEl.querySelector(`a.game[data-target='${dir}']`) || (topGEl && topGEl.querySelector(`a.game[data-target='${dir}']`)); if (el) fragment.appendChild(el.cloneNode(true)); }); container.appendChild(fragment); } function _rebuildRecent() { const container = document.getElementById("recentgames"); const gamesEl = document.getElementById("games"); const topGEl = document.getElementById("topGames"); container.innerHTML = ""; const fragment = document.createDocumentFragment(); recentGames.slice(0, 4).forEach((dir) => { const el = gamesEl.querySelector(`a.game[data-target='${dir}']`) || (topGEl && topGEl.querySelector(`a.game[data-target='${dir}']`)); if (el) fragment.appendChild(el.cloneNode(true)); }); container.appendChild(fragment); container.querySelectorAll("#star").forEach((e) => { e.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(e); }); }); } function getTotalPlayTime(directory) { const playTime = JSON.parse(localStorage.getItem("selenite.playTime") || "{}"); return Object.values(playTime).reduce((total, day) => total + (day[directory] || 0), 0); } function reSort(sortBy) { const gamesEl = document.getElementById("games"); const topGEl = document.getElementById("topGames"); const topHeader = document.getElementById("topHeader"); if (sortBy === "az") { if (topGEl) { const frag = document.createDocumentFragment(); topElements.forEach(el => frag.appendChild(el)); topGEl.appendChild(frag); if (topHeader && topElements.length > 0) topHeader.style.display = "block"; } const sorted = [...gameElements].sort((a, b) => a.dataset.name < b.dataset.name ? -1 : a.dataset.name > b.dataset.name ? 1 : 0); const frag = document.createDocumentFragment(); sorted.forEach(el => frag.appendChild(el)); gamesEl.appendChild(frag); return; } if (topHeader) topHeader.style.display = "none"; const all = [...gameElements, ...topElements]; if (sortBy === "za") { all.sort((a, b) => a.dataset.name > b.dataset.name ? -1 : a.dataset.name < b.dataset.name ? 1 : 0); } else if (sortBy === "played") { all.sort((a, b) => getTotalPlayTime(b.dataset.target) - getTotalPlayTime(a.dataset.target)); } else if (sortBy === "global") { if (!globalPlays) return; all.sort((a, b) => (globalPlays.get(b.dataset.target) || 0) - (globalPlays.get(a.dataset.target) || 0)); } else if (sortBy === "new") { all.sort((a, b) => a.dataset.index - b.dataset.index); } const fragment = document.createDocumentFragment(); all.forEach(el => fragment.appendChild(el)); gamesEl.appendChild(fragment); } document.addEventListener("DOMContentLoaded", () => { const sortSelect = document.getElementById("gamesort"); if (!sortSelect) return; sortSelect.addEventListener("change", async () => { if (sortSelect.value === "global" && !globalPlays) { sortSelect.disabled = true; try { const data = await (await fetch("/api/analytics/get")).json(); globalPlays = new Map(data.map(e => [e.name, e.plays])); } finally { sortSelect.disabled = false; } } reSort(sortSelect.value); }); }); document.addEventListener("DOMContentLoaded", () => { const randomBtn = document.getElementById("randomGame"); if (!randomBtn) return; randomBtn.addEventListener("click", () => { if (elements.length === 0) return; const visible = elements.filter((el) => el.style.display !== "none"); const pool = visible.length > 0 ? visible : elements; const random = pool[Math.floor(Math.random() * pool.length)]; location.href = random.href; }); }); function starEvent(e) { const game = e.target.parentNode.parentNode.getAttribute("data-target"); starredGames = JSON.parse(localStorage.getItem("selenite.starred") || "[]"); const header = document.getElementById("starredHeader"); if (starredGames.indexOf(game) == -1) { starredGames.push(game); starredGames.sort(); header.style.display = "block"; e.target.src = "/img/star-fill.svg"; } else { starredGames.splice(starredGames.indexOf(game), 1); document.querySelectorAll(`a.game[data-target='${game}'] #star`).forEach((s) => { s.src = "/img/star.svg"; }); header.style.display = starredGames.length > 0 ? "block" : "none"; } localStorage.setItem("selenite.starred", JSON.stringify(starredGames)); _rebuildStarred(); document.querySelectorAll("#starredgames #star").forEach((e) => { e.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(e); }); }); }