ui stuff, proxy?, and whatever else

This commit is contained in:
first@lynx.com
2026-08-17 14:23:34 -04:00
parent b1a1403c13
commit 9d640ff608
18 changed files with 1262 additions and 149 deletions

View File

@ -40,7 +40,13 @@
</head>
<body>
<div class="gamesToolbar">
<input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." />
<button id="filtersToggle" type="button">filters</button>
<button id="randomGame">random app</button>
</div>
<div id="tagFilters">
<p class="filterLabel">sorting</p>
<select id="gamesort">
<option value="az">a -> z</option>
<option value="za">z -> a</option>
@ -48,9 +54,14 @@
<option value="played">your top apps</option>
<option value="global">global top apps</option>
</select>
<p class="filterLabel" id="tagChipsLabel">game genres</p>
<div id="tagChips"></div>
</div>
<p id="gameCount">xx apps loaded..</p>
<p id="starredHeader">starred apps</p>
<div id="starredgames"></div>
<p id="recentHeader">recently played</p>
<div id="recentgames"></div>
<p id="allHeader" class="title">all apps</p>
<div id="games">
<p id="loadingMsg">apps loading..</p>

View File

@ -50,7 +50,7 @@
transform: scale(1.1);
filter: drop-shadow(0 0 5px var(--color-1))
}
#games, #starredgames, #topGames {
#games, #starredgames, #topGames, #recentgames {
width: 90vw;
display: flex;
flex-direction: row;
@ -64,16 +64,107 @@
font-size: 24px;
color: var(--color-1);
}
#starredHeader, #allHeader, #topHeader {
#starredHeader, #allHeader, #topHeader, #recentHeader {
font-size: 32px;
color: var(--color-1);
}
#noResults, #starredHeader, #allHeader, #topHeader {
#noResults, #starredHeader, #allHeader, #topHeader, #recentHeader {
display: none;
}
body {
justify-content: flex-start;
}
.gamesToolbar {
width: 90vw;
max-width: 700px;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 10px;
margin: 10px 0 4px;
}
.gamesToolbar .searchbar {
flex: 2 1 320px;
width: auto;
margin: 0;
padding: 16px 20px;
font-size: 18px;
}
.gamesToolbar #gamesort,
.gamesToolbar #randomGame,
.gamesToolbar #filtersToggle {
margin: 0;
flex: 0 0 auto;
}
.gamesToolbar #randomGame:hover,
.gamesToolbar #filtersToggle:hover {
filter: brightness(1.1);
box-shadow: none;
transform: none;
}
.gamesToolbar #filtersToggle.hasActive {
background-color: var(--color-1);
border-color: var(--color-1);
color: var(--bg-2);
font-weight: bold;
}
.gamesToolbar #filtersToggle.open {
box-shadow: inset 0 0 0 2px var(--text-color);
}
#tagFilters {
display: none;
flex-direction: column;
align-items: center;
gap: 6px;
width: 90vw;
max-width: 700px;
margin: 4px 0 10px;
}
#tagFilters.open {
display: flex;
}
#tagFilters #gamesort {
margin: 0;
}
.filterLabel {
font-size: 16px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-color);
margin: 0;
}
#tagChipsLabel {
margin-top: 8px;
}
#tagChips {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.tagChip {
border-style: solid;
border-width: 2px;
padding: 6px 14px;
border-radius: 999px;
background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%);
border-color: var(--color-3);
color: var(--text-color);
outline: none;
transition-duration: 0.25s;
font-size: 13px;
cursor: pointer;
}
.tagChip:hover {
filter: brightness(1.15);
}
.tagChip.active {
background-color: var(--color-1);
border-color: var(--color-1);
color: var(--bg-2);
font-weight: bold;
}
#gamesort {
border-style: solid;
border-width: 2px;

50
css/home.css Normal file
View File

