perf fix + ad guard for the games grid, search section cleanup
- drop backdrop-filter from .game tiles (likely cause of a freeze-on-load bug on heavy uncached loads) and harden tile sizing so it can't balloon regardless; fix some long-dead CSS selectors along the way (mobile breakpoints were silently never applying) - strip AdSense in-feed ads if they try to inject into the games grid - top games / starred / recently played now only show a section if something inside it still matches the current search or tag filter, instead of leaving orphaned tiles with no header or stale clutter - remove the proxy nav card from home (dead now that it's disabled) - delete cookie.js and savedownload.js, confirmed fully unreferenced dead code superseded by the save/load flow already in settings.html
This commit is contained in:
108
js/loader.js
108
js/loader.js
@ -1,4 +1,33 @@
|
||||
document.addEventListener("DOMContentLoaded", loadGames);
|
||||
document.addEventListener("DOMContentLoaded", guardGameGridsFromAds);
|
||||
|
||||
// AdSense in-feed auto ads see the uniform grid of .game tiles as a "feed" and
|
||||
// inject an <ins> ad as a sibling tile, which breaks the flex layout. Strip any
|
||||
// ad node that lands directly inside a game grid so ads never fill into the list.
|
||||
function guardGameGridsFromAds() {
|
||||
const gridIds = ["games", "topGames", "starredgames", "recentgames"];
|
||||
const isAdNode = (node) => {
|
||||
if (!node || node.nodeType !== 1) return false;
|
||||
if (node.tagName === "INS") return true;
|
||||
if (node.classList && node.classList.contains("adsbygoogle")) return true;
|
||||
if (/^(aswift_|google_ads_|div-gpt-)/.test(node.id || "")) return true;
|
||||
if (typeof node.querySelector === "function" &&
|
||||
node.querySelector("ins.adsbygoogle, iframe[id^='google_ads_iframe'], iframe[id^='aswift_']")) return true;
|
||||
return false;
|
||||
};
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
for (const node of m.addedNodes) {
|
||||
if (isAdNode(node)) node.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
gridIds.forEach((id) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) observer.observe(el, { childList: true });
|
||||
});
|
||||
}
|
||||
|
||||
let elements = [];
|
||||
let gameElements = [];
|
||||
let topElements = [];
|
||||
@ -143,9 +172,6 @@ async function loadGames() {
|
||||
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");
|
||||
@ -219,15 +245,39 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function elementMatchesFilters(el, input, selectedTags) {
|
||||
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));
|
||||
return matchesSearch && matchesTags;
|
||||
}
|
||||
|
||||
// Filters a section built from cloned .game tiles (starred / recently played):
|
||||
// hides individual clones that don't match, and hides the whole section
|
||||
// (container + header) only if nothing inside it matches anymore.
|
||||
function filterClonedSection(containerId, headerId, input, selectedTags) {
|
||||
const container = document.getElementById(containerId);
|
||||
const header = document.getElementById(headerId);
|
||||
if (!container) return;
|
||||
let anyVisible = false;
|
||||
container.querySelectorAll(".game").forEach((el) => {
|
||||
if (elementMatchesFilters(el, input, selectedTags)) {
|
||||
el.style.display = "flex";
|
||||
anyVisible = true;
|
||||
} else {
|
||||
el.style.display = "none";
|
||||
}
|
||||
});
|
||||
container.style.display = anyVisible ? "" : "none";
|
||||
if (header) header.style.display = anyVisible ? "block" : "none";
|
||||
}
|
||||
|
||||
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) {
|
||||
if (elementMatchesFilters(el, input, selectedTags)) {
|
||||
el.style.display = "flex";
|
||||
} else {
|
||||
el.style.display = "none";
|
||||
@ -235,6 +285,17 @@ function applyFilters() {
|
||||
}
|
||||
});
|
||||
document.getElementById("noResults").style.display = hidden >= elements.length ? "flex" : "none";
|
||||
|
||||
// top games shares tiles with the main list (already filtered above via
|
||||
// `elements`) - its header just needs to track whether any survived.
|
||||
const topHeader = document.getElementById("topHeader");
|
||||
if (topHeader) {
|
||||
const anyTopVisible = topElements.some((el) => el.style.display !== "none");
|
||||
topHeader.style.display = anyTopVisible ? "block" : "none";
|
||||
}
|
||||
|
||||
filterClonedSection("starredgames", "starredHeader", input, selectedTags);
|
||||
filterClonedSection("recentgames", "recentHeader", input, selectedTags);
|
||||
}
|
||||
|
||||
let searchRaf = null;
|
||||
@ -248,26 +309,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function _rebuildStarred() {
|
||||
const container = document.getElementById("starredgames");
|
||||
// cloneNode(true) does not copy event listeners, so the cloned stars need their
|
||||
// click handler re-bound here rather than relying on each caller to do it.
|
||||
function _rebuildList(containerId, dirs) {
|
||||
const container = document.getElementById(containerId);
|
||||
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) => {
|
||||
dirs.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));
|
||||
});
|
||||
@ -277,6 +327,14 @@ function _rebuildRecent() {
|
||||
});
|
||||
}
|
||||
|
||||
function _rebuildStarred() {
|
||||
_rebuildList("starredgames", starredGames);
|
||||
}
|
||||
|
||||
function _rebuildRecent() {
|
||||
_rebuildList("recentgames", recentGames.slice(0, 4));
|
||||
}
|
||||
|
||||
function getTotalPlayTime(directory) {
|
||||
const playTime = JSON.parse(localStorage.getItem("selenite.playTime") || "{}");
|
||||
return Object.values(playTime).reduce((total, day) => total + (day[directory] || 0), 0);
|
||||
@ -365,8 +423,4 @@ function starEvent(e) {
|
||||
|
||||
localStorage.setItem("selenite.starred", JSON.stringify(starredGames));
|
||||
_rebuildStarred();
|
||||
|
||||
document.querySelectorAll("#starredgames #star").forEach((e) => {
|
||||
e.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(e); });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user