@ -0,0 +1,50 @@
.homeNav {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
max-width: 700px;
margin-top: 24px;
}
.navCard {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
width: 110px;
height: 100px;
border-radius: 16px;
background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%);
box-shadow: 0 0 10px var(--color-2);
backdrop-filter: blur(2px);
text-decoration: none;
color: var(--text-color);
transition-duration: 0.25s;
}
.navCard:hover {
transform: scale(1.1);
}
.navCard img {
width: 32px;
height: 32px;
user-select: none;
}
.navCard span {
font-size: 14px;
}
body[fast] .navCard {
background-color: color-mix(in srgb, var(--color-2) 70%, #000000 30%);
backdrop-filter: blur(0);
}
@-moz-document url-prefix() {
.navCard {
backdrop-filter: blur(0);
}
}
@media screen and (max-width: 500px) {
.navCard {
width: 90px;
height: 85px;
}
}

View File

@ -36,7 +36,7 @@ body {
#pjs {
width: 100vw;
height: 100vh;
position: absolute;
position: fixed;
z-index: -1000;
border: none;
overflow-y: hidden;

165
css/proxy.css Normal file
View File

@ -0,0 +1,165 @@
body {
overflow: hidden;
align-items: stretch;
justify-content: flex-start;
}
#proxyBar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
flex: 0 0 auto;
background-color: var(--bg-2);
box-shadow: 0 0 10px var(--color-2);
z-index: 10;
}
#proxyBar a {
flex: 0 0 auto;
display: flex;
align-items: center;
}
#proxyBar a img {
width: 28px;
height: 28px;
}
#sj-form {
display: flex;
flex: 1;
gap: 8px;
}
#sj-address {
flex: 1;
width: auto;
margin: 0;
padding: 10px 16px;
font-size: 16px;
}
#proxyBar button {
margin: 0;
}
#proxyFullscreen {
flex: 0 0 auto;
width: 28px;
height: 28px;
cursor: pointer;
transition-duration: 0.25s;
filter: brightness(0.9);
}
#proxyFullscreen:hover {
filter: brightness(1);
transform: scale(1.1);
}
#proxyFullscreen:active {
filter: brightness(1.2);
transform: scale(0.95);
}
#proxyError {
flex: 0 0 auto;
padding: 0 10px;
text-align: left;
}
#proxyError p {
color: #ff8a8a;
font-size: 14px;
margin: 4px 0;
}
#proxyError pre {
color: color-mix(in srgb, var(--text-color) 60%, #00000000 40%);
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
margin: 0 0 6px;
}
#proxyFrameHolder {
flex: 1;
width: 100%;
position: relative;
}
#sj-frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
background-color: black;
display: block;
}
#proxyShortcuts {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 18px;
padding: 24px 16px;
overflow-y: auto;
}
.filterLabel {
font-size: 16px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-color);
margin: 0;
}
#proxyShortcutsGrid {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0;
width: 90vw;
max-width: 1000px;
}
.proxyShortcut {
width: 20vw;
min-width: 180px;
height: 9vh;
min-height: 64px;
background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 20px;
margin: 15px;
border: none;
padding: 0;
backdrop-filter: blur(2px);
box-shadow: 0 0 10px var(--color-2);
color: var(--text-color);
outline: none;
transition-duration: 0.25s;
font-family: inherit;
}
.proxyShortcut:hover {
transform: scale(1.1);
}
.proxyShortcut img {
height: calc(100% - 8px);
border-radius: 15px;
aspect-ratio: 1 / 1;
margin: 4px;
margin-right: auto;
}
.proxyShortcutHolder {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
height: 100%;
}
.proxyShortcutHolder h1 {
font-size: 14px;
margin: 15px;
text-align: right;
}
body[fast] .proxyShortcut {
background-color: color-mix(in srgb, var(--color-2) 70%, #000000 30%);
backdrop-filter: blur(0);
}
@-moz-document url-prefix() {
.proxyShortcut {
backdrop-filter: blur(0);
}
}

View File

@ -1,6 +1,10 @@
body {
justify-content: flex-start;
}
section {
min-width: 20%;
height: 420px;
min-height: 420px;
padding: 8px;
margin: 16px;
background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%);
@ -27,14 +31,132 @@ sections {
margin: 6px;
}
.feedback {
min-height: 16px;
font-size: 14px;
margin: 6px 4px 0;
text-align: center;
}
.feedback.error {
color: #ff8a8a;
}
.feedback.success {
color: #7cffb2;
}
.toggleRow {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
margin: 6px 0;
}
.toggleRow label {
cursor: pointer;
}
.toggleRow .switch {
display: inline-flex;
align-items: center;
flex: none;
}
.toggleRow .switch input[type=checkbox] {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.toggleRow .switchTrack {
display: inline-block;
box-sizing: border-box;
width: 42px;
height: 24px;
border-radius: 999px;
background-color: color-mix(in srgb, var(--color-4) 60%, #00000000 40%);
border: 2px solid var(--color-3);
position: relative;
transition-duration: 0.25s;
}
.toggleRow .switchTrack::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: var(--text-color);
transition-duration: 0.25s;
}
.toggleRow .switch:hover .switchTrack {
filter: brightness(1.15);
}
.toggleRow .switch input[type=checkbox]:checked ~ .switchTrack {
background-color: var(--color-1);
border-color: var(--color-1);
}
.toggleRow .switch input[type=checkbox]:checked ~ .switchTrack::after {
transform: translateX(18px);
background-color: var(--bg-2);
}
.toggleRow .switch input[type=checkbox]:focus-visible ~ .switchTrack {
outline: 2px solid var(--color-1);
outline-offset: 2px;
}
#hiddenGamesSection {
width: min(640px, 90vw);
}
.hiddenGamesRow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
gap: 16px;
width: 100%;
}
.hiddenGamesControls {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
flex: 1 1 220px;
}
#hiddenGamesList {
width: min(280px, 90%);
height: 200px;
width: auto;
flex: 1 1 260px;
align-self: flex-start;
height: 340px;
min-height: 0;
overflow-y: auto;
contain: layout;
border: 1px solid color-mix(in srgb, var(--color-1) 50%, #00000000 50%);
border-radius: 12px;
padding: 8px;
margin: 8px 0;
margin: 0;
box-sizing: border-box;
}
.hidden-game-row {
@ -45,6 +167,58 @@ sections {
width: 100%;
}
.hidden-game-row input[type=checkbox] {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.hidden-game-row .checkboxBox {
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 18px;
height: 18px;
min-width: 18px;
border: 2px solid var(--color-1);
border-radius: 4px;
background-color: color-mix(in srgb, var(--color-4) 40%, #00000000 60%);
cursor: pointer;
transition-duration: 0.25s;
}
.hidden-game-row:hover .checkboxBox {
border-color: var(--text-color);
background-color: color-mix(in srgb, var(--color-3) 50%, #00000000 50%);
}
.hidden-game-row input[type=checkbox]:checked ~ .checkboxBox {
background-color: var(--color-1);
border-color: var(--color-1);
}
.hidden-game-row input[type=checkbox]:checked ~ .checkboxBox::after {
content: "";
width: 4px;
height: 8px;
margin-bottom: 2px;
border: solid var(--bg-2);
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.hidden-game-row input[type=checkbox]:focus-visible ~ .checkboxBox {
outline: 2px solid var(--color-1);
outline-offset: 2px;
}
#hiddenGamesSearch {
width: min(280px, 90%);
}

View File

@ -6,7 +6,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
@ -37,6 +37,7 @@
<!-- initialize my stuff -->
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/pages.css" />
<link rel="stylesheet" href="/css/home.css" />
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Mulish&display=swap" as="style"
onload="this.onload=null;this.rel='stylesheet'" />
<script src="/js/main.js" defer></script>
@ -59,6 +60,24 @@
document.getElementById("randomquote").innerHTML = randomQuote;
})
.catch((error) => console.error("Error loading quotes:", error));
fetch("/api/proxy/status")
.then((res) => res.json())
.then((data) => {
if (data.unlocked) document.getElementById("proxyNavCard").style.display = "";
})
.catch(() => { });
document.getElementById("openblank").addEventListener("click", (e) => {
e.preventDefault();
let win = window.open();
win.document.body.style.margin = "0";
win.document.body.style.height = "100vh";
let html = `<style>*{margin:0;padding:0;border:none;height:100vh;width:100vw}</style><iframe src=index.html></iframe>`;
win.document.querySelector("html").innerHTML = html;
location.href = "https://google.com";
window.close();
});
});
</script>
<link rel="manifest" href="/manifest.json" />
@ -74,16 +93,39 @@
<h1 class="title">selenite.</h1>
<noscript>enable javascript if you want the games to actually load</noscript>
<p id="randomquote">better thank me for this unblocked website</p>
<div class="samerow">
<div class="img-container">
<a href="/users"><img src="img/user_index.svg" /></a>
</div>
<div class="img-container">
<a href="projects.html"><img src="img/games.svg" /></a>
</div>
<div class="img-container">
<a target="_blank" href="https://discord.gg/7jyufnwJNf"><img src="img/discord.svg" /></a>
</div>
<div class="homeNav">
<a class="navCard" href="projects.html">
<img src="img/games.svg" />
<span>games</span>
</a>
<a class="navCard" href="apps.html">
<img src="img/apps.svg" />
<span>apps</span>
</a>
<a class="navCard" href="proxy.html" id="proxyNavCard" style="display: none;">
<img src="img/addlink.svg" />
<span>proxy</span>
</a>
<a class="navCard" href="bookmarklets.html">
<img src="img/bookmarklets.svg" />
<span>bookmarklets</span>
</a>
<a class="navCard" href="/users">
<img src="img/user_index.svg" />
<span>profile</span>
</a>
<a class="navCard" href="settings.html">
<img src="img/settings.svg" />
<span>settings</span>
</a>
<a class="navCard" href="#" id="openblank">
<img src="img/open.svg" />
<span>open about:blank</span>
</a>
<a class="navCard" target="_blank" href="https://discord.gg/7jyufnwJNf">
<img src="img/discord.svg" />
<span>discord</span>
</a>
</div>
</body>

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M680-160v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm560-40h-80q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#FFFFFF"><path d="M680-160v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm560-40h-80q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480Z"/></svg>

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 383 B

View File

@ -6,7 +6,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
@ -144,9 +144,7 @@
let screenIDs = ["zero", "one", "two", "three", "four"];
let screens = screenIDs.map((e) => document.getElementById(e));
let index = 0;
let welcome = document.querySelector("welcome");
// let blur = document.getElementById("blur");
// TODO: finish
hideAll();
@ -238,24 +236,21 @@
}
}
}, 500);
document.addEventListener("keydown", (e) => {
if (currentPanic.key.length > 0 && currentPanic.url.length > 0) {
if (e.key = currentPanic.key) {
function handlePanicKeydown(e) {
if (currentPanic.key.length > 0 && currentPanic.url.length > 0 && e.key.toLowerCase() === currentPanic.key.toLowerCase()) {
location.href = currentPanic.url;
}
}
})
document.getElementById("iframe").contentWindow.document.addEventListener("keydown", (e) => {
if (currentPanic.key.length > 0 && currentPanic.url.length > 0) {
if (e.key = currentPanic.key) {
location.href = currentPanic.url;
}
}
})
document.addEventListener("keydown", handlePanicKeydown);
document.getElementById("iframe").addEventListener("load", () => {
try {
document.getElementById("iframe").contentWindow.document.addEventListener("keydown", handlePanicKeydown);
} catch { }
try {
document.getElementById("iframe").contentWindow.addEventListener("beforeunload", () => {
alert("unloading! show loading bar")
})
} catch { }
});
document.querySelectorAll(".sidebar-item").forEach((item) => {
if (item.childNodes[0].target) {
@ -353,7 +348,7 @@
</div> <!-- open blank -->
<div class="sidebar-item"><a href="#" id="fullscreen"><img src="/img/fullscreen.svg" /></a>
<div class="sidebar-item-descriptor">fullscreen</div>
</div> <!-- open blank -->
</div> <!-- fullscreen -->
<div class="sidebar-divider"></div>
<div class="sidebar-item"><a href="#" target="/u/"><img src="/img/user.svg" /></a>
<div class="sidebar-item-descriptor">user profile</div>

View File

@ -6,7 +6,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
@ -66,6 +66,7 @@
<h2>credits</h2>
<p>whilst the majority of the work has been done by the creator, sky, selenite would not be possible without some
key sources and people.</p>
<p><a href="https://discordapp.com/users/1406787010131398706">firstlynx</a> (continuation and new owner)</p>
<p><a href="https://github.com/a456pur/seraph">seraph</a> by <a href="https://a456pur.dev">a456pur</a> (games)</p>
<p><a href="https://gitlab.com/3kh0/3kh0-assets">3kh0 assets</a> by <a href="https://3kh0.com">3kh0</a> (games)</p>
<p><a href="https://github.com/genizy/web-port">web ports</a> by <a href="https://gn-math.github.io/">gn-math</a>

View File

@ -5,7 +5,9 @@ let topElements = [];
let isDev = [];
let pageData;
let starredGames = [];
let recentGames = [];
let globalPlays = null;
let selectedTags = new Set();
function getHiddenGames() {
try {
@ -16,12 +18,22 @@ function getHiddenGames() {
}
}
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);
@ -50,6 +62,7 @@ async function loadGames() {
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}`;
@ -73,6 +86,7 @@ async function loadGames() {
newElement.appendChild(image);
newElement.appendChild(holder);
newElement.appendChild(warnings);
holder.appendChild(star);
if (element.tags) {
[["18+", "thirteen", "18+"], ["13+", "thirteen", "13+"],
@ -91,18 +105,16 @@ async function loadGames() {
w.className = "thirteen";
warnings.appendChild(w);
}
if (element.tags.includes("top")) {
if (element.tags.includes("top") && !topGamesDisabled) {
topFragment.appendChild(newElement);
topElements.push(newElement);
} else {
gamesFragment.appendChild(newElement);
gameElements.push(newElement);
holder.appendChild(star);
}
} else {
gamesFragment.appendChild(newElement);
gameElements.push(newElement);
holder.appendChild(star);
}
elements.push(newElement);
@ -117,9 +129,10 @@ async function loadGames() {
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} games loaded (${hiddenCount} hidden)`
: `${visibleGameCount} games loaded!`;
? `${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");
@ -134,17 +147,87 @@ async function loadGames() {
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);
}
let searchRaf = null;
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("gamesearch").addEventListener("input", () => {
if (searchRaf) cancelAnimationFrame(searchRaf);
searchRaf = requestAnimationFrame(() => {
const input = document.getElementById("gamesearch").value.toUpperCase();
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) => {
if (el.dataset.name.includes(input)) {
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";
@ -152,6 +235,14 @@ document.addEventListener("DOMContentLoaded", () => {
}
});
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;
});
});
@ -160,15 +251,32 @@ document.addEventListener("DOMContentLoaded", () => {
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}']`);
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);
@ -227,6 +335,18 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
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") || "[]");

View File

@ -98,16 +98,18 @@ document.addEventListener("DOMContentLoaded", async ()=>{
}
})
let scrollRaf = null;
document.addEventListener("scroll", () => {
if (scrollRaf) return;
scrollRaf = requestAnimationFrame(() => {
const pjs = document.getElementById("pjs");
if (pjs) pjs.style.top = window.scrollY + "px";
scrollRaf = null;
});
});
function handlePanicKeydown(e) {
let panic;
try {
panic = JSON.parse(localStorage.getItem("selenite.panic-mode") || "{}");
} catch {
return;
}
if (panic.key && panic.url && e.key.toLowerCase() === panic.key.toLowerCase()) {
location.href = panic.url;
}
}
document.addEventListener("keydown", handlePanicKeydown);
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener('message', (event) => {

View File

@ -20,6 +20,31 @@
<script src="/js/all.min.js" defer></script>
<script defer>
let gameData = {};
function recordRecentlyPlayed(directory) {
if (!directory) return;
let recent = [];
try {
recent = JSON.parse(localStorage.getItem("selenite.recentlyPlayed") || "[]");
} catch {
recent = [];
}
recent = recent.filter((dir) => dir !== directory);
recent.unshift(directory);
if (recent.length > 20) recent.length = 20;
localStorage.setItem("selenite.recentlyPlayed", JSON.stringify(recent));
}
function handlePanicKeydown(e) {
let panic;
try {
panic = JSON.parse(localStorage.getItem("selenite.panic-mode") || "{}");
} catch {
return;
}
if (panic.key && panic.url && e.key.toLowerCase() === panic.key.toLowerCase()) {
location.href = panic.url;
}
}
document.addEventListener("keydown", handlePanicKeydown);
document.addEventListener("DOMContentLoaded", () => {
const gameFrame = document.getElementById("gameFrame");
const gameImage = document.getElementById("gameImage");
@ -35,6 +60,7 @@
gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`;
gameImage.src = `/resources/${prefix}/${gameData.path}/${params.get("img")}`;
gameName.innerText = decodeURIComponent(params.get("title"));
recordRecentlyPlayed(gameData.path);
} else if (localStorage.getItem("selenite.lastGame")) {
const game = JSON.parse(decodeURIComponent(atob(localStorage.getItem("selenite.lastGame"))));
gameData.type = game[3];
@ -43,8 +69,15 @@
gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`;
gameImage.src = `/resources/${prefix}/${gameData.path}/${decodeURIComponent(game[1])}`;
gameName.innerText = game[2];
recordRecentlyPlayed(gameData.path);
}
gameFrame.addEventListener("load", () => {
try {
gameFrame.contentWindow.document.addEventListener("keydown", handlePanicKeydown);
} catch { }
});
document.getElementById("fullscreen").addEventListener("click", () => gameFrame.requestFullscreen());
document.getElementById("reload").addEventListener("click", () => { gameFrame.src = gameFrame.src; });
document.getElementById("open").addEventListener("click", () => window.open(gameFrame.src));

View File

@ -6,7 +6,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
@ -28,7 +28,7 @@
<!-- seo + other things -->
<title>Projects | Selenite</title>
<title>Selenite</title>
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
@ -40,7 +40,13 @@
</head>
<body>
<div class="gamesToolbar">
<input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." />
<button id="filtersToggle" type="button">filters</button>
<button id="randomGame">random game</button>
</div>
<div id="tagFilters">
<p class="filterLabel">sorting</p>
<select id="gamesort">
<option value="az">a -> z</option>
<option value="za">z -> a</option>
@ -48,9 +54,14 @@
<option value="played">your top games</option>
<option value="global">global top games</option>
</select>
<p class="filterLabel" id="tagChipsLabel">game genres</p>
<div id="tagChips"></div>
</div>
<p id="gameCount">xx games loaded..</p>
<p id="starredHeader">starred games</p>
<div id="starredgames"></div>
<p id="recentHeader">recently played</p>
<div id="recentgames"></div>
<p id="topHeader" class="title">top games</p>
<div id="topGames"></div>
<p id="allHeader" class="title">all games</p>

28
proxy-locked.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
</script>
<script src="/js/main.js" defer></script>
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/pages.css" />
<link rel="manifest" href="/manifest.json" />
<title>Selenite</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<h1 class="title">locked</h1>
<p>this is locked behind a secret password.</p>
<p>enter it in <a href="/settings.html">settings</a> to unlock it.</p>
</body>
</html>

201
proxy.html Normal file
View File

@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Selenite",
"alternateName": "selenite.cc",
"url": "https://selenite.cc",
"logo": "https://selenite.cc/favicon.png",
"sameAs": [
"https://github.com/selenite-cc",
"https://youtube.com/@selenitecc",
"https://tiktok.com/@selenitecc",
"https://selenite.cc",
]
}
</script>
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/pages.css" />
<link rel="stylesheet" href="/css/proxy.css" />
<link rel="manifest" href="/manifest.json" />
<title>Selenite</title>
<link rel="icon" href="/favicon.ico" />
<script src="/scram-assets/scramjet.all.js"></script>
<script src="/scram-assets/baremux/index.js"></script>
<script defer>
"use strict";
const swAllowedHostnames = ["localhost", "127.0.0.1"];
async function registerProxySW() {
if (!navigator.serviceWorker) {
throw new Error("your browser doesn't support service workers.");
}
if (location.protocol !== "https:" && !swAllowedHostnames.includes(location.hostname)) {
throw new Error("service workers cannot be registered without https.");
}
const registration = await navigator.serviceWorker.register("/scram-sw.js", { scope: "/scram/" });
const worker = registration.installing || registration.waiting || registration.active;
if (worker && worker.state !== "activated") {
await new Promise((resolve) => {
worker.addEventListener("statechange", () => {
if (worker.state === "activated") resolve();
});
});
}
}
async function renderProxyShortcuts(onSelect) {
const grid = document.getElementById("proxyShortcutsGrid");
if (!grid) return;
try {
const sites = await (await fetch("/resources/proxysites.json")).json();
const fragment = document.createDocumentFragment();
sites.forEach((site) => {
const button = document.createElement("button");
button.type = "button";
button.className = "proxyShortcut";
button.dataset.url = site.url;
const icon = document.createElement("img");
icon.src = site.icon || `https://s2.googleusercontent.com/s2/favicons?domain_url=${encodeURIComponent(site.url)}&sz=256`;
if (site.iconBg) {
icon.style.backgroundColor = site.iconBg;
icon.style.padding = "10px";
icon.style.boxSizing = "border-box";
}
const holder = document.createElement("div");
holder.className = "proxyShortcutHolder";
const title = document.createElement("h1");
title.innerText = site.name;
holder.appendChild(title);
button.appendChild(icon);
button.appendChild(holder);
button.addEventListener("click", () => onSelect(site.url));
fragment.appendChild(button);
});
grid.appendChild(fragment);
} catch {
const shortcuts = document.getElementById("proxyShortcuts");
if (shortcuts) shortcuts.style.display = "none";
}
}
function resolveAddress(input) {
try {
return new URL(input).toString();
} catch { }
try {
const url = new URL(`http://${input}`);
if (url.hostname.includes(".")) return url.toString();
} catch { }
return `https://www.google.com/search?q=${encodeURIComponent(input)}`;
}
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("sj-form");
const address = document.getElementById("sj-address");
const error = document.getElementById("sj-error");
const errorCode = document.getElementById("sj-error-code");
const frameHolder = document.getElementById("proxyFrameHolder");
const { ScramjetController } = $scramjetLoadController();
const scramjet = new ScramjetController({
prefix: "/scram/",
files: {
wasm: "/scram-assets/scramjet.wasm.wasm",
all: "/scram-assets/scramjet.all.js",
sync: "/scram-assets/scramjet.sync.js",
},
});
scramjet.init();
const connection = new BareMux.BareMuxConnection("/scram-assets/baremux/worker.js");
form.addEventListener("submit", async (event) => {
event.preventDefault();
error.textContent = "";
errorCode.textContent = "";
try {
await registerProxySW();
} catch (err) {
error.textContent = "failed to register service worker.";
errorCode.textContent = err.toString();
return;
}
const url = resolveAddress(address.value);
try {
const wispUrl = (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + "/wisp/";
if ((await connection.getTransport()) !== "/scram-assets/libcurl/index.mjs") {
await connection.setTransport("/scram-assets/libcurl/index.mjs", [{ websocket: wispUrl }]);
}
} catch (err) {
error.textContent = "failed to set up the proxy connection.";
errorCode.textContent = err.toString();
return;
}
frameHolder.innerHTML = "";
const frame = scramjet.createFrame();
frame.frame.id = "sj-frame";
frameHolder.appendChild(frame.frame);
frame.go(url);
});
document.getElementById("proxyFullscreen").addEventListener("click", () => {
const frame = document.getElementById("sj-frame");
if (frame) frame.requestFullscreen();
});
renderProxyShortcuts((url) => {
address.value = url;
form.requestSubmit();
});
});
</script>
</head>
<body>
<div id="proxyBar">
<a href="/home.html" title="back to home"><img src="/img/home.svg" /></a>
<form id="sj-form">
<input id="sj-address" type="text" placeholder="search or enter a url" autocomplete="off" />
<button type="submit">go</button>
</form>
<img id="proxyFullscreen" src="/img/fullscreen.svg" title="fullscreen" />
</div>
<div id="proxyError">
<p id="sj-error"></p>
<pre id="sj-error-code"></pre>
</div>
<div id="proxyFrameHolder">
<div id="proxyShortcuts">
<p class="filterLabel">recommended sites</p>
<div id="proxyShortcutsGrid"></div>
</div>
</div>
</body>
</html>

16
scram-sw.js Normal file
View File

@ -0,0 +1,16 @@
importScripts("/scram-assets/scramjet.all.js");
const { ScramjetServiceWorker } = $scramjetLoadWorker();
const scramjet = new ScramjetServiceWorker();
async function handleRequest(event) {
await scramjet.loadConfig();
if (scramjet.route(event)) {
return scramjet.fetch(event);
}
return fetch(event.request);
}
self.addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event));
});

View File

@ -6,7 +6,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N');
@ -84,9 +84,19 @@
if (localStorage.getItem("selenite.super-fast-mode") == 'true') {
document.getElementById("superFastMode").setAttribute("checked", "")
};
if (localStorage.getItem("selenite.disableTopGames") == 'false') {
if (localStorage.getItem("selenite.disableTopGames") == 'true') {
document.getElementById("topGamesToggle").setAttribute("checked", "")
};
if (localStorage.getItem("selenite.disableRecentlyPlayed") == 'true') {
document.getElementById("recentlyPlayedToggle").setAttribute("checked", "")
};
try {
const savedPanic = JSON.parse(localStorage.getItem("selenite.panic-mode") || "null");
if (savedPanic && savedPanic.key && savedPanic.url) {
document.getElementById("panicKey").value = savedPanic.key;
document.getElementById("panicURL").value = savedPanic.url;
}
} catch { }
document.getElementById("themes").addEventListener("input", (e) => {
localStorage.setItem("selenite.theme", e.target.value);
document.body.setAttribute("theme", e.target.value);
@ -114,26 +124,93 @@
location.reload();
});
document.getElementById("topGamesToggle").addEventListener("click", (e) => {
localStorage.setItem("selenite.disableTopGames", (e.target.checked == false));
localStorage.setItem("selenite.disableTopGames", e.target.checked ? "true" : "false");
});
document.getElementById("submitCloak").addEventListener("click", (e) => {
if (document.getElementById("cloaks").value) {
localStorage.setItem("selenite.tab-cloak", JSON.stringify({
"name": cloaks[document.getElementById("cloaks").value][0],
"icon": cloaks[document.getElementById("cloaks").value][1]
}));
document.getElementById("recentlyPlayedToggle").addEventListener("click", (e) => {
localStorage.setItem("selenite.disableRecentlyPlayed", e.target.checked ? "true" : "false");
});
document.getElementById("submitCloak").addEventListener("click", () => {
const feedback = document.getElementById("cloakFeedback");
const presetValue = document.getElementById("cloaks").value;
let name, icon;
if (presetValue) {
name = cloaks[presetValue][0];
icon = cloaks[presetValue][1];
} else {
localStorage.setItem("selenite.tab-cloak", JSON.stringify({
"name": document.getElementById("cloakName").value || "",
"icon": document.getElementById("cloakIcon").value || ""
}));
name = document.getElementById("cloakName").value.trim();
icon = document.getElementById("cloakIcon").value.trim();
}
localStorage.setItem("selenite.tab-cloak", JSON.stringify({ name, icon }));
if (!name && !icon) {
feedback.textContent = "tab cloak reset to default.";
feedback.className = "feedback";
} else {
feedback.textContent = `tab cloak set to "${name || "Selenite"}"!`;
feedback.className = "feedback success";
}
});
document.getElementById("submitPanic").addEventListener("click", (e) => {
localStorage.setItem("selenite.panic-mode", JSON.stringify({
"key": document.getElementById("panicKey").value || "",
"url": document.getElementById("panicURL").value || ""
}));
document.getElementById("submitPanic").addEventListener("click", () => {
const feedback = document.getElementById("panicFeedback");
const key = document.getElementById("panicKey").value.trim();
let url = document.getElementById("panicURL").value.trim();
if (!key) {
feedback.textContent = "enter a key to trigger panic mode.";
feedback.className = "feedback error";
return;
}
if (!url) {
feedback.textContent = "enter a website to redirect to.";
feedback.className = "feedback error";
return;
}
if (!/^https?:\/\//i.test(url)) {
url = "https://" + url;
}
try {
new URL(url);
} catch {
feedback.textContent = "that doesn't look like a valid website url.";
feedback.className = "feedback error";
return;
}
localStorage.setItem("selenite.panic-mode", JSON.stringify({ key, url }));
document.getElementById("panicURL").value = url;
feedback.textContent = `panic mode saved! press "${key}" to instantly leave.`;
feedback.className = "feedback success";
});
document.getElementById("submitProxyUnlock").addEventListener("click", () => {
const feedback = document.getElementById("proxyUnlockFeedback");
const passwordInput = document.getElementById("proxyPassword");
const password = passwordInput.value;
if (!password) {
feedback.textContent = "enter a password.";
feedback.className = "feedback error";
return;
}
fetch("/api/proxy/unlock", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password }),
})
.then((res) => res.json())
.then((data) => {
if (data.success) {
feedback.textContent = "beta access granted!";
feedback.className = "feedback success";
passwordInput.value = "";
} else {
feedback.textContent = data.message || "incorrect password.";
feedback.className = "feedback error";
}
})
.catch(() => {
feedback.textContent = "failed to reach the server.";
feedback.className = "feedback error";
});
});
document.getElementById("toggleCDN").addEventListener("change", () => {
let toggle = document.getElementById("toggleCDN");
@ -207,10 +284,14 @@
updateHiddenGamesCount(games.length);
});
const box = document.createElement("span");
box.className = "checkboxBox";
const text = document.createElement("span");
text.innerText = game.name;
row.appendChild(checkbox);
row.appendChild(box);
row.appendChild(text);
list.appendChild(row);
});
@ -234,6 +315,27 @@
searchInput.addEventListener("input", () => {
renderHiddenGameList(games, new Set(getHiddenGames()), searchInput.value);
});
document.getElementById("unhideAllGames").addEventListener("click", () => {
const feedback = document.getElementById("hiddenGamesFeedback");
const previousCount = getHiddenGames().length;
setHiddenGames([]);
renderHiddenGameList(games, new Set(), searchInput.value);
updateHiddenGamesCount(games.length);
if (feedback) {
if (previousCount > 0) {
feedback.textContent = `unhid ${previousCount} game${previousCount === 1 ? "" : "s"}!`;
feedback.className = "feedback success";
} else {
feedback.textContent = "no games were hidden.";
feedback.className = "feedback";
}
}
});
}
function clearRecentlyPlayed() {
localStorage.removeItem("selenite.recentlyPlayed");
}
function wipeData() {
@ -244,38 +346,85 @@
})
}
}
function setDataFeedback(message, type) {
const el = document.getElementById("dataFeedback");
if (!el) return;
el.textContent = message;
el.className = "feedback" + (type ? " " + type : "");
}
function downloadData() {
let storage = JSON.stringify(localStorage);
let cookies = document.cookie;
let data = btoa(JSON.stringify({ localStorage: storage, cookies: cookies }));
let file = new Blob([data], { type: "text" });
let a = document.createElement("a");
a.href = URL.createObjectURL(file);
try {
const data = JSON.stringify({
localStorage: { ...localStorage },
cookies: document.cookie
});
const file = new Blob([data], { type: "application/json" });
const url = URL.createObjectURL(file);
const a = document.createElement("a");
a.href = url;
a.download = "selenite.dat";
a.click();
URL.revokeObjectURL(url);
setDataFeedback("data downloaded!", "success");
} catch (err) {
setDataFeedback("failed to download data: " + err.message, "error");
}
}
function parseBackup(text) {
let data;
try {
data = JSON.parse(text);
} catch {
// fall back to the old base64 + double-stringified-localStorage format
data = JSON.parse(atob(text));
}
if (!data || typeof data.cookies !== "string") {
throw new Error("this file doesn't look like a selenite backup.");
}
let local = data.localStorage;
if (typeof local === "string") local = JSON.parse(local);
if (!local || typeof local !== "object") {
throw new Error("this file doesn't look like a selenite backup.");
}
return { localStorage: local, cookies: data.cookies };
}
function uploadData() {
let input = document.createElement("input");
const input = document.createElement("input");
input.type = "file";
input.accept = "dat";
input.click();
input.accept = ".dat,application/json";
input.addEventListener("change", e => {
if (prompt("Are you sure you want to upload data?\nThis may overwrite any previous data you had stored.\n\nType \"i am sure\" to continue.") == "i am sure") {
let fileReader = new FileReader();
fileReader.addEventListener("load", e => {
let data = JSON.parse(atob(e.target.result));
let local = JSON.parse(data["localStorage"]);
let cookies = data["cookies"];
const file = e.target.files[0];
if (!file) return;
if (prompt("Are you sure you want to upload data?\nThis may overwrite any previous data you had stored.\n\nType \"i am sure\" to continue.") != "i am sure") return;
document.cookie = cookies;
Object.keys(local).forEach(e => {
localStorage.setItem(e, local[e]);
})
const fileReader = new FileReader();
fileReader.addEventListener("load", e => {
try {
const { localStorage: local, cookies } = parseBackup(e.target.result);
document.cookie.split(";").forEach(c => {
const name = c.split("=")[0].trim();
if (name) document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
});
fileReader.readAsText(e.target.files[0]);
cookies.split(";").forEach(c => {
c = c.trim();
if (c) document.cookie = c;
});
Object.keys(local).forEach(key => {
localStorage.setItem(key, local[key]);
});
setDataFeedback("data uploaded! reload the page to see changes.", "success");
} catch (err) {
setDataFeedback("failed to upload data: " + err.message, "error");
}
})
});
fileReader.addEventListener("error", () => {
setDataFeedback("failed to read that file.", "error");
});
fileReader.readAsText(file);
});
input.click();
};
async function deleteAllCaches() {
try {
@ -308,12 +457,18 @@
</select>
<input type="color" id="colorPicker" style="display: none">
<p style="font-size:20px;margin:6px;">toggles</p>
<div class="samerow"><input type="checkbox" id="fastMode"><label for="fastMode"
<div class="toggleRow"><label class="switch"><input type="checkbox" id="fastMode"><span
class="switchTrack"></span></label><label for="fastMode"
title="disables particles, animated background, & other misc effects">fast mode</label></div>
<div class="samerow"><input type="checkbox" id="superFastMode"><label for="superFastMode"
<div class="toggleRow"><label class="switch"><input type="checkbox" id="superFastMode"><span
class="switchTrack"></span></label><label for="superFastMode"
title="disables all blurs and transparency">super fast mode</label></div>
<div class="samerow"><input type="checkbox" id="topGamesToggle"><label for="topGamesToggle">disable top
<div class="toggleRow"><label class="switch"><input type="checkbox" id="topGamesToggle"><span
class="switchTrack"></span></label><label for="topGamesToggle">disable top
games</label></div>
<div class="toggleRow"><label class="switch"><input type="checkbox" id="recentlyPlayedToggle"><span
class="switchTrack"></span></label><label for="recentlyPlayedToggle">disable
recently played</label></div>
</section>
<section>
<h2>tab cloak</h2>
@ -332,6 +487,7 @@
</select>
<button id="submitCloak">set tab cloak</button>
<p id="cloakFeedback" class="feedback"></p>
</section>
<section>
<h2>panic mode</h2>
@ -340,6 +496,7 @@
<p title="the url itll direct you to">website</p>
<input type="text" id="panicURL">
<button id="submitPanic">set panic mode</button>
<p id="panicFeedback" class="feedback"></p>
</section>
<section>
<h2>manage data</h2>
@ -347,13 +504,29 @@
<button onclick="uploadData()">upload</button>
<button onclick="wipeData()">wipe (erases all game data)</button>
<button onclick="deleteAllCaches()">wipe all cache</button>
<input id="toggleCDN" name="toggleCDN" type="checkbox"></inpuit> <label for="toggleCDN">cdn enabled</label>
<button onclick="clearRecentlyPlayed()">clear recently played</button>
<div class="toggleRow"><label class="switch"><input id="toggleCDN" name="toggleCDN" type="checkbox"><span
class="switchTrack"></span></label><label for="toggleCDN">cdn enabled</label></div>
<p id="dataFeedback" class="feedback"></p>
</section>
<section>
<section id="hiddenGamesSection">
<h2>hidden games</h2>
<p id="hiddenGamesCount">0 hidden</p>
<div class="hiddenGamesRow">
<div class="hiddenGamesControls">
<input type="text" id="hiddenGamesSearch" placeholder="search games">
<button id="unhideAllGames">unhide all</button>
<p id="hiddenGamesFeedback" class="feedback"></p>
</div>
<div id="hiddenGamesList"></div>
</div>
</section>
<section>
<h2>beta access</h2>
<p title="the secret password for beta access">password</p>
<input type="password" id="proxyPassword" autocomplete="off">
<button id="submitProxyUnlock">unlock</button>
<p id="proxyUnlockFeedback" class="feedback"></p>
</section>
</sections>
</body